Skip to content
Merged
1 change: 1 addition & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion detection_rules/kbwrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/kibana/kibana/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/kibana/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down