Skip to content

Commit 4b47e84

Browse files
authored
EQL: Fix cidrMatch function fails to match when used in scripts (#56246)
Addresses #55709
1 parent cf40c96 commit 4b47e84

File tree

15 files changed

+747
-51
lines changed

15 files changed

+747
-51
lines changed

x-pack/plugin/eql/qa/common/src/main/resources/test_queries_supported.toml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,43 @@ query = '''
1414
file where between(file_path, "dev", ".json", true) == "\\TestLogs\\something"
1515
'''
1616

17+
[[queries]]
18+
expected_event_ids = [75304, 75305]
19+
query = '''
20+
network where cidrMatch(source_address, "10.6.48.157/8") == true
21+
'''
22+
23+
[[queries]]
24+
expected_event_ids = [75304, 75305]
25+
query = '''
26+
network where string(cidrMatch(source_address, "10.6.48.157/8")) == "true"
27+
'''
28+
29+
[[queries]]
30+
expected_event_ids = [75304, 75305]
31+
query = '''
32+
network where true == cidrMatch(source_address, "10.6.48.157/8")
33+
'''
34+
35+
[[queries]]
36+
expected_event_ids = []
37+
query = '''
38+
network where cidrMatch(source_address, "192.168.0.0/16") == true
39+
'''
40+
41+
[[queries]]
42+
expected_event_ids = [75304, 75305]
43+
query = '''
44+
network where cidrMatch(source_address, "192.168.0.0/16", "10.6.48.157/8") == true
45+
'''
46+
47+
[[queries]]
48+
expected_event_ids = [75304, 75305]
49+
query = '''
50+
network where cidrMatch(source_address, "0.0.0.0/0") == true
51+
'''
52+
53+
1754
[[queries]]
1855
description = "test string concatenation. update test to avoid case-sensitivity issues"
1956
query = '''
@@ -41,7 +78,6 @@ query = 'process where serial_event_id < 5 and concat(process_name, null, null)
4178
expected_event_ids = [1, 2, 3, 4]
4279

4380

44-
4581
[[queries]]
4682
query = 'process where serial_event_id < 5 and concat(parent_process_name, null) == null'
4783
expected_event_ids = [1, 2, 3, 4]

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/CIDRMatch.java

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,58 +6,55 @@
66

77
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
88

9+
import org.elasticsearch.common.logging.LoggerMessageFormat;
910
import org.elasticsearch.xpack.ql.expression.Expression;
1011
import org.elasticsearch.xpack.ql.expression.Expressions;
1112
import org.elasticsearch.xpack.ql.expression.Expressions.ParamOrdinal;
12-
import org.elasticsearch.xpack.ql.expression.function.scalar.BaseSurrogateFunction;
13+
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
1314
import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
14-
import org.elasticsearch.xpack.ql.expression.predicate.logical.Or;
15-
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.Equals;
15+
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
16+
import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
17+
import org.elasticsearch.xpack.ql.expression.gen.script.Scripts;
1618
import org.elasticsearch.xpack.ql.tree.NodeInfo;
1719
import org.elasticsearch.xpack.ql.tree.Source;
1820
import org.elasticsearch.xpack.ql.type.DataType;
1921
import org.elasticsearch.xpack.ql.type.DataTypes;
2022
import org.elasticsearch.xpack.ql.util.CollectionUtils;
2123

24+
import java.util.ArrayList;
25+
import java.util.LinkedHashSet;
2226
import java.util.List;
2327

28+
import static java.util.Collections.emptyList;
2429
import static java.util.Collections.singletonList;
30+
import static org.elasticsearch.xpack.eql.expression.function.scalar.string.CIDRMatchFunctionProcessor.doProcess;
2531
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isFoldable;
2632
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isIPAndExact;
2733
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact;
34+
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder;
2835

2936
/**
3037
* EQL specific cidrMatch function
3138
* Returns true if the source address matches any of the provided CIDR blocks.
3239
* Refer to: https://eql.readthedocs.io/en/latest/query-guide/functions.html#cidrMatch
3340
*/
34-
public class CIDRMatch extends BaseSurrogateFunction {
41+
public class CIDRMatch extends ScalarFunction {
3542

3643
private final Expression field;
3744
private final List<Expression> addresses;
3845

3946
public CIDRMatch(Source source, Expression field, List<Expression> addresses) {
40-
super(source, CollectionUtils.combine(singletonList(field), addresses));
47+
super(source, CollectionUtils.combine(singletonList(field), addresses == null ? emptyList() : addresses));
4148
this.field = field;
42-
this.addresses = addresses;
49+
this.addresses = addresses == null ? emptyList() : addresses;
4350
}
4451

45-
@Override
46-
protected NodeInfo<? extends Expression> info() {
47-
return NodeInfo.create(this, CIDRMatch::new, field, addresses);
52+
public Expression field() {
53+
return field;
4854
}
4955

50-
@Override
51-
public Expression replaceChildren(List<Expression> newChildren) {
52-
if (newChildren.size() < 2) {
53-
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
54-
}
55-
return new CIDRMatch(source(), newChildren.get(0), newChildren.subList(1, newChildren.size()));
56-
}
57-
58-
@Override
59-
public DataType dataType() {
60-
return DataTypes.BOOLEAN;
56+
public List<Expression> addresses() {
57+
return addresses;
6158
}
6259

6360
@Override
@@ -71,15 +68,6 @@ protected TypeResolution resolveType() {
7168
return resolution;
7269
}
7370

74-
for (Expression addr : addresses) {
75-
// Currently we have limited enum for ordinal numbers
76-
// So just using default here for error messaging
77-
resolution = isStringAndExact(addr, sourceText(), ParamOrdinal.DEFAULT);
78-
if (resolution.unresolved()) {
79-
return resolution;
80-
}
81-
}
82-
8371
int index = 1;
8472

8573
for (Expression addr : addresses) {
@@ -101,14 +89,60 @@ protected TypeResolution resolveType() {
10189
}
10290

10391
@Override
104-
public ScalarFunction makeSubstitute() {
105-
ScalarFunction func = null;
106-
92+
protected Pipe makePipe() {
93+
ArrayList<Pipe> arr = new ArrayList<>(addresses.size());
10794
for (Expression address : addresses) {
108-
final Equals eq = new Equals(source(), field, address);
109-
func = (func == null) ? eq : new Or(source(), func, eq);
95+
arr.add(Expressions.pipe(address));
11096
}
97+
return new CIDRMatchFunctionPipe(source(), this, Expressions.pipe(field), arr);
98+
}
99+
100+
@Override
101+
public boolean foldable() {
102+
return field.foldable() && Expressions.foldable(addresses);
103+
}
111104

112-
return func;
105+
@Override
106+
public Object fold() {
107+
return doProcess(field.fold(), Expressions.fold(addresses));
108+
}
109+
110+
@Override
111+
protected NodeInfo<? extends Expression> info() {
112+
return NodeInfo.create(this, CIDRMatch::new, field, addresses);
113+
}
114+
115+
@Override
116+
public ScriptTemplate asScript() {
117+
ScriptTemplate leftScript = asScript(field);
118+
119+
List<Object> values = new ArrayList<>(new LinkedHashSet<>(Expressions.fold(addresses)));
120+
return new ScriptTemplate(
121+
formatTemplate(LoggerMessageFormat.format("{eql}.","cidrMatch({}, {})", leftScript.template())),
122+
paramsBuilder()
123+
.script(leftScript.params())
124+
.variable(values)
125+
.build(),
126+
dataType());
127+
}
128+
129+
@Override
130+
public ScriptTemplate scriptWithField(FieldAttribute field) {
131+
return new ScriptTemplate(processScript(Scripts.DOC_VALUE),
132+
paramsBuilder().variable(field.exactAttribute().name()).build(),
133+
dataType());
134+
}
135+
136+
@Override
137+
public DataType dataType() {
138+
return DataTypes.BOOLEAN;
139+
}
140+
141+
@Override
142+
public Expression replaceChildren(List<Expression> newChildren) {
143+
if (newChildren.size() < 2) {
144+
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
145+
}
146+
return new CIDRMatch(source(), newChildren.get(0), newChildren.subList(1, newChildren.size()));
113147
}
114148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
7+
8+
import org.elasticsearch.xpack.ql.execution.search.QlSourceBuilder;
9+
import org.elasticsearch.xpack.ql.expression.Expression;
10+
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
11+
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor;
12+
import org.elasticsearch.xpack.ql.tree.NodeInfo;
13+
import org.elasticsearch.xpack.ql.tree.Source;
14+
import org.elasticsearch.xpack.ql.util.CollectionUtils;
15+
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import java.util.Objects;
20+
21+
public class CIDRMatchFunctionPipe extends Pipe {
22+
23+
private final Pipe source;
24+
private final List<Pipe> addresses;
25+
26+
public CIDRMatchFunctionPipe(Source source, Expression expression, Pipe src, List<Pipe> addresses) {
27+
super(source, expression, CollectionUtils.combine(Collections.singletonList(src), addresses));
28+
this.source = src;
29+
this.addresses = addresses;
30+
}
31+
32+
@Override
33+
public final Pipe replaceChildren(List<Pipe> newChildren) {
34+
if (newChildren.size() < 2) {
35+
throw new IllegalArgumentException("expected at least [2] children but received [" + newChildren.size() + "]");
36+
}
37+
return replaceChildren(newChildren.get(0), newChildren.subList(1, newChildren.size()));
38+
}
39+
40+
@Override
41+
public final Pipe resolveAttributes(AttributeResolver resolver) {
42+
Pipe newSource = source.resolveAttributes(resolver);
43+
boolean same = (newSource == source);
44+
45+
ArrayList<Pipe> newAddresses = new ArrayList<Pipe>(addresses.size());
46+
for (Pipe address : addresses) {
47+
Pipe newAddress = address.resolveAttributes(resolver);
48+
newAddresses.add(newAddress);
49+
same = same && (address == newAddress);
50+
}
51+
if (same) {
52+
return this;
53+
}
54+
return replaceChildren(newSource, newAddresses);
55+
}
56+
57+
@Override
58+
public boolean supportedByAggsOnlyQuery() {
59+
if (source.supportedByAggsOnlyQuery() == false) {
60+
return false;
61+
}
62+
for (Pipe address : addresses) {
63+
if (address.supportedByAggsOnlyQuery() == false) {
64+
return false;
65+
}
66+
}
67+
return true;
68+
}
69+
70+
@Override
71+
public boolean resolved() {
72+
if (source.resolved() == false) {
73+
return false;
74+
}
75+
for (Pipe address : addresses) {
76+
if (address.resolved() == false) {
77+
return false;
78+
}
79+
}
80+
return true;
81+
}
82+
83+
protected Pipe replaceChildren(Pipe newSource, List<Pipe> newAddresses) {
84+
return new CIDRMatchFunctionPipe(source(), expression(), newSource, newAddresses);
85+
}
86+
87+
@Override
88+
public final void collectFields(QlSourceBuilder sourceBuilder) {
89+
source.collectFields(sourceBuilder);
90+
for (Pipe address : addresses) {
91+
address.collectFields(sourceBuilder);
92+
}
93+
}
94+
95+
@Override
96+
protected NodeInfo<CIDRMatchFunctionPipe> info() {
97+
return NodeInfo.create(this, CIDRMatchFunctionPipe::new, expression(), source, addresses);
98+
}
99+
100+
@Override
101+
public CIDRMatchFunctionProcessor asProcessor() {
102+
ArrayList<Processor> processors = new ArrayList<>(addresses.size());
103+
for (Pipe address: addresses) {
104+
processors.add(address.asProcessor());
105+
}
106+
return new CIDRMatchFunctionProcessor(source.asProcessor(), processors);
107+
}
108+
109+
public Pipe src() {
110+
return source;
111+
}
112+
113+
public List<Pipe> addresses() {
114+
return addresses;
115+
}
116+
117+
@Override
118+
public int hashCode() {
119+
return Objects.hash(source(), addresses());
120+
}
121+
122+
@Override
123+
public boolean equals(Object obj) {
124+
if (this == obj) {
125+
return true;
126+
}
127+
128+
if (obj == null || getClass() != obj.getClass()) {
129+
return false;
130+
}
131+
132+
CIDRMatchFunctionPipe other = (CIDRMatchFunctionPipe) obj;
133+
return Objects.equals(source(), other.source())
134+
&& Objects.equals(addresses(), other.addresses());
135+
}
136+
}

0 commit comments

Comments
 (0)