Skip to content

Commit 4521ca3

Browse files
authored
EQL: Add Head/Tail pipe support (#58536)
Introduce pipe support, in particular head and tail (which can also be chained).
1 parent a7aa3da commit 4521ca3

File tree

64 files changed

+1755
-816
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1755
-816
lines changed

docs/reference/eql/eql-search-api.asciidoc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,12 @@ the events in ascending, lexicographic order.
553553
},
554554
"sort": [
555555
1607252647000
556-
]
556+
],
557+
"fields": {
558+
"@timestamp": [
559+
"1607252647000"
560+
]
561+
}
557562
},
558563
{
559564
"_index": "my_index",
@@ -583,7 +588,12 @@ the events in ascending, lexicographic order.
583588
},
584589
"sort": [
585590
1607339228000
586-
]
591+
],
592+
"fields": {
593+
"@timestamp": [
594+
"1607339228000"
595+
]
596+
}
587597
}
588598
]
589599
}

docs/reference/eql/search.asciidoc

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ https://en.wikipedia.org/wiki/Unix_time[Unix epoch], in ascending order.
101101
},
102102
"sort": [
103103
1607252645000
104-
]
104+
],
105+
"fields": {
106+
"@timestamp": [
107+
"1607252645000"
108+
]
109+
}
105110
},
106111
{
107112
"_index": "sec_logs",
@@ -124,7 +129,12 @@ https://en.wikipedia.org/wiki/Unix_time[Unix epoch], in ascending order.
124129
},
125130
"sort": [
126131
1607339167000
127-
]
132+
],
133+
"fields": {
134+
"@timestamp": [
135+
"1607339167000"
136+
]
137+
}
128138
}
129139
]
130140
}
@@ -169,6 +179,7 @@ GET /sec_logs/_eql/search
169179
"""
170180
}
171181
----
182+
// TEST[s/search/search\?filter_path\=\-\*\.sequences\.events\.\*fields/]
172183
173184
The API returns the following response. Matching events in
174185
the `hits.sequences.events` property are sorted by
@@ -216,11 +227,6 @@ the https://en.wikipedia.org/wiki/Unix_time[Unix epoch], in ascending order.
216227
"path": "C:\\Windows\\System32\\cmd.exe"
217228
}
218229
},
219-
"fields": {
220-
"@timestamp": [
221-
"1607339228000"
222-
]
223-
},
224230
"sort": [
225231
1607339228000
226232
]
@@ -244,11 +250,6 @@ the https://en.wikipedia.org/wiki/Unix_time[Unix epoch], in ascending order.
244250
"path": "C:\\Windows\\System32\\regsvr32.exe"
245251
}
246252
},
247-
"fields": {
248-
"@timestamp": [
249-
"1607339229000"
250-
]
251-
},
252253
"sort": [
253254
1607339229000
254255
]
@@ -293,6 +294,7 @@ GET /sec_logs/_eql/search
293294
"""
294295
}
295296
----
297+
// TEST[s/search/search\?filter_path\=\-\*\.sequences\.events\.\*fields/]
296298
297299
The API returns the following response. The `hits.sequences.join_keys` property
298300
contains the shared `agent.id` value for each matching event.
@@ -341,11 +343,6 @@ contains the shared `agent.id` value for each matching event.
341343
"path": "C:\\Windows\\System32\\cmd.exe"
342344
}
343345
},
344-
"fields": {
345-
"@timestamp": [
346-
"1607339228000"
347-
]
348-
},
349346
"sort": [
350347
1607339228000
351348
]
@@ -369,11 +366,6 @@ contains the shared `agent.id` value for each matching event.
369366
"path": "C:\\Windows\\System32\\regsvr32.exe"
370367
}
371368
},
372-
"fields": {
373-
"@timestamp": [
374-
"1607339229000"
375-
]
376-
},
377369
"sort": [
378370
1607339229000
379371
]

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# This file is populated with additional EQL queries that were not present in the original EQL python implementation
22
# test_queries.toml file in order to keep the original unchanges and easier to sync with the EQL reference implementation tests.
33

