156 lines
5.0 KiB
Python
156 lines
5.0 KiB
Python
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
# or more contributor license agreements. Licensed under the Elastic License
|
|
# 2.0; you may not use this file except in compliance with the Elastic License
|
|
# 2.0.
|
|
|
|
import operator
|
|
import re
|
|
|
|
import eql.ast
|
|
from eql import Walker, EqlCompileError, utils
|
|
from eql.functions import CidrMatch
|
|
from .errors import KqlRuntimeError, KqlCompileError
|
|
|
|
|
|
class FilterGenerator(Walker):
|
|
__cidr_cache = {}
|
|
|
|
def _walk_default(self, node, *args, **kwargs):
|
|
raise KqlCompileError("Unable to convert {}".format(node))
|
|
|
|
@classmethod
|
|
def equals(cls, term, value):
|
|
if utils.is_string(term) and utils.is_string(value):
|
|
if CidrMatch.ip_compiled.match(term) and CidrMatch.cidr_compiled.match(value):
|
|
# check for an ipv4 cidr
|
|
if value not in cls.__cidr_cache:
|
|
cls.__cidr_cache[value] = CidrMatch.get_callback(None, eql.ast.String(value))
|
|
return cls.__cidr_cache[value](term)
|
|
|
|
return term == value
|
|
|
|
@classmethod
|
|
def get_terms(cls, document, path):
|
|
if isinstance(document, (tuple, list)):
|
|
for d in document:
|
|
for term in cls.get_terms(d, path):
|
|
yield term
|
|
|
|
elif isinstance(document, dict):
|
|
document = document.get(path[0])
|
|
path = path[1:]
|
|
|
|
if len(path) > 0:
|
|
for term in cls.get_terms(document, path):
|
|
yield term
|
|
elif isinstance(document, (tuple, list)):
|
|
for term in document:
|
|
yield term
|
|
elif document is not None:
|
|
yield document
|
|
|
|
def _walk_value(self, tree, compare_function=None):
|
|
value = tree.value
|
|
compare_function = compare_function or self.equals
|
|
|
|
def check_value(term):
|
|
if term is None:
|
|
return False
|
|
|
|
if isinstance(term, list):
|
|
return any(check_value(t) for t in term)
|
|
|
|
if isinstance(term, (bool, float, int)) or utils.is_string(term):
|
|
v = value
|
|
|
|
if utils.is_string(v) and isinstance(term, (bool, int, float)):
|
|
if isinstance(v, bool):
|
|
v = v == "false"
|
|
if isinstance(term, int):
|
|
v = int(v)
|
|
elif isinstance(v, float):
|
|
v = float(v)
|
|
|
|
elif utils.is_string(term) and isinstance(v, (bool, int, float)):
|
|
v = utils.to_unicode(v)
|
|
|
|
return compare_function(term, v)
|
|
else:
|
|
raise KqlRuntimeError("Cannot compare value {}".format(term))
|
|
|
|
return check_value
|
|
|
|
def _walk_exists(self, _):
|
|
return lambda terms: any(t is not None for t in terms)
|
|
|
|
def _walk_wildcard(self, tree):
|
|
pattern = tree.value
|
|
regex = re.compile(".*?".join(map(re.escape, pattern.split("*"))), re.UNICODE | re.DOTALL)
|
|
return lambda terms: any(t is not None and regex.fullmatch(t) for t in terms)
|
|
|
|
def _walk_field(self, field):
|
|
path = field.name.split(".")
|
|
get_terms = self.get_terms
|
|
|
|
def callback(document):
|
|
terms = get_terms(document, path)
|
|
terms = list(terms)
|
|
return terms
|
|
|
|
return callback
|
|
|
|
def _walk_field_range(self, tree):
|
|
field = self.walk(tree.field)
|
|
operators = {"<": operator.lt, "<=": operator.le, ">=": operator.ge, ">": operator.gt}
|
|
|
|
check_range = self.walk(tree.value, operators[tree.operator])
|
|
return lambda doc: check_range(field(doc))
|
|
|
|
def _walk_nested_query(self, tree):
|
|
field = self.walk(tree.field)
|
|
expr = self.walk(tree.expr)
|
|
|
|
def check_nested(doc):
|
|
doc = field(doc)
|
|
|
|
if isinstance(doc, dict):
|
|
return expr(doc)
|
|
elif isinstance(doc, (list, tuple)):
|
|
return any(expr(d) for d in doc)
|
|
|
|
return check_nested
|
|
|
|
def _walk_list(self, trees, reduce_function, *args, **kwargs):
|
|
walked = [self.walk(item, *args, **kwargs) for item in trees.items]
|
|
return lambda x: reduce_function(item(x) for item in walked)
|
|
|
|
def _walk_not_expr(self, tree):
|
|
expr = self.walk(tree.expr)
|
|
return lambda doc: not expr(doc)
|
|
|
|
def _walk_and_expr(self, tree):
|
|
return self._walk_list(tree, all)
|
|
|
|
def _walk_or_expr(self, tree):
|
|
return self._walk_list(tree, any)
|
|
|
|
def _walk_and_values(self, tree):
|
|
return self._walk_list(tree, all)
|
|
|
|
def _walk_or_values(self, tree):
|
|
return self._walk_list(tree, any)
|
|
|
|
def _walk_not_value(self, tree):
|
|
expr = self.walk(tree.value)
|
|
return lambda value: not expr(value)
|
|
|
|
def _walk_field_comparison(self, tree):
|
|
field = self.walk(tree.field)
|
|
value = self.walk(tree.value)
|
|
|
|
return lambda doc: value(field(doc))
|
|
|
|
@classmethod
|
|
def filter(cls, expression):
|
|
return cls().walk(expression)
|