|
19 | 19 | from rta import get_ttp_names
|
20 | 20 |
|
21 | 21 | from detection_rules import attack, beats, ecs
|
| 22 | +from detection_rules.packaging import load_versions |
22 | 23 | from detection_rules.rule_loader import FILE_PATTERN, find_unneeded_defaults_from_rule
|
23 |
| -from detection_rules.utils import load_etc_dump |
| 24 | +from detection_rules.utils import get_path, load_etc_dump |
24 | 25 | from detection_rules.rule import Rule
|
25 | 26 |
|
26 | 27 | from .base import BaseRuleTest
|
@@ -60,9 +61,20 @@ def test_file_names(self):
|
60 | 61 |
|
61 | 62 | def test_all_rules_as_rule_schema(self):
|
62 | 63 | """Ensure that every rule file validates against the rule schema."""
|
| 64 | + rules_path = get_path('rules') |
| 65 | + |
63 | 66 | for file_name, contents in self.rule_files.items():
|
64 | 67 | rule = Rule(file_name, contents)
|
65 |
| - rule.validate(as_rule=True) |
| 68 | + |
| 69 | + if rule.metadata['maturity'] == 'deprecated': |
| 70 | + continue |
| 71 | + |
| 72 | + try: |
| 73 | + rule.validate(as_rule=True) |
| 74 | + except jsonschema.ValidationError as exc: |
| 75 | + rule_path = Path(rule.path).relative_to(rules_path) |
| 76 | + exc.message = f'{rule_path} -> {exc}' |
| 77 | + raise exc |
66 | 78 |
|
67 | 79 | def test_all_rule_queries_optimized(self):
|
68 | 80 | """Ensure that every rule query is in optimized form."""
|
@@ -430,6 +442,43 @@ def test_updated_date_newer_than_creation(self):
|
430 | 442 | err_msg = f'The following rules have an updated_date older than the creation_date\n {rules_str}'
|
431 | 443 | self.fail(err_msg)
|
432 | 444 |
|
| 445 | + def test_deprecated_rules(self): |
| 446 | + """Test that deprecated rules are properly handled.""" |
| 447 | + versions = load_versions() |
| 448 | + deprecations = load_etc_dump('deprecated_rules.json') |
| 449 | + deprecated_rules = {} |
| 450 | + |
| 451 | + for rule in self.rules: |
| 452 | + maturity = rule.metadata['maturity'] |
| 453 | + |
| 454 | + if maturity == 'deprecated': |
| 455 | + deprecated_rules[rule.id] = rule |
| 456 | + err_msg = f'{self.rule_str(rule)} cannot be deprecated if it has not been version locked. ' \ |
| 457 | + f'Convert to `development` or delete the rule file instead' |
| 458 | + self.assertIn(rule.id, versions, err_msg) |
| 459 | + |
| 460 | + rule_path = Path(rule.path).relative_to(get_path('rules')) |
| 461 | + err_msg = f'{self.rule_str(rule)} deprecated rules should be stored in ' \ |
| 462 | + f'"{get_path("rules", "_deprecated")}" folder' |
| 463 | + self.assertEqual('_deprecated', rule_path.parts[0], err_msg) |
| 464 | + |
| 465 | + err_msg = f'{self.rule_str(rule)} missing deprecation date' |
| 466 | + self.assertIn('deprecation_date', rule.metadata, err_msg) |
| 467 | + |
| 468 | + err_msg = f'{self.rule_str(rule)} deprecation_date and updated_date should match' |
| 469 | + self.assertEqual(rule.metadata['deprecation_date'], rule.metadata['updated_date'], err_msg) |
| 470 | + |
| 471 | + missing_rules = sorted(set(versions).difference(set(self.rule_lookup))) |
| 472 | + missing_rule_strings = '\n '.join(f'{r} - {versions[r]["rule_name"]}' for r in missing_rules) |
| 473 | + err_msg = f'Deprecated rules should not be removed, but moved to the rules/_deprecated folder instead. ' \ |
| 474 | + f'The following rules have been version locked and are missing. ' \ |
| 475 | + f'Re-add to the deprecated folder and update maturity to "deprecated": \n {missing_rule_strings}' |
| 476 | + self.assertEqual([], missing_rules, err_msg) |
| 477 | + |
| 478 | + for rule_id, entry in deprecations.items(): |
| 479 | + rule_str = f'{rule_id} - {entry["rule_name"]} ->' |
| 480 | + self.assertIn(rule_id, deprecated_rules, f'{rule_str} is logged in "deprecated_rules.json" but is missing') |
| 481 | + |
433 | 482 |
|
434 | 483 | class TestTuleTiming(BaseRuleTest):
|
435 | 484 | """Test rule timing and timestamps."""
|
|
0 commit comments