4-
54
[[queries]]
65
expected_event_ids = [95]
76
query = '''

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

Lines changed: 56 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# query = 'process where serial_event_id = 1'
1313
# expected_event_ids = [1]
1414

15+
[[queries]]
16+
expected_event_ids = []
17+
query = 'process where missing_field != null'
1518

1619
# fails because of string check - msbuild does not match MSBuild
1720
[[queries]]
@@ -20,19 +23,10 @@ sequence by unique_pid [process where opcode=1 and process_name == 'msbuild.exe'
2023
expected_event_ids = [75273, 75304]
2124
description = "test that process sequences are working correctly"
2225

23-
24-
[[queries]]
25-
query = 'process where true | head 6'
26-
expected_event_ids = [1, 2, 3, 4, 5, 6]
27-
2826
[[queries]]
2927
expected_event_ids = []
3028
query = 'process where missing_field != null'
3129

32-
[[queries]]
33-
expected_event_ids = [1, 2, 3, 4, 5]
34-
query = 'process where bad_field == null | head 5'
35-
3630
[[queries]]
3731
tags = ["comparisons", "pipes"]
3832
query = '''
@@ -65,25 +59,6 @@ process where true
6559
'''
6660
expected_event_ids = [9, 10]
6761

68-
[[queries]]
69-
note = "check that comparisons against null values return false"
70-
expected_event_ids = []
71-
query = '''
72-
process where not (exit_code > -1)
73-
and serial_event_id in (58, 64, 69, 74, 80, 85, 90, 93, 94)
74-
| head 10
75-
'''
76-
77-
[[queries]]
78-
note = "check that comparisons against null values return false"
79-
expected_event_ids = [1, 2, 3, 4, 5, 6, 7]
80-
query = 'process where not (exit_code > -1) | head 7'
81-
82-
[[queries]]
83-
note = "check that comparisons against null values return false"
84-
expected_event_ids = [1, 2, 3, 4, 5, 6, 7]
85-
query = 'process where not (-1 < exit_code) | head 7'
86-
8762
[[queries]]
8863
query = 'process where (serial_event_id<9 and serial_event_id >= 7) or (opcode == pid)'
8964
expected_event_ids = [7, 8]
@@ -182,12 +157,6 @@ process where opcode=1 and process_name == "smss.exe"
182157
'''
183158
expected_event_ids = [78]
184159

185-
[[queries]]
186-
query = '''
187-
file where true
188-
| tail 3'''
189-
expected_event_ids = [92, 95, 96]
190-
191160
[[queries]]
192161
query = '''
193162
process where opcode in (1,3) and process_name in (parent_process_name, "SYSTEM")
@@ -256,17 +225,6 @@ sequence
256225
expected_event_ids = [1, 2, 2, 3]
257226

258227

259-
[[queries]]
260-
query = '''
261-
sequence
262-
[file where event_subtype_full == "file_create_event"] by file_path
263-
[process where opcode == 1] by process_path
264-
[process where opcode == 2] by process_path
265-
[file where event_subtype_full == "file_delete_event"] by file_path
266-
| head 4
267-
| tail 2'''
268-
expected_event_ids = [67, 68, 69, 70, 72, 73, 74, 75]
269-
270228
[[queries]]
271229
query = '''
272230
sequence with maxspan=1d
@@ -322,14 +280,6 @@ sequence with maxspan=0.5s
322280
| tail 2'''
323281
expected_event_ids = []
324282

325-
[[queries]]
326-
query = '''
327-
sequence
328-
[file where opcode=0] by unique_pid
329-
[file where opcode=0] by unique_pid
330-
| head 1'''
331-
expected_event_ids = [55, 61]
332-
333283
[[queries]]
334284
query = '''
335285
sequence
@@ -466,6 +416,59 @@ query = '''
466416
registry where length(bad_field) > 0
467417
'''
468418

