diff --git a/CLI.md b/CLI.md index 61e43abd6..39ea3f9f6 100644 --- a/CLI.md +++ b/CLI.md @@ -516,6 +516,7 @@ Options: Directory to export exceptions to -da, --default-author TEXT Default author for rules missing one -r, --rule-id TEXT Optional Rule IDs to restrict export to + -rn, --rule-name TEXT Optional Rule name to restrict export to (KQL, case-insensitive, supports wildcards) -ac, --export-action-connectors Include action connectors in export -e, --export-exceptions Include exceptions in export diff --git a/detection_rules/kbwrap.py b/detection_rules/kbwrap.py index c5feebc02..b6802b7ef 100644 --- a/detection_rules/kbwrap.py +++ b/detection_rules/kbwrap.py @@ -195,6 +195,8 @@ def kibana_import_rules(ctx: click.Context, rules: RuleCollection, overwrite: Op @click.option("--exceptions-directory", "-ed", required=False, type=Path, help="Directory to export exceptions to") @click.option("--default-author", "-da", type=str, required=False, help="Default author for rules missing one") @click.option("--rule-id", "-r", multiple=True, help="Optional Rule IDs to restrict export to") +@click.option("--rule-name", "-rn", required=False, help="Optional Rule name to restrict export to " + "(KQL, case-insensitive, supports wildcards)") @click.option("--export-action-connectors", "-ac", is_flag=True, help="Include action connectors in export") @click.option("--export-exceptions", "-e", is_flag=True, help="Include exceptions in export") @click.option("--skip-errors", "-s", is_flag=True, help="Skip errors when exporting rules") @@ -207,14 +209,24 @@ def kibana_import_rules(ctx: click.Context, rules: RuleCollection, overwrite: Op @click.pass_context def kibana_export_rules(ctx: click.Context, directory: Path, action_connectors_directory: Optional[Path], exceptions_directory: Optional[Path], default_author: str, - rule_id: Optional[Iterable[str]] = None, export_action_connectors: bool = False, + rule_id: Optional[Iterable[str]] = None, rule_name: Optional[str] = None, + export_action_connectors: bool = False, export_exceptions: bool = False, skip_errors: bool = False, strip_version: bool = False, no_tactic_filename: bool = False, local_creation_date: bool = False, local_updated_date: bool = False) -> List[TOMLRule]: """Export custom rules from Kibana.""" kibana = ctx.obj["kibana"] kibana_include_details = export_exceptions or export_action_connectors + + # Only allow one of rule_id or rule_name + if rule_name and rule_id: + raise click.UsageError("Cannot use --rule-id and --rule-name together. Please choose one.") + with kibana: + # Look up rule IDs by name if --rule-name was provided + if rule_name: + found = RuleResource.find(filter=f"alert.attributes.name:{rule_name}") + rule_id = [r["rule_id"] for r in found] results = RuleResource.export_rules(list(rule_id), exclude_export_details=not kibana_include_details) # Handle Exceptions Directory Location diff --git a/lib/kibana/kibana/resources.py b/lib/kibana/kibana/resources.py index a46d2530f..b29199210 100644 --- a/lib/kibana/kibana/resources.py +++ b/lib/kibana/kibana/resources.py @@ -42,7 +42,8 @@ class BaseResource(dict): if per_page is None: per_page = DEFAULT_PAGE_SIZE - params.setdefault("sort_field", "_id") + # _id is no valid sort field so we sort by name by default + params.setdefault("sort_field", "name") params.setdefault("sort_order", "asc") return ResourceIterator(cls, cls.BASE_URI + "/_find", per_page=per_page, **params) diff --git a/lib/kibana/pyproject.toml b/lib/kibana/pyproject.toml index a2a9edf34..7a703adab 100644 --- a/lib/kibana/pyproject.toml +++ b/lib/kibana/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "detection-rules-kibana" -version = "0.4.2" +version = "0.4.3" description = "Kibana API utilities for Elastic Detection Rules" license = {text = "Elastic License v2"} keywords = ["Elastic", "Kibana", "Detection Rules", "Security", "Elasticsearch"] diff --git a/pyproject.toml b/pyproject.toml index 24b7d7d4b..6de03a142 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "detection_rules" -version = "1.0.11" +version = "1.0.12" 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"