def setUpClass(cls): cls.compatible_rule = Rule( "test.toml", { "author": ["Elastic"], "description": "test description", "index": ["filebeat-*"], "language": "kuery", "license": "Elastic License", "name": "test rule", "query": "process.name:test.query", "risk_score": 21, "rule_id": str(uuid.uuid4()), "severity": "low", "type": "query" }) cls.versioned_rule = cls.compatible_rule.copy() cls.versioned_rule.contents["version"] = 10 cls.threshold_rule = Rule( "test.toml", { "author": ["Elastic"], "description": "test description", "language": "kuery", "license": "Elastic License", "name": "test rule", "query": "process.name:test.query", "risk_score": 21, "rule_id": str(uuid.uuid4()), "severity": "low", "threshold": { "field": "destination.bytes", "value": 75, }, "type": "threshold", })
def test_eql_validation(self): base_fields = { "author": ["Elastic"], "description": "test description", "index": ["filebeat-*"], "language": "eql", "license": "Elastic License", "name": "test rule", "risk_score": 21, "rule_id": str(uuid.uuid4()), "severity": "low", "type": "eql" } Rule( "test.toml", dict(base_fields, query=""" process where process.name == "cmd.exe" """)) with self.assertRaises(eql.EqlSyntaxError): Rule( "test.toml", dict(base_fields, query=""" process where process.name == this!is$not#v@lid """)) with self.assertRaises(eql.EqlSemanticError): Rule( "test.toml", dict(base_fields, query=""" process where process.invalid_field == "hello world" """)) with self.assertRaises(eql.EqlTypeMismatchError): Rule( "test.toml", dict(base_fields, query=""" process where process.pid == "some string field" """))
def test_all_rule_queries_optimized(self): """Ensure that every rule query is in optimized form.""" for file_name, contents in rule_loader.load_rule_files().items(): rule = Rule(file_name, contents) if rule.query and rule.contents['language'] == 'kuery': tree = kql.parse(rule.query, optimize=False) optimized = tree.optimize(recursive=True) err_message = '\nQuery not optimized for rule: {} - {}\nExpected: {}\nActual: {}'.format( rule.name, rule.id, optimized, rule.query) self.assertEqual(tree, optimized, err_message)
def test_no_unrequired_defaults(self): """Test that values that are not required in the schema are not set with default values.""" rules_with_hits = {} for file_name, contents in rule_loader.load_rule_files().items(): rule = Rule(file_name, contents) default_matches = rule_loader.find_unneeded_defaults(rule) if default_matches: rules_with_hits['{} - {}'.format(rule.name, rule.id)] = default_matches error_msg = 'The following rules have unnecessary default values set: \n{}'.format( json.dumps(rules_with_hits, indent=2)) self.assertDictEqual(rules_with_hits, {}, error_msg)
def test_all_rule_files(self): """Ensure that every rule file can be loaded and validate against schema.""" rules = [] for file_name, contents in rule_loader.load_rule_files().items(): try: rule = Rule(file_name, contents) rules.append(rule) except (pytoml.TomlError, toml.TomlDecodeError) as e: print("TOML error when parsing rule file \"{}\"".format(os.path.basename(file_name)), file=sys.stderr) raise e except jsonschema.ValidationError as e: print("Schema error when parsing rule file \"{}\"".format(os.path.basename(file_name)), file=sys.stderr) raise e
def test_all_rules_as_rule_schema(self): """Ensure that every rule file validates against the rule schema.""" for file_name, contents in rule_loader.load_rule_files().items(): rule = Rule(file_name, contents) rule.validate(as_rule=True)
def setUpClass(cls): # expected contents for a downgraded rule cls.v78_kql = { "description": "test description", "index": ["filebeat-*"], "language": "kuery", "name": "test rule", "query": "process.name:test.query", "risk_score": 21, "rule_id": str(uuid.uuid4()), "severity": "low", "type": "query", "threat": [{ "framework": "MITRE ATT&CK", "tactic": { "id": "TA0001", "name": "Execution", "reference": "https://attack.mitre.org/tactics/TA0001/" }, "technique": [{ "id": "T1059", "name": "Command and Scripting Interpreter", "reference": "https://attack.mitre.org/techniques/T1059/", }], }] } cls.v79_kql = dict(cls.v78_kql, author=["Elastic"], license="Elastic License") cls.v711_kql = copy.deepcopy(cls.v79_kql) cls.v711_kql["threat"][0]["technique"][0]["subtechnique"] = [{ "id": "T1059.001", "name": "PowerShell", "reference": "https://attack.mitre.org/techniques/T1059/001/" }] cls.v711_kql["threat"].append({ "framework": "MITRE ATT&CK", "tactic": { "id": "TA0008", "name": "Lateral Movement", "reference": "https://attack.mitre.org/tactics/TA0008/" }, }) cls.versioned_rule = Rule("test.toml", copy.deepcopy(cls.v79_kql)) cls.versioned_rule.contents["version"] = 10 cls.threshold_rule = Rule( "test.toml", { "author": ["Elastic"], "description": "test description", "language": "kuery", "license": "Elastic License", "name": "test rule", "query": "process.name:test.query", "risk_score": 21, "rule_id": str(uuid.uuid4()), "severity": "low", "threshold": { "field": "destination.bytes", "value": 75, }, "type": "threshold", })