From 8eecd39e9c07f26e2a4da81ca98a11b60315f611 Mon Sep 17 00:00:00 2001 From: Eric Forte <119343520+eric-forte-elastic@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:16:32 -0500 Subject: [PATCH] [Bug] [DaC] Metadata maturity field default mismatch and poor enforcement of rule naming conventions (#4285) * Add stub for solution * Add date and maturity logic * Add date and maturity logic * Version Bump * Remove Date Inheritance * Remove Datetime import (cherry picked from commit 2ea674ce84e52d8b785926961012070d74146f58) --- detection_rules/cli_utils.py | 26 ++++++++++++++++++++++++-- detection_rules/kbwrap.py | 19 +++++++++++++++---- pyproject.toml | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/detection_rules/cli_utils.py b/detection_rules/cli_utils.py index ab0e64a843d..e2701038d52 100644 --- a/detection_rules/cli_utils.py +++ b/detection_rules/cli_utils.py @@ -22,7 +22,7 @@ DEFAULT_PREBUILT_RULES_DIRS, RuleCollection, dict_filter) from .schemas import definitions -from .utils import clear_caches +from .utils import clear_caches, rulename_to_filename def single_collection(f): @@ -95,6 +95,16 @@ def get_collection(*args, **kwargs): if len(rules) == 0: client_error("No rules found") + # Warn that if the path does not match the expected path, it will be saved to the expected path + for rule in rules: + threat = rule.contents.data.get("threat") + first_tactic = threat[0].tactic.name if threat else "" + rule_name = rulename_to_filename(rule.contents.data.name, tactic_name=first_tactic) + if rule.path.name != rule_name: + click.secho( + f"WARNING: Rule path does not match required path: {rule.path.name} != {rule_name}", fg="yellow" + ) + kwargs["rules"] = rules return f(*args, **kwargs) @@ -200,7 +210,19 @@ def rule_prompt(path=None, rule_type=None, required_only=True, save=True, verbos # DEFAULT_PREBUILT_RULES_DIRS[0] is a required directory just as a suggestion suggested_path = Path(DEFAULT_PREBUILT_RULES_DIRS[0]) / contents['name'] path = Path(path or input(f'File path for rule [{suggested_path}]: ') or suggested_path).resolve() - meta = {'creation_date': creation_date, 'updated_date': creation_date, 'maturity': 'development'} + # Inherit maturity from the rule already exists + maturity = "development" + if path.exists(): + rules = RuleCollection() + rules.load_file(path) + if rules: + maturity = rules.rules[0].contents.metadata.maturity + + meta = { + "creation_date": creation_date, + "updated_date": creation_date, + "maturity": maturity, + } try: rule = TOMLRule(path=Path(path), contents=TOMLRuleContents.from_dict({'rule': contents, 'metadata': meta})) diff --git a/detection_rules/kbwrap.py b/detection_rules/kbwrap.py index 527920357b4..8307f3a6079 100644 --- a/detection_rules/kbwrap.py +++ b/detection_rules/kbwrap.py @@ -237,10 +237,21 @@ def kibana_export_rules(ctx: click.Context, directory: Path, action_connectors_d rule_resource["author"] = rule_resource.get("author") or default_author or [rule_resource.get("created_by")] if isinstance(rule_resource["author"], str): rule_resource["author"] = [rule_resource["author"]] - contents = TOMLRuleContents.from_rule_resource(rule_resource, maturity="production") - threat = contents.data.get("threat") - first_tactic = threat[0].tactic.name if threat else "" - rule_name = rulename_to_filename(contents.data.name, tactic_name=first_tactic) + # Inherit maturity from the rule already exists + maturity = "development" + threat = rule_resource.get("threat") + first_tactic = threat[0].get("tactic").get("name") if threat else "" + rule_name = rulename_to_filename(rule_resource.get("name"), tactic_name=first_tactic) + # check if directory / f"{rule_name}" exists + if (directory / f"{rule_name}").exists(): + rules = RuleCollection() + rules.load_file(directory / f"{rule_name}") + if rules: + maturity = rules.rules[0].contents.metadata.maturity + + contents = TOMLRuleContents.from_rule_resource( + rule_resource, maturity=maturity + ) rule = TOMLRule(contents=contents, path=directory / f"{rule_name}") except Exception as e: if skip_errors: diff --git a/pyproject.toml b/pyproject.toml index aceb817e64f..933b63954e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "detection_rules" -version = "0.3.19" +version = "0.4.0" description = "Detection Rules is the home for rules used by Elastic Security. This repository is used for the development, maintenance, testing, validation, and release of rules for Elastic Security’s Detection Engine." readme = "README.md" requires-python = ">=3.12"