Skip to content

Commit ce4b940

Browse files
Add support for local file contents
1 parent bbdde20 commit ce4b940

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

detection_rules/kbwrap.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ def _process_imported_items(
250250
'"alert.attributes.tags: \\"test\\"" to filter for rules that have the tag "test"'
251251
),
252252
)
253+
@click.option(
254+
"--load-rule-loading",
255+
"-lr",
256+
is_flag=True,
257+
help="Enable arbitrary rule loading from the rules directory (Can be very slow!)",
258+
)
253259
@click.pass_context
254260
def kibana_export_rules( # noqa: PLR0912, PLR0913, PLR0915
255261
ctx: click.Context,
@@ -268,6 +274,7 @@ def kibana_export_rules( # noqa: PLR0912, PLR0913, PLR0915
268274
local_updated_date: bool = False,
269275
custom_rules_only: bool = False,
270276
export_query: str | None = None,
277+
load_rule_loading: bool = False,
271278
) -> list[TOMLRule]:
272279
"""Export custom rules from Kibana."""
273280
kibana = ctx.obj["kibana"]
@@ -277,6 +284,10 @@ def kibana_export_rules( # noqa: PLR0912, PLR0913, PLR0915
277284
if rule_name and rule_id:
278285
raise click.UsageError("Cannot use --rule-id and --rule-name together. Please choose one.")
279286

287+
rules = None
288+
if load_rule_loading:
289+
rules = RuleCollection.default()
290+
280291
with kibana:
281292
# Look up rule IDs by name if --rule-name was provided
282293
if rule_name:
@@ -358,10 +369,16 @@ def kibana_export_rules( # noqa: PLR0912, PLR0913, PLR0915
358369
tactic_name = first_tactic if not no_tactic_filename else None # type: ignore[reportUnknownMemberType]
359370
rule_name = rulename_to_filename(rule_resource.get("name"), tactic_name=tactic_name) # type: ignore[reportUnknownMemberType]
360371

372+
local_contents = None
361373
save_path = directory / f"{rule_name}"
374+
if rules and params.get("rule_id") in rules.id_map:
375+
save_path = rules.id_map[params["rule_id"]].path
376+
local_contents = rules.id_map[params["rule_id"]].contents
362377
params.update(
363378
update_metadata_from_file(
364-
save_path, {"creation_date": local_creation_date, "updated_date": local_updated_date}
379+
{"creation_date": local_creation_date, "updated_date": local_updated_date},
380+
save_path,
381+
local_contents,
365382
)
366383
)
367384
contents = TOMLRuleContents.from_rule_resource(**params) # type: ignore[reportArgumentType]

detection_rules/main.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ def generate_rules_index(
157157
@click.option("--strip-none-values", "-snv", is_flag=True, help="Strip None values from the rule")
158158
@click.option("--local-creation-date", "-lc", is_flag=True, help="Preserve the local creation date of the rule")
159159
@click.option("--local-updated-date", "-lu", is_flag=True, help="Preserve the local updated date of the rule")
160+
@click.option(
161+
"--load-rule-loading",
162+
"-lr",
163+
is_flag=True,
164+
help="Enable arbitrary rule loading from the rules directory (Can be very slow!)",
165+
)
160166
def import_rules_into_repo( # noqa: PLR0912, PLR0913, PLR0915
161167
input_file: tuple[Path, ...] | None,
162168
required_only: bool,
@@ -171,6 +177,7 @@ def import_rules_into_repo( # noqa: PLR0912, PLR0913, PLR0915
171177
strip_none_values: bool,
172178
local_creation_date: bool,
173179
local_updated_date: bool,
180+
load_rule_loading: bool,
174181
) -> None:
175182
"""Import rules from json, toml, or yaml files containing Kibana exported rule(s)."""
176183
errors: list[str] = []
@@ -189,6 +196,10 @@ def import_rules_into_repo( # noqa: PLR0912, PLR0913, PLR0915
189196
if not file_contents:
190197
click.echo("Must specify at least one file!")
191198

199+
rules = None
200+
if load_rule_loading:
201+
rules = RuleCollection.default()
202+
192203
exceptions_containers = {}
193204
exceptions_items = {}
194205

@@ -210,7 +221,14 @@ def import_rules_into_repo( # noqa: PLR0912, PLR0913, PLR0915
210221
base_path = rulename_to_filename(base_path) if base_path else base_path
211222
if base_path is None:
212223
raise ValueError(f"Invalid rule file, please ensure the rule has a name field: {contents}")
213-
rule_path = Path(os.path.join(str(save_directory) if save_directory else RULES_DIRS[0], base_path)) # noqa: PTH118
224+
225+
local_contents = None
226+
rule_path = Path(
227+
os.path.join(str(save_directory) if save_directory else RULES_DIRS[0], base_path) # noqa: PTH118
228+
)
229+
if rules and contents.get("rule_id") in rules.id_map:
230+
rule_path = rules.id_map[contents["rule_id"]].path
231+
local_contents = rules.id_map[contents["rule_id"]].contents
214232

215233
# handle both rule json formats loaded from kibana and toml
216234
data_view_id = contents.get("data_view_id") or contents.get("rule", {}).get("data_view_id")
@@ -226,7 +244,7 @@ def import_rules_into_repo( # noqa: PLR0912, PLR0913, PLR0915
226244

227245
contents.update(
228246
update_metadata_from_file(
229-
Path(rule_path), {"creation_date": local_creation_date, "updated_date": local_updated_date}
247+
{"creation_date": local_creation_date, "updated_date": local_updated_date}, rule_path, local_contents
230248
)
231249
)
232250

detection_rules/rule_loader.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,20 @@ def load_locks_from_tag(
139139
return commit_hash, version, deprecated
140140

141141

142-
def update_metadata_from_file(rule_path: Path, fields_to_update: dict[str, Any]) -> dict[str, Any]:
143-
"""Update metadata fields for a rule with local contents."""
142+
def update_metadata_from_file(
143+
fields_to_update: dict[str, Any], rule_path: Path | None = None, local_contents: TOMLRuleContents | None = None
144+
) -> dict[str, Any]:
145+
"""Update metadata fields for a rule with local contents or provided contents."""
144146

145147
contents: dict[str, Any] = {}
146-
if not rule_path.exists():
147-
return contents
148-
149-
rule_contents = RuleCollection().load_file(rule_path).contents
148+
if not rule_path and not local_contents:
149+
raise ValueError("Either 'rule_path' or 'local_contents' must be provided.")
150+
151+
rule_contents = None
152+
if rule_path:
153+
if not rule_path.exists():
154+
return contents
155+
rule_contents = local_contents or RuleCollection().load_file(rule_path).contents
150156

151157
if not isinstance(rule_contents, TOMLRuleContents):
152158
raise TypeError("TOML rule expected")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "detection_rules"
3-
version = "1.3.10"
3+
version = "1.3.11"
44
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."
55
readme = "README.md"
66
requires-python = ">=3.12"

0 commit comments

Comments
 (0)