EQL: Fix cidrMatch function fails to match when used in scripts (#56246) (#56735)

EQL: Fix cidrMatch function fails to match when used in scripts (#56246)

Addresses https://github.com/elastic/elasticsearch/issues/55709
This commit is contained in:
Aleksandr Maus 2020-05-13 22:41:24 -04:00 committed by GitHub
parent 83e9ff42da
commit 87a10806ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 748 additions and 51 deletions

View File

@ -14,6 +14,43 @@ query = '''
file where between(file_path, "dev", ".json", true) == "\\TestLogs\\something" file where between(file_path, "dev", ".json", true) == "\\TestLogs\\something"
''' '''
[[queries]]
expected_event_ids = [75304, 75305]
query = '''
network where cidrMatch(source_address, "10.6.48.157/8") == true
'''
[[queries]]
expected_event_ids = [75304, 75305]
query = '''
network where string(cidrMatch(source_address, "10.6.48.157/8")) == "true"
'''
[[queries]]
expected_event_ids = [75304, 75305]
query = '''
network where true == cidrMatch(source_address, "10.6.48.157/8")
'''
[[queries]]
expected_event_ids = []
query = '''
network where cidrMatch(source_address, "192.168.0.0/16") == true
'''
[[queries]]
expected_event_ids = [75304, 75305]
query = '''
network where cidrMatch(source_address, "192.168.0.0/16", "10.6.48.157/8") == true
'''
[[queries]]
expected_event_ids = [75304, 75305]
query = '''
network where cidrMatch(source_address, "0.0.0.0/0") == true
'''
[[queries]] [[queries]]
description = "test string concatenation. update test to avoid case-sensitivity issues" description = "test string concatenation. update test to avoid case-sensitivity issues"
query = ''' query = '''
@ -41,7 +78,6 @@ query = 'process where serial_event_id < 5 and concat(process_name, null, null)
expected_event_ids = [1, 2, 3, 4] expected_event_ids = [1, 2, 3, 4]
[[queries]] [[queries]]
query = 'process where serial_event_id < 5 and concat(parent_process_name, null) == null' query = 'process where serial_event_id < 5 and concat(parent_process_name, null) == null'
expected_event_ids = [1, 2, 3, 4] expected_event_ids = [1, 2, 3, 4]

View File

@ -6,58 +6,55 @@
package org.elasticsearch.xpack.eql.expression.function.scalar.string; package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.Expressions; import org.elasticsearch.xpack.ql.expression.Expressions;
import org.elasticsearch.xpack.ql.expression.Expressions.ParamOrdinal; import org.elasticsearch.xpack.ql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.ql.expression.function.scalar.BaseSurrogateFunction; import org.elasticsearch.xpack.ql.expression.FieldAttribute;
import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction; import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or; import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals; import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.ql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.ql.tree.NodeInfo; import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source; import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataType; import org.elasticsearch.xpack.ql.type.DataType;
import org.elasticsearch.xpack.ql.type.DataTypes; import org.elasticsearch.xpack.ql.type.DataTypes;
import org.elasticsearch.xpack.ql.util.CollectionUtils; import org.elasticsearch.xpack.ql.util.CollectionUtils;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.eql.expression.function.scalar.string.CIDRMatchFunctionProcessor.doProcess;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isFoldable; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isFoldable;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isIPAndExact; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isIPAndExact;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact;
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder;
/** /**
* EQL specific cidrMatch function * EQL specific cidrMatch function
* Returns true if the source address matches any of the provided CIDR blocks. * Returns true if the source address matches any of the provided CIDR blocks.
* Refer to: https://eql.readthedocs.io/en/latest/query-guide/functions.html#cidrMatch * Refer to: https://eql.readthedocs.io/en/latest/query-guide/functions.html#cidrMatch
*/ */
public class CIDRMatch extends BaseSurrogateFunction { public class CIDRMatch extends ScalarFunction {
private final Expression field; private final Expression field;
private final List<Expression> addresses; private final List<Expression> addresses;
public CIDRMatch(Source source, Expression field, List<Expression> addresses) { public CIDRMatch(Source source, Expression field, List<Expression> addresses) {
super(source, CollectionUtils.combine(singletonList(field), addresses)); super(source, CollectionUtils.combine(singletonList(field), addresses == null ? emptyList() : addresses));
this.field = field; this.field = field;
this.addresses = addresses; this.addresses = addresses == null ? emptyList() : addresses;
} }
@Override public Expression field() {
protected NodeInfo<? extends Expression> info() { return field;
return NodeInfo.create(this, CIDRMatch::new, field, addresses);
} }
@Override public List<Expression> addresses() {
public Expression replaceChildren(List<Expression> newChildren) { return addresses;
if (newChildren.size() < 2) {
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
}
return new CIDRMatch(source(), newChildren.get(0), newChildren.subList(1, newChildren.size()));
}
@Override
public DataType dataType() {
return DataTypes.BOOLEAN;
} }
@Override @Override
@ -71,15 +68,6 @@ public class CIDRMatch extends BaseSurrogateFunction {
return resolution; return resolution;
} }
for (Expression addr : addresses) {
// Currently we have limited enum for ordinal numbers
// So just using default here for error messaging
resolution = isStringAndExact(addr, sourceText(), ParamOrdinal.DEFAULT);
if (resolution.unresolved()) {
return resolution;
}
}
int index = 1; int index = 1;
for (Expression addr : addresses) { for (Expression addr : addresses) {
@ -101,14 +89,60 @@ public class CIDRMatch extends BaseSurrogateFunction {
} }
@Override @Override
public ScalarFunction makeSubstitute() { protected Pipe makePipe() {
ScalarFunction func = null; ArrayList<Pipe> arr = new ArrayList<>(addresses.size());
for (Expression address : addresses) { for (Expression address : addresses) {
final Equals eq = new Equals(source(), field, address); arr.add(Expressions.pipe(address));
func = (func == null) ? eq : new Or(source(), func, eq); }
return new CIDRMatchFunctionPipe(source(), this, Expressions.pipe(field), arr);
} }
return func; @Override
public boolean foldable() {
return field.foldable() && Expressions.foldable(addresses);
}
@Override
public Object fold() {
return doProcess(field.fold(), Expressions.fold(addresses));
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, CIDRMatch::new, field, addresses);
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate leftScript = asScript(field);
List<Object> values = new ArrayList<>(new LinkedHashSet<>(Expressions.fold(addresses)));
return new ScriptTemplate(
formatTemplate(LoggerMessageFormat.format("{eql}.","cidrMatch({}, {})", leftScript.template())),
paramsBuilder()
.script(leftScript.params())
.variable(values)
.build(),
dataType());
}
@Override
public ScriptTemplate scriptWithField(FieldAttribute field) {
return new ScriptTemplate(processScript(Scripts.DOC_VALUE),
paramsBuilder().variable(field.exactAttribute().name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataTypes.BOOLEAN;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() < 2) {
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
}
return new CIDRMatch(source(), newChildren.get(0), newChildren.subList(1, newChildren.size()));
} }
} }

View File

@ -0,0 +1,136 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.xpack.ql.execution.search.QlSourceBuilder;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class CIDRMatchFunctionPipe extends Pipe {
private final Pipe source;
private final List<Pipe> addresses;
public CIDRMatchFunctionPipe(Source source, Expression expression, Pipe src, List<Pipe> addresses) {
super(source, expression, CollectionUtils.combine(Collections.singletonList(src), addresses));
this.source = src;
this.addresses = addresses;
}
@Override
public final Pipe replaceChildren(List<Pipe> newChildren) {
if (newChildren.size() < 2) {
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.subList(1, newChildren.size()));
}
@Override
public final Pipe resolveAttributes(AttributeResolver resolver) {
Pipe newSource = source.resolveAttributes(resolver);
boolean same = (newSource == source);
ArrayList<Pipe> newAddresses = new ArrayList<Pipe>(addresses.size());
for (Pipe address : addresses) {
Pipe newAddress = address.resolveAttributes(resolver);
newAddresses.add(newAddress);
same = same && (address == newAddress);
}
if (same) {
return this;
}
return replaceChildren(newSource, newAddresses);
}
@Override
public boolean supportedByAggsOnlyQuery() {
if (source.supportedByAggsOnlyQuery() == false) {
return false;
}
for (Pipe address : addresses) {
if (address.supportedByAggsOnlyQuery() == false) {
return false;
}
}
return true;
}
@Override
public boolean resolved() {
if (source.resolved() == false) {
return false;
}
for (Pipe address : addresses) {
if (address.resolved() == false) {
return false;
}
}
return true;
}
protected Pipe replaceChildren(Pipe newSource, List<Pipe> newAddresses) {
return new CIDRMatchFunctionPipe(source(), expression(), newSource, newAddresses);
}
@Override
public final void collectFields(QlSourceBuilder sourceBuilder) {
source.collectFields(sourceBuilder);
for (Pipe address : addresses) {
address.collectFields(sourceBuilder);
}
}
@Override
protected NodeInfo<CIDRMatchFunctionPipe> info() {
return NodeInfo.create(this, CIDRMatchFunctionPipe::new, expression(), source, addresses);
}
@Override
public CIDRMatchFunctionProcessor asProcessor() {
ArrayList<Processor> processors = new ArrayList<>(addresses.size());
for (Pipe address: addresses) {
processors.add(address.asProcessor());
}
return new CIDRMatchFunctionProcessor(source.asProcessor(), processors);
}
public Pipe src() {
return source;
}
public List<Pipe> addresses() {
return addresses;
}
@Override
public int hashCode() {
return Objects.hash(source(), addresses());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CIDRMatchFunctionPipe other = (CIDRMatchFunctionPipe) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(addresses(), other.addresses());
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.ql.util.Check;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CIDRMatchFunctionProcessor implements Processor {
public static final String NAME = "cdrm";
private final Processor source;
private final List<Processor> addresses;
public CIDRMatchFunctionProcessor(Processor source, List<Processor> addresses) {
this.source = source;
this.addresses = addresses;
}
public CIDRMatchFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
addresses = in.readNamedWriteableList(Processor.class);
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(source);
out.writeNamedWriteableList(addresses);
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public Object process(Object input) {
Object src = source.process(input);
ArrayList<Object> arr = new ArrayList<>(addresses.size());
for (Processor address : addresses) {
arr.add(address.process(input));
}
return doProcess(src, arr);
}
public static Object doProcess(Object source, List<Object> addresses) {
if (source == null) {
return null;
}
Check.isString(source);
String[] arr = new String[addresses.size()];
int i = 0;
for (Object address: addresses) {
Check.isString(address);
arr[i++] = (String)address;
}
return CIDRUtils.isInRange((String)source, arr);
}
protected Processor source() {
return source;
}
public List<Processor> addresses() {
return addresses;
}
@Override
public int hashCode() {
return Objects.hash(source(), addresses());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CIDRMatchFunctionProcessor other = (CIDRMatchFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(addresses(), other.addresses());
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.apache.lucene.util.FutureArrays;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
import java.net.InetAddress;
public class CIDRUtils {
// Borrowed from Lucene, rfc4291 prefix
static final byte[] IPV4_PREFIX = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1};
private CIDRUtils() {
}
public static boolean isInRange(String address, String... cidrAddresses) {
try {
// Check if address is parsable first
byte[] addr = InetAddresses.forString(address).getAddress();
if (cidrAddresses == null || cidrAddresses.length == 0) {
return false;
}
for (String cidrAddress : cidrAddresses) {
if (cidrAddress == null) continue;
byte[] lower, upper;
if (cidrAddress.contains("/")) {
final Tuple<byte[], byte[]> range = getLowerUpper(InetAddresses.parseCidr(cidrAddress));
lower = range.v1();
upper = range.v2();
} else {
lower = InetAddresses.forString(cidrAddress).getAddress();
upper = lower;
}
if (isBetween(addr, lower, upper)) return true;
}
} catch (IllegalArgumentException e) {
throw new EqlIllegalArgumentException(e.getMessage());
}
return false;
}
private static Tuple<byte[], byte[]> getLowerUpper(Tuple<InetAddress, Integer> cidr) {
final InetAddress value = cidr.v1();
final Integer prefixLength = cidr.v2();
if (prefixLength < 0 || prefixLength > 8 * value.getAddress().length) {
throw new IllegalArgumentException("illegal prefixLength '" + prefixLength +
"'. Must be 0-32 for IPv4 ranges, 0-128 for IPv6 ranges");
}
byte[] lower = value.getAddress();
byte[] upper = value.getAddress();
// Borrowed from Lucene
for (int i = prefixLength; i < 8 * lower.length; i++) {
int m = 1 << (7 - (i & 7));
lower[i >> 3] &= ~m;
upper[i >> 3] |= m;
}
return new Tuple<>(lower, upper);
}
private static boolean isBetween(byte[] addr, byte[] lower, byte[] upper) {
// Encode the addresses bytes if lengths do not match
if (addr.length != lower.length) {
addr = encode(addr);
lower = encode(lower);
upper = encode(upper);
}
return FutureArrays.compareUnsigned(lower, 0, lower.length, addr, 0, addr.length) <= 0 &&
FutureArrays.compareUnsigned(upper, 0, upper.length, addr, 0, addr.length) >= 0;
}
// Borrowed from Lucene to make this consistent IP fields matching for the mix of IPv4 and IPv6 values
// Modified signature to avoid extra conversions
private static byte[] encode(byte[] address) {
if (address.length == 4) {
byte[] mapped = new byte[16];
System.arraycopy(IPV4_PREFIX, 0, mapped, 0, IPV4_PREFIX.length);
System.arraycopy(address, 0, mapped, IPV4_PREFIX.length, address.length);
address = mapped;
} else if (address.length != 16) {
throw new UnsupportedOperationException("Only IPv4 and IPv6 addresses are supported");
}
return address;
}
}

View File

@ -7,6 +7,7 @@
package org.elasticsearch.xpack.eql.expression.function.scalar.whitelist; package org.elasticsearch.xpack.eql.expression.function.scalar.whitelist;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.BetweenFunctionProcessor; import org.elasticsearch.xpack.eql.expression.function.scalar.string.BetweenFunctionProcessor;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.CIDRMatchFunctionProcessor;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.ConcatFunctionProcessor; import org.elasticsearch.xpack.eql.expression.function.scalar.string.ConcatFunctionProcessor;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.EndsWithFunctionProcessor; import org.elasticsearch.xpack.eql.expression.function.scalar.string.EndsWithFunctionProcessor;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.IndexOfFunctionProcessor; import org.elasticsearch.xpack.eql.expression.function.scalar.string.IndexOfFunctionProcessor;
@ -32,6 +33,10 @@ public class InternalEqlScriptUtils extends InternalQlScriptUtils {
return (String) BetweenFunctionProcessor.doProcess(s, left, right, greedy, caseSensitive); return (String) BetweenFunctionProcessor.doProcess(s, left, right, greedy, caseSensitive);
} }
public static Boolean cidrMatch(String s, List<Object> addresses) {
return (Boolean) CIDRMatchFunctionProcessor.doProcess(s, addresses);
}
public static String concat(List<Object> values) { public static String concat(List<Object> values) {
return (String) ConcatFunctionProcessor.doProcess(values); return (String) ConcatFunctionProcessor.doProcess(values);
} }

View File

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.planner;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.planner.QlTranslatorHandler;
import org.elasticsearch.xpack.ql.querydsl.query.Query;
public class EqlTranslatorHandler extends QlTranslatorHandler {
@Override
public Query asQuery(Expression e) {
return QueryTranslator.toQuery(e, this);
}
}

View File

@ -69,7 +69,7 @@ class QueryFolder extends RuleExecutor<PhysicalPlan> {
EsQueryExec exec = (EsQueryExec) plan.child(); EsQueryExec exec = (EsQueryExec) plan.child();
QueryContainer qContainer = exec.queryContainer(); QueryContainer qContainer = exec.queryContainer();
Query query = ExpressionTranslators.toQuery(plan.condition()); Query query = QueryTranslator.toQuery(plan.condition());
if (qContainer.query() != null || query != null) { if (qContainer.query() != null || query != null) {
query = ExpressionTranslators.and(plan.source(), qContainer.query(), query); query = ExpressionTranslators.and(plan.source(), qContainer.query(), query);

View File

@ -0,0 +1,116 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.planner;
import org.elasticsearch.xpack.eql.expression.function.scalar.string.CIDRMatch;
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.Expressions;
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.ql.expression.predicate.logical.And;
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or;
import org.elasticsearch.xpack.ql.planner.ExpressionTranslator;
import org.elasticsearch.xpack.ql.planner.ExpressionTranslators;
import org.elasticsearch.xpack.ql.planner.TranslatorHandler;
import org.elasticsearch.xpack.ql.querydsl.query.Query;
import org.elasticsearch.xpack.ql.querydsl.query.ScriptQuery;
import org.elasticsearch.xpack.ql.querydsl.query.TermsQuery;
import org.elasticsearch.xpack.ql.util.CollectionUtils;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import static org.elasticsearch.xpack.ql.planner.ExpressionTranslators.and;
import static org.elasticsearch.xpack.ql.planner.ExpressionTranslators.or;
final class QueryTranslator {
public static final List<ExpressionTranslator<?>> QUERY_TRANSLATORS = Arrays.asList(
new ExpressionTranslators.BinaryComparisons(),
new ExpressionTranslators.Ranges(),
new BinaryLogic(),
new ExpressionTranslators.Nots(),
new ExpressionTranslators.Likes(),
new ExpressionTranslators.InComparisons(),
new ExpressionTranslators.StringQueries(),
new ExpressionTranslators.Matches(),
new ExpressionTranslators.MultiMatches(),
new Scalars()
);
public static Query toQuery(Expression e) {
return toQuery(e, new EqlTranslatorHandler());
}
public static Query toQuery(Expression e, TranslatorHandler handler) {
Query translation = null;
for (ExpressionTranslator<?> translator : QUERY_TRANSLATORS) {
translation = translator.translate(e, handler);
if (translation != null) {
return translation;
}
}
throw new QlIllegalArgumentException("Don't know how to translate {} {}", e.nodeName(), e);
}
public static class BinaryLogic extends ExpressionTranslator<org.elasticsearch.xpack.ql.expression.predicate.logical.BinaryLogic> {
@Override
protected Query asQuery(org.elasticsearch.xpack.ql.expression.predicate.logical.BinaryLogic e, TranslatorHandler handler) {
if (e instanceof And) {
return and(e.source(), toQuery(e.left(), handler), toQuery(e.right(), handler));
}
if (e instanceof Or) {
return or(e.source(), toQuery(e.left(), handler), toQuery(e.right(), handler));
}
return null;
}
}
public static Object valueOf(Expression e) {
if (e.foldable()) {
return e.fold();
}
throw new QlIllegalArgumentException("Cannot determine value for {}", e);
}
public static class Scalars extends ExpressionTranslator<ScalarFunction> {
@Override
protected Query asQuery(ScalarFunction f, TranslatorHandler handler) {
return doTranslate(f, handler);
}
public static Query doTranslate(ScalarFunction f, TranslatorHandler handler) {
Query q = ExpressionTranslators.Scalars.doKnownTranslate(f, handler);
if (q != null) {
return q;
}
if (f instanceof CIDRMatch) {
CIDRMatch cm = (CIDRMatch) f;
if (cm.field() instanceof FieldAttribute && Expressions.foldable(cm.addresses())) {
String targetFieldName = handler.nameOf(((FieldAttribute) cm.field()).exactAttribute());
Set<Object> set = new LinkedHashSet<>(CollectionUtils.mapSize(cm.addresses().size()));
for (Expression e : cm.addresses()) {
set.add(valueOf(e));
}
return new TermsQuery(f.source(), targetFieldName, set);
}
}
return handler.wrapFunctionQuery(f, f, new ScriptQuery(f.source(), f.asScript()));
}
}
}

View File

@ -66,6 +66,7 @@ class org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalE
# ASCII Functions # ASCII Functions
# #
String between(String, String, String, Boolean, Boolean) String between(String, String, String, Boolean, Boolean)
Boolean cidrMatch(String, java.util.List)
String concat(java.util.List) String concat(java.util.List)
Boolean endsWith(String, String) Boolean endsWith(String, String)
Integer indexOf(String, String, Number) Integer indexOf(String, String, Number)

View File

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
import org.elasticsearch.xpack.ql.expression.Expression;
import java.util.ArrayList;
import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTestUtils.l;
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
public class CIDRMatchProcessorTests extends ESTestCase {
public void testCIDRMatchFunctionValidInput() {
// Expects null if source was null
assertNull(new CIDRMatch(EMPTY, l(null), null).makePipe().asProcessor().process(null));
ArrayList<Expression> addresses = new ArrayList<>();
assertNull(new CIDRMatch(EMPTY, l(null), addresses).makePipe().asProcessor().process(null));
assertFalse((Boolean) new CIDRMatch(EMPTY, l("10.6.48.157"), addresses).makePipe().asProcessor().process(null));
addresses.add(l("10.6.48.157/8"));
assertTrue((Boolean) new CIDRMatch(EMPTY, l("10.6.48.157"), addresses).makePipe().asProcessor().process(null));
}
public void testCIDRMatchFunctionInvalidInput() {
ArrayList<Expression> addresses = new ArrayList<>();
// Invalid source address
EqlIllegalArgumentException e = expectThrows(EqlIllegalArgumentException.class,
() -> new CIDRMatch(EMPTY, l("10.6.48"), addresses).makePipe().asProcessor().process(null));
assertEquals("'10.6.48' is not an IP string literal.", e.getMessage());
// Invalid match ip address
addresses.add(l("10.6.48"));
e = expectThrows(EqlIllegalArgumentException.class,
() -> new CIDRMatch(EMPTY, l("10.6.48.157"), addresses).makePipe().asProcessor().process(null));
assertEquals("'10.6.48' is not an IP string literal.", e.getMessage());
addresses.clear();
// Invalid CIDR
addresses.add(l("10.6.12/12"));
e = expectThrows(EqlIllegalArgumentException.class,
() -> new CIDRMatch(EMPTY, l("10.6.48.157"), addresses).makePipe().asProcessor().process(null));
assertEquals("'10.6.12' is not an IP string literal.", e.getMessage());
addresses.clear();
// Invalid source type
QlIllegalArgumentException eqe = expectThrows(QlIllegalArgumentException.class,
() -> new CIDRMatch(EMPTY, l(12345), addresses).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [12345]", eqe.getMessage());
// Invalid cidr type
addresses.add(l(5678));
eqe = expectThrows(QlIllegalArgumentException.class,
() -> new CIDRMatch(EMPTY, l("10.6.48.157"), addresses).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [5678]", eqe.getMessage());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.test.ESTestCase;
public class CIDRUtilsTests extends ESTestCase {
public void testCIDRUtils() {
// Missing or empty param
assertFalse(CIDRUtils.isInRange("10.6.48.157"));
assertFalse(CIDRUtils.isInRange("10.6.48.157", (String)null));
// EQL tests matches
assertTrue(CIDRUtils.isInRange("10.6.48.157", "10.6.48.157/8"));
assertFalse(CIDRUtils.isInRange("10.6.48.157", "192.168.0.0/16"));
assertTrue(CIDRUtils.isInRange("10.6.48.157", "192.168.0.0/16", "10.6.48.157/8"));
assertTrue(CIDRUtils.isInRange("10.6.48.157", "0.0.0.0/0"));
assertFalse(CIDRUtils.isInRange("10.6.48.157", "0.0.0.0"));
// Random things
assertTrue(CIDRUtils.isInRange("192.168.2.1", "192.168.2.1"));
assertFalse(CIDRUtils.isInRange("192.168.2.1", "192.168.2.0/32"));
assertTrue(CIDRUtils.isInRange("192.168.2.5", "192.168.2.0/24"));
assertFalse(CIDRUtils.isInRange("92.168.2.1", "fe80:0:0:0:0:0:c0a8:1/120"));
assertTrue(CIDRUtils.isInRange("192.168.2.5", "192.168.2.0/24"));
assertTrue(CIDRUtils.isInRange("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/120"));
assertFalse(CIDRUtils.isInRange("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/128"));
assertFalse(CIDRUtils.isInRange("fe80:0:0:0:0:0:c0a8:11", "192.168.2.0/32"));
assertTrue(CIDRUtils.isInRange("2001:db8:3c0d:5b6d:0:0:42:8329", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertTrue(CIDRUtils.isInRange("2001:db8:3c0d:5b40::", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertTrue(CIDRUtils.isInRange("2001:db8:3c0d:5b7f:ffff:ffff:ffff:ffff", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertTrue(CIDRUtils.isInRange("2001:db8:3c0d:5b53:0:0:0:1", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertFalse(CIDRUtils.isInRange("2001:db8:3c0d:5b3f:ffff:ffff:ffff:ffff", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertFalse(CIDRUtils.isInRange("2001:db8:3c0d:5b80::", "2001:db8:3c0d:5b6d:0:0:42:8329/58"));
assertTrue(CIDRUtils.isInRange("2001:db8:3c0d:5b6d:0:0:42:8329", "2001:db8:3c0d:5b6d:0:0:42:8329/128"));
assertFalse(CIDRUtils.isInRange("2001:db8:3c0d:5b6d:0:0:42:8329", "192.168.2.0/32"));
assertTrue(CIDRUtils.isInRange("127.0.0.1", "127.0.0.1/8"));
assertTrue(CIDRUtils.isInRange("127.0.0.1", "::1/64"));
}
}

View File

@ -90,7 +90,7 @@ public class QueryFolderFailTests extends AbstractQueryFolderTestCase {
() -> plan("process where cidrMatch(source_address, 12345)")); () -> plan("process where cidrMatch(source_address, 12345)"));
String msg = e.getMessage(); String msg = e.getMessage();
assertEquals("Found 1 problem\n" + assertEquals("Found 1 problem\n" +
"line 1:15: argument of [cidrMatch(source_address, 12345)] must be [string], found value [12345] type [integer]", msg); "line 1:15: second argument of [cidrMatch(source_address, 12345)] must be [string], found value [12345] type [integer]", msg);
} }
public void testConcatWithInexact() { public void testConcatWithInexact() {

View File

@ -78,28 +78,28 @@ functionEqualsTrue
process where cidrMatch(source_address, "10.0.0.0/8") == true process where cidrMatch(source_address, "10.0.0.0/8") == true
; ;
{"bool":{"must":[{"term":{"event.category":{"value":"process" {"bool":{"must":[{"term":{"event.category":{"value":"process"
{"term":{"source_address":{"value":"10.0.0.0/8" {"terms":{"source_address":["10.0.0.0/8"]
; ;
functionEqualsFalse functionEqualsFalse
process where cidrMatch(source_address, "10.0.0.0/8") == false process where cidrMatch(source_address, "10.0.0.0/8") == false
; ;
{"bool":{"must":[{"term":{"event.category":{"value":"process" {"bool":{"must":[{"term":{"event.category":{"value":"process"
{"bool":{"must_not":[{"term":{"source_address":{"value":"10.0.0.0/8" {"bool":{"must_not":[{"terms":{"source_address":["10.0.0.0/8"]
; ;
functionNotEqualsTrue functionNotEqualsTrue
process where cidrMatch(source_address, "10.0.0.0/8") != true process where cidrMatch(source_address, "10.0.0.0/8") != true
; ;
{"bool":{"must":[{"term":{"event.category":{"value":"process" {"bool":{"must":[{"term":{"event.category":{"value":"process"
{"bool":{"must_not":[{"term":{"source_address":{"value":"10.0.0.0/8" {"bool":{"must_not":[{"terms":{"source_address":["10.0.0.0/8"]
; ;
functionNotEqualsFalse functionNotEqualsFalse
process where cidrMatch(source_address, "10.0.0.0/8") != false process where cidrMatch(source_address, "10.0.0.0/8") != false
; ;
{"bool":{"must":[{"term":{"event.category":{"value":"process" {"bool":{"must":[{"term":{"event.category":{"value":"process"
{"term":{"source_address":{"value":"10.0.0.0/8" {"terms":{"source_address":["10.0.0.0/8"]
; ;
twoFunctionsEqualsBooleanLiterals twoFunctionsEqualsBooleanLiterals
@ -221,22 +221,44 @@ InternalEqlScriptUtils.concat([InternalQlScriptUtils.docValue(doc,params.v0),par
cidrMatchFunctionOne cidrMatchFunctionOne
process where cidrMatch(source_address, "10.0.0.0/8") process where cidrMatch(source_address, "10.0.0.0/8")
; ;
"term":{"source_address":{"value":"10.0.0.0/8" {"bool":{"must":[{"term":{"event.category":{"value":"process"
{"terms":{"source_address":["10.0.0.0/8"]
;
cidrMatchFunctionOneBool
process where cidrMatch(source_address, "10.0.0.0/8") == true
;
{"bool":{"must":[{"term":{"event.category":{"value":"process"
{"terms":{"source_address":["10.0.0.0/8"]
; ;
cidrMatchFunctionTwo cidrMatchFunctionTwo
process where cidrMatch(source_address, "10.0.0.0/8", "192.168.0.0/16") process where cidrMatch(source_address, "10.0.0.0/8", "192.168.0.0/16")
; ;
"term":{"source_address":{"value":"10.0.0.0/8" {"bool":{"must":[{"term":{"event.category":{"value":"process"
"term":{"source_address":{"value":"192.168.0.0/16" {"terms":{"source_address":["10.0.0.0/8","192.168.0.0/16"]
;
cidrMatchFunctionTwoWithOr
process where cidrMatch(source_address, "10.0.0.0/8") or cidrMatch(source_address, "192.168.0.0/16")
;
{"bool":{"must":[{"term":{"event.category":{"value":"process"
{"bool":{"should":[{"terms":{"source_address":["10.0.0.0/8"],"boost":1.0}},{"terms":{"source_address":["192.168.0.0/16"],"boost":1.0}}
; ;
cidrMatchFunctionThree cidrMatchFunctionThree
process where cidrMatch(source_address, "10.0.0.0/8", "192.168.0.0/16", "2001:db8::/32") process where cidrMatch(source_address, "10.0.0.0/8", "192.168.0.0/16", "2001:db8::/32")
; ;
"term":{"source_address":{"value":"10.0.0.0/8" {"bool":{"must":[{"term":{"event.category":{"value":"process"
"term":{"source_address":{"value":"192.168.0.0/16" {"terms":{"source_address":["10.0.0.0/8","192.168.0.0/16","2001:db8::/32"]
"term":{"source_address":{"value":"2001:db8::/32" ;
cidrMatchFunctionWrapped
process where string(cidrMatch(source_address, "10.6.48.157/8")) == "true"
;
{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(InternalEqlScriptUtils.string(
InternalEqlScriptUtils.cidrMatch(InternalQlScriptUtils.docValue(doc,params.v0),params.v1)),params.v2))"
"params":{"v0":"source_address","v1":["10.6.48.157/8"],"v2":"true"}
; ;
matchFunctionOne matchFunctionOne

View File

@ -375,6 +375,14 @@ public final class ExpressionTranslators {
} }
public static Query doTranslate(ScalarFunction f, TranslatorHandler handler) { public static Query doTranslate(ScalarFunction f, TranslatorHandler handler) {
Query q = doKnownTranslate(f, handler);
if (q != null) {
return q;
}
return handler.wrapFunctionQuery(f, f, new ScriptQuery(f.source(), f.asScript()));
}
public static Query doKnownTranslate(ScalarFunction f, TranslatorHandler handler) {
if (f instanceof StartsWith) { if (f instanceof StartsWith) {
StartsWith sw = (StartsWith) f; StartsWith sw = (StartsWith) f;
if (sw.isCaseSensitive() && sw.field() instanceof FieldAttribute && sw.pattern().foldable()) { if (sw.isCaseSensitive() && sw.field() instanceof FieldAttribute && sw.pattern().foldable()) {
@ -384,8 +392,7 @@ public final class ExpressionTranslators {
return new PrefixQuery(f.source(), targetFieldName, pattern); return new PrefixQuery(f.source(), targetFieldName, pattern);
} }
} }
return null;
return handler.wrapFunctionQuery(f, f, new ScriptQuery(f.source(), f.asScript()));
} }
} }