419+
[[queries]]
420+
expected_event_ids = [1, 2, 3, 4, 5]
421+
query = 'process where bad_field == null | head 5'
422+
423+
[[queries]]
424+
note = "check that comparisons against null values return false"
425+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
426+
query = 'process where exit_code >= 0'
427+
428+
[[queries]]
429+
note = "check that comparisons against null values return false"
430+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
431+
query = 'process where 0 <= exit_code'
432+
433+
[[queries]]
434+
note = "check that comparisons against null values return false"
435+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
436+
query = 'process where exit_code <= 0'
437+
438+
[[queries]]
439+
note = "check that comparisons against null values return false"
440+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
441+
query = 'process where exit_code < 1'
442+
443+
[[queries]]
444+
note = "check that comparisons against null values return false"
445+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
446+
query = 'process where exit_code > -1'
447+
448+
[[queries]]
449+
note = "check that comparisons against null values return false"
450+
expected_event_ids = [58, 64, 69, 74, 80, 85, 90, 93, 94, 75303]
451+
query = 'process where -1 < exit_code'
452+
453+
[[queries]]
454+
note = "check that comparisons against null values return false"
455+
expected_event_ids = []
456+
query = '''
457+
process where not (exit_code > -1)
458+
and serial_event_id in (58, 64, 69, 74, 80, 85, 90, 93, 94)
459+
| head 10
460+
'''
461+
462+
[[queries]]
463+
note = "check that comparisons against null values return false"
464+
expected_event_ids = [1, 2, 3, 4, 5, 6, 7]
465+
query = 'process where not (exit_code > -1) | head 7'
466+
467+
[[queries]]
468+
note = "check that comparisons against null values return false"
469+
expected_event_ids = [1, 2, 3, 4, 5, 6, 7]
470+
query = 'process where not (-1 < exit_code) | head 7'
471+
469472
[[queries]]
470473
expected_event_ids = [1, 55, 57, 63, 75304]
471474
query = '''
@@ -539,16 +542,6 @@ expected_event_ids = [55, 95]
539542
query = 'process where event of [process where process_name = "python.exe" ]'
540543
expected_event_ids = [48, 50, 51, 54, 93]
541544

542-
[[queries]]
543-
query = '''
544-
sequence by user_name
545-
[file where opcode=0] by file_path
546-
[process where opcode=1] by process_path
547-
[process where opcode=2] by process_path
548-
[file where opcode=2] by file_path
549-
| tail 1'''
550-
expected_event_ids = [88, 89, 90, 91]
551-
552545
[[queries]]
553546
query = '''
554547
sequence by user_name
@@ -567,16 +560,6 @@ until [process where opcode=5] by ppid,process_path
567560
| head 2'''
568561
expected_event_ids = [55, 59, 61, 65]
569562

570-
[[queries]]
571-
query = '''
572-
sequence by pid
573-
[file where opcode=0] by file_path
574-
[process where opcode=1] by process_path
575-
[process where opcode=2] by process_path
576-
[file where opcode=2] by file_path
577-
| tail 1'''
578-
expected_event_ids = []
579-
580563
[[queries]]
581564
query = '''
582565
join by user_name

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/execution/assembler/Criterion.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,35 @@
99
import org.elasticsearch.search.SearchHit;
1010
import org.elasticsearch.search.builder.SearchSourceBuilder;
1111
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
12+
import org.elasticsearch.xpack.eql.execution.search.QueryRequest;
1213
import org.elasticsearch.xpack.ql.execution.search.extractor.HitExtractor;
1314

1415
import java.util.List;
1516

16-
public class Criterion {
17+
public class Criterion implements QueryRequest {
1718

1819
private final SearchSourceBuilder searchSource;
1920
private final List<HitExtractor> keyExtractors;
2021
private final HitExtractor timestampExtractor;
2122
private final HitExtractor tiebreakerExtractor;
2223

24+
// search after markers
25+
private Object[] startMarker;
26+
private Object[] stopMarker;
27+
28+
//TODO: should accept QueryRequest instead of another SearchSourceBuilder
2329
public Criterion(SearchSourceBuilder searchSource, List<HitExtractor> searchAfterExractors, HitExtractor timestampExtractor,
2430
HitExtractor tiebreakerExtractor) {
2531
this.searchSource = searchSource;
2632
this.keyExtractors = searchAfterExractors;
2733
this.timestampExtractor = timestampExtractor;
2834
this.tiebreakerExtractor = tiebreakerExtractor;
35+
36+
this.startMarker = null;
37+
this.stopMarker = null;
2938
}
3039

40+
@Override
3141
public SearchSourceBuilder searchSource() {
3242
return searchSource;
3343
}
@@ -64,8 +74,34 @@ public Comparable<Object> tiebreaker(SearchHit hit) {
6474
throw new EqlIllegalArgumentException("Expected tiebreaker to be Comparable but got {}", tb);
6575
}
6676

67-
public void fromMarkers(Object[] markers) {
68-
// TODO: this is likely to be rewritten afterwards
69-
searchSource.searchAfter(markers);
77+
public Object[] startMarker() {
78+
return startMarker;
79+
}
80+
81+
public Object[] stopMarker() {
82+
return stopMarker;
83+
}
84+
85+
private Object[] marker(SearchHit hit) {
86+
long timestamp = timestamp(hit);
87+
Object tiebreaker = null;
88+
if (tiebreakerExtractor() != null) {
89+
tiebreaker = tiebreaker(hit);
90+
}
91+
92+
return tiebreaker != null ? new Object[] { timestamp, tiebreaker } : new Object[] { timestamp };
93+
}
94+
95+
public void startMarker(SearchHit hit) {
96+
startMarker = marker(hit);
97+
}
98+
99+
public void stopMarker(SearchHit hit) {
100+
stopMarker = marker(hit);
101+
}
102+
103+
public Criterion useMarker(Object[] marker) {
104+
searchSource.searchAfter(marker);
105+
return this;
70106
}
71107
}

0 commit comments

Comments
 (0)