diff --git a/kql/ast.py b/kql/ast.py index e6c7de11b..7f8c7f88f 100644 --- a/kql/ast.py +++ b/kql/ast.py @@ -61,6 +61,8 @@ class Value(KqlNode): def from_python(cls, value): if value is None: return Null() + elif is_string(value) and ('*' in value or '?' in value): + return Wildcard(value) elif isinstance(value, bool): return Boolean(value) elif is_number(value): @@ -104,7 +106,7 @@ class String(Value): class Wildcard(Value): escapes = {"\t": "\\t", "\r": "\\r"} - slash_escaped = r'''^\\():<>"*{} ''' + slash_escaped = r'''^\\():<>"{} ''' def _render(self): escaped = [] diff --git a/kql/eql2kql.py b/kql/eql2kql.py index 7e01764fa..4136257fb 100755 --- a/kql/eql2kql.py +++ b/kql/eql2kql.py @@ -8,7 +8,7 @@ from eql import DepthFirstWalker from .ast import ( Value, String, OrValues, Field, Expression, FieldRange, FieldComparison, - NotExpr, AndExpr, OrExpr, Exists + NotExpr, AndExpr, OrExpr, Exists, Wildcard ) @@ -66,8 +66,16 @@ class Eql2Kql(DepthFirstWalker): def _walk_function_call(self, tree): # type: (eql.ast.FunctionCall) -> KqlNode if tree.name in ("wildcard", "cidrMatch"): if isinstance(tree.arguments[0], Field): - return FieldComparison(tree.arguments[0], OrValues(tree.arguments[1:])) - + if tree.name == "wildcard": + args = [] + for arg in tree.arguments[1:]: + if '*' in arg.value or '?' in arg.value: + args.append(Wildcard(arg.value)) + else: + args.append(arg) + return FieldComparison(tree.arguments[0], OrValues(args)) + else: + return FieldComparison(tree.arguments[0], OrValues(tree.arguments[1:])) raise eql.errors.EqlCompileError("Unable to convert `{}`".format(tree)) def _walk_literal(self, tree): diff --git a/tests/kuery/test_eql2kql.py b/tests/kuery/test_eql2kql.py index 6757f908a..de0e4404c 100644 --- a/tests/kuery/test_eql2kql.py +++ b/tests/kuery/test_eql2kql.py @@ -3,6 +3,7 @@ # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. +import eql import unittest import kql @@ -51,3 +52,12 @@ class TestEql2Kql(unittest.TestCase): self.validate("dest:192.168.255.255", "dest == '192.168.255.255'") self.validate("dest:192.168.0.0/16", "cidrMatch(dest, '192.168.0.0/16')") self.validate("dest:192.168.0.0/16", "cidrMatch(dest, '192.168.0.0/16')") + + def test_wildcard_field(self): + with eql.parser.elasticsearch_validate_optional_fields: + self.validate('field:value-*', 'field : "value-*"') + self.validate('field:value-?', 'field : "value-?"') + + with eql.parser.elasticsearch_validate_optional_fields, self.assertRaises(AssertionError): + self.validate('field:"value-*"', 'field == "value-*"') + self.validate('field:"value-?"', 'field == "value-?"') diff --git a/tests/kuery/test_evaluator.py b/tests/kuery/test_evaluator.py index 94ae0c0be..97033e97b 100644 --- a/tests/kuery/test_evaluator.py +++ b/tests/kuery/test_evaluator.py @@ -99,7 +99,8 @@ class EvaluatorTests(unittest.TestCase): self.assertFalse(self.evaluate('ip:10.0.0.0/8')) def test_quoted_wildcard(self): - self.assertFalse(self.evaluate('string:"*"')) + self.assertFalse(self.evaluate("string:'*'")) + self.assertFalse(self.evaluate("string:'?'")) def test_wildcard(self): self.assertTrue(self.evaluate('string:hello*'))