|
8 | 8 | import copy
|
9 | 9 | import unittest
|
10 | 10 | import uuid
|
| 11 | +from pathlib import Path |
11 | 12 |
|
12 | 13 | import eql
|
| 14 | +import pytest |
| 15 | +import pytoml |
13 | 16 | from marshmallow import ValidationError
|
14 | 17 | from semver import Version
|
15 | 18 |
|
16 | 19 | from detection_rules import utils
|
17 | 20 | from detection_rules.config import load_current_package_version
|
18 | 21 | from detection_rules.rule import TOMLRuleContents
|
| 22 | +from detection_rules.rule_loader import RuleCollection |
19 | 23 | from detection_rules.schemas import RULES_CONFIG, downgrade
|
20 | 24 | from detection_rules.version_lock import VersionLockFile
|
21 | 25 |
|
@@ -302,3 +306,53 @@ def test_stack_schema_map(self):
|
302 | 306 | stack_map = utils.load_etc_dump(["stack-schema-map.yaml"])
|
303 | 307 | err_msg = f"There is no entry defined for the current package ({package_version}) in the stack-schema-map"
|
304 | 308 | self.assertIn(package_version, [Version.parse(v) for v in stack_map], err_msg)
|
| 309 | + |
| 310 | + |
| 311 | +class TestESQLValidation(unittest.TestCase): |
| 312 | + """Test ESQL rule validation""" |
| 313 | + |
| 314 | + def test_esql_data_validation(self): |
| 315 | + """Test ESQL rule data validation""" |
| 316 | + |
| 317 | + # A random ESQL rule to deliver a test query |
| 318 | + rule_path = Path("rules/windows/defense_evasion_posh_obfuscation_index_reversal.toml") |
| 319 | + rule_body = rule_path.read_text() |
| 320 | + rule_dict = pytoml.loads(rule_body) |
| 321 | + |
| 322 | + # Most used order of the metadata fields |
| 323 | + query = """ |
| 324 | + FROM logs-windows.powershell_operational* METADATA _id, _version, _index |
| 325 | + | WHERE event.code == "4104" |
| 326 | + | KEEP event.count |
| 327 | + """ |
| 328 | + rule_dict["rule"]["query"] = query |
| 329 | + _ = RuleCollection().load_dict(rule_dict, path=rule_path) |
| 330 | + |
| 331 | + # The order of the metadata fields from the example in the docs - |
| 332 | + # https://www.elastic.co/guide/en/security/8.17/rules-ui-create.html#esql-non-agg-query |
| 333 | + query = """ |
| 334 | + FROM logs-windows.powershell_operational* METADATA _id, _index, _version |
| 335 | + | WHERE event.code == "4104" |
| 336 | + | KEEP event.count |
| 337 | + """ |
| 338 | + rule_dict["rule"]["query"] = query |
| 339 | + _ = RuleCollection().load_dict(rule_dict, path=rule_path) |
| 340 | + |
| 341 | + # Different metadata fields |
| 342 | + with pytest.raises(ValidationError): |
| 343 | + query = """ |
| 344 | + FROM logs-windows.powershell_operational* METADATA _foo, _index |
| 345 | + | WHERE event.code == "4104" |
| 346 | + | KEEP event.count |
| 347 | + """ |
| 348 | + rule_dict["rule"]["query"] = query |
| 349 | + _ = RuleCollection().load_dict(rule_dict, path=rule_path) |
| 350 | + |
| 351 | + # Missing `keep` |
| 352 | + with pytest.raises(ValidationError): |
| 353 | + query = """ |
| 354 | + FROM logs-windows.powershell_operational* METADATA _id, _index, _version |
| 355 | + | WHERE event.code == "4104" |
| 356 | + """ |
| 357 | + rule_dict["rule"]["query"] = query |
| 358 | + _ = RuleCollection().load_dict(rule_dict, path=rule_path) |
0 commit comments