diff --git a/CLI.md b/CLI.md index 61e43abd6d3..39ea3f9f65f 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 c5feebc021a..b6802b7efa2 100644 --- a/detection_rules/kbwrap.py +++ b/detection_rules/kbwrap.py @@ -195,6 +195,8 @@ def _process_imported_items(imported_items_list, item_type_description, item_key @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 _process_imported_items(imported_items_list, item_type_description, item_key @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 a46d2530f14..b2919921041 100644 --- a/lib/kibana/kibana/resources.py +++ b/lib/kibana/kibana/resources.py @@ -42,7 +42,8 @@ def find(cls, per_page=None, **params) -> iter: 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 a2a9edf34c6..7a703adab9d 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 24b7d7d4b5f..6de03a14205 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"