mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Alerts] EQL rules fallback to @timestamp if timestamp override doesn't exist (#127989)
* EQL rules fallback to @timestamp if timestamp override doesn't exist * Fix getEventCount test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a1085f4b75
commit
bdecf1568e
7 changed files with 447 additions and 482 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getQueryFilter, getAllFilters, buildEqlSearchRequest } from './get_query_filter';
|
||||
import { getQueryFilter, getAllFilters } from './get_query_filter';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { getExceptionListItemSchemaMock } from '../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
|
||||
|
@ -1112,197 +1112,6 @@ describe('get_filter', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('buildEqlSearchRequest', () => {
|
||||
test('should build a basic request with time range', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
undefined,
|
||||
[],
|
||||
undefined
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'now',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should build a request with timestamp and event category overrides', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
'event.ingested',
|
||||
[],
|
||||
'event.other_category'
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
event_category_field: 'event.other_category',
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
gte: 'now-5m',
|
||||
lte: 'now',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: 'event.ingested',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should build a request with exceptions', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
undefined,
|
||||
[getExceptionListItemSchemaMock()],
|
||||
undefined
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'now',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
nested: {
|
||||
path: 'some.parentField',
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'some.parentField.nested.field': 'some value',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
score_mode: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'some.not.nested.field': 'some value',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllFilters', () => {
|
||||
const exceptionsFilter = {
|
||||
meta: { alias: null, negate: false, disabled: false },
|
||||
|
|
|
@ -12,13 +12,9 @@ import type {
|
|||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { buildExceptionFilter } from '@kbn/securitysolution-list-utils';
|
||||
import { Filter, EsQueryConfig, DataViewBase, buildEsQuery } from '@kbn/es-query';
|
||||
import {
|
||||
EqlSearchRequest,
|
||||
QueryDslQueryContainer,
|
||||
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { ESBoolQuery } from '../typed_json';
|
||||
import { Query, Index, TimestampOverrideOrUndefined } from './schemas/common/schemas';
|
||||
import { Query, Index } from './schemas/common/schemas';
|
||||
|
||||
export const getQueryFilter = (
|
||||
query: Query,
|
||||
|
@ -61,76 +57,3 @@ export const getAllFilters = (filters: Filter[], exceptionFilter: Filter | undef
|
|||
return [...filters];
|
||||
}
|
||||
};
|
||||
|
||||
export const buildEqlSearchRequest = (
|
||||
query: string,
|
||||
index: string[],
|
||||
from: string,
|
||||
to: string,
|
||||
size: number,
|
||||
timestampOverride: TimestampOverrideOrUndefined,
|
||||
exceptionLists: ExceptionListItemSchema[],
|
||||
eventCategoryOverride: string | undefined
|
||||
): EqlSearchRequest => {
|
||||
const timestamp = timestampOverride ?? '@timestamp';
|
||||
|
||||
const defaultTimeFields = ['@timestamp'];
|
||||
const timestamps =
|
||||
timestampOverride != null ? [timestampOverride, ...defaultTimeFields] : defaultTimeFields;
|
||||
const docFields = timestamps.map((tstamp) => ({
|
||||
field: tstamp,
|
||||
format: 'strict_date_optional_time',
|
||||
}));
|
||||
|
||||
// Assume that `indices.query.bool.max_clause_count` is at least 1024 (the default value),
|
||||
// allowing us to make 1024-item chunks of exception list items.
|
||||
// Discussion at https://issues.apache.org/jira/browse/LUCENE-4835 indicates that 1024 is a
|
||||
// very conservative value.
|
||||
const exceptionFilter = buildExceptionFilter({
|
||||
lists: exceptionLists,
|
||||
excludeExceptions: true,
|
||||
chunkSize: 1024,
|
||||
});
|
||||
const requestFilter: QueryDslQueryContainer[] = [
|
||||
{
|
||||
range: {
|
||||
[timestamp]: {
|
||||
gte: from,
|
||||
lte: to,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
if (exceptionFilter !== undefined) {
|
||||
requestFilter.push({
|
||||
bool: {
|
||||
must_not: {
|
||||
bool: exceptionFilter.query?.bool,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
const fields = [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
...docFields,
|
||||
];
|
||||
return {
|
||||
index,
|
||||
allow_no_indices: true,
|
||||
body: {
|
||||
size,
|
||||
query,
|
||||
filter: {
|
||||
bool: {
|
||||
filter: requestFilter,
|
||||
},
|
||||
},
|
||||
event_category_field: eventCategoryOverride,
|
||||
fields,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildEventsSearchQuery } from './build_events_query';
|
||||
import { buildEqlSearchRequest, buildEventsSearchQuery } from './build_events_query';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
|
||||
describe('create_signals', () => {
|
||||
test('it builds a now-5m up to today filter', () => {
|
||||
|
@ -29,25 +30,12 @@ describe('create_signals', () => {
|
|||
filter: [
|
||||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -100,13 +88,22 @@ describe('create_signals', () => {
|
|||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
|
@ -115,33 +112,18 @@ describe('create_signals', () => {
|
|||
},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'event.ingested',
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'event.ingested',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -204,25 +186,12 @@ describe('create_signals', () => {
|
|||
filter: [
|
||||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -275,25 +244,12 @@ describe('create_signals', () => {
|
|||
filter: [
|
||||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -345,25 +301,12 @@ describe('create_signals', () => {
|
|||
filter: [
|
||||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -422,25 +365,12 @@ describe('create_signals', () => {
|
|||
filter: [
|
||||
{},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'today',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -536,4 +466,226 @@ describe('create_signals', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildEqlSearchRequest', () => {
|
||||
test('should build a basic request with time range', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
undefined,
|
||||
[],
|
||||
undefined
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'now',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should build a request with timestamp and event category overrides', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
'event.ingested',
|
||||
[],
|
||||
'event.other_category'
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
event_category_field: 'event.other_category',
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
lte: 'now',
|
||||
gte: 'now-5m',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: 'now',
|
||||
gte: 'now-5m',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: 'event.ingested',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: 'event.ingested',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should build a request with exceptions', () => {
|
||||
const request = buildEqlSearchRequest(
|
||||
'process where true',
|
||||
['testindex1', 'testindex2'],
|
||||
'now-5m',
|
||||
'now',
|
||||
100,
|
||||
undefined,
|
||||
[getExceptionListItemSchemaMock()],
|
||||
undefined
|
||||
);
|
||||
expect(request).toEqual({
|
||||
allow_no_indices: true,
|
||||
index: ['testindex1', 'testindex2'],
|
||||
body: {
|
||||
size: 100,
|
||||
query: 'process where true',
|
||||
filter: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5m',
|
||||
lte: 'now',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
nested: {
|
||||
path: 'some.parentField',
|
||||
query: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'some.parentField.nested.field': 'some value',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
score_mode: 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match_phrase: {
|
||||
'some.not.nested.field': 'some value',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
{
|
||||
field: '@timestamp',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { buildExceptionFilter } from '@kbn/securitysolution-list-utils';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas';
|
||||
|
||||
interface BuildEventsSearchQuery {
|
||||
aggregations?: Record<string, estypes.AggregationsAggregationContainer>;
|
||||
index: string[];
|
||||
|
@ -21,6 +22,70 @@ interface BuildEventsSearchQuery {
|
|||
trackTotalHits?: boolean;
|
||||
}
|
||||
|
||||
const buildTimeRangeFilter = ({
|
||||
to,
|
||||
from,
|
||||
timestampOverride,
|
||||
}: {
|
||||
to: string;
|
||||
from: string;
|
||||
timestampOverride?: string;
|
||||
}): estypes.QueryDslQueryContainer => {
|
||||
// If the timestampOverride is provided, documents must either populate timestampOverride with a timestamp in the range
|
||||
// or must NOT populate the timestampOverride field at all and `@timestamp` must fall in the range.
|
||||
// If timestampOverride is not provided, we simply use `@timestamp`
|
||||
return timestampOverride != null
|
||||
? {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
[timestampOverride]: {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: timestampOverride,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
: {
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const buildEventsSearchQuery = ({
|
||||
aggregations,
|
||||
index,
|
||||
|
@ -41,59 +106,9 @@ export const buildEventsSearchQuery = ({
|
|||
format: 'strict_date_optional_time',
|
||||
}));
|
||||
|
||||
const rangeFilter: estypes.QueryDslQueryContainer[] =
|
||||
timestampOverride != null
|
||||
? [
|
||||
{
|
||||
range: {
|
||||
[timestampOverride]: {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
must_not: {
|
||||
exists: {
|
||||
field: timestampOverride,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: to,
|
||||
gte: from,
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
const rangeFilter = buildTimeRangeFilter({ to, from, timestampOverride });
|
||||
|
||||
const filterWithTime: estypes.QueryDslQueryContainer[] = [
|
||||
filter,
|
||||
{ bool: { filter: [{ bool: { should: [...rangeFilter], minimum_should_match: 1 } }] } },
|
||||
];
|
||||
const filterWithTime: estypes.QueryDslQueryContainer[] = [filter, rangeFilter];
|
||||
|
||||
const sort: estypes.Sort = [];
|
||||
if (timestampOverride) {
|
||||
|
@ -151,3 +166,66 @@ export const buildEventsSearchQuery = ({
|
|||
}
|
||||
return searchQuery;
|
||||
};
|
||||
|
||||
export const buildEqlSearchRequest = (
|
||||
query: string,
|
||||
index: string[],
|
||||
from: string,
|
||||
to: string,
|
||||
size: number,
|
||||
timestampOverride: TimestampOverrideOrUndefined,
|
||||
exceptionLists: ExceptionListItemSchema[],
|
||||
eventCategoryOverride: string | undefined
|
||||
): estypes.EqlSearchRequest => {
|
||||
const defaultTimeFields = ['@timestamp'];
|
||||
const timestamps =
|
||||
timestampOverride != null ? [timestampOverride, ...defaultTimeFields] : defaultTimeFields;
|
||||
const docFields = timestamps.map((tstamp) => ({
|
||||
field: tstamp,
|
||||
format: 'strict_date_optional_time',
|
||||
}));
|
||||
|
||||
// Assume that `indices.query.bool.max_clause_count` is at least 1024 (the default value),
|
||||
// allowing us to make 1024-item chunks of exception list items.
|
||||
// Discussion at https://issues.apache.org/jira/browse/LUCENE-4835 indicates that 1024 is a
|
||||
// very conservative value.
|
||||
const exceptionFilter = buildExceptionFilter({
|
||||
lists: exceptionLists,
|
||||
excludeExceptions: true,
|
||||
chunkSize: 1024,
|
||||
});
|
||||
|
||||
const rangeFilter = buildTimeRangeFilter({ to, from, timestampOverride });
|
||||
const requestFilter: estypes.QueryDslQueryContainer[] = [rangeFilter];
|
||||
if (exceptionFilter !== undefined) {
|
||||
requestFilter.push({
|
||||
bool: {
|
||||
must_not: {
|
||||
bool: exceptionFilter.query?.bool,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
const fields = [
|
||||
{
|
||||
field: '*',
|
||||
include_unmapped: true,
|
||||
},
|
||||
...docFields,
|
||||
];
|
||||
return {
|
||||
index,
|
||||
allow_no_indices: true,
|
||||
body: {
|
||||
size,
|
||||
query,
|
||||
filter: {
|
||||
bool: {
|
||||
filter: requestFilter,
|
||||
},
|
||||
},
|
||||
event_category_field: eventCategoryOverride,
|
||||
fields,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
AlertInstanceState,
|
||||
AlertServices,
|
||||
} from '../../../../../../alerting/server';
|
||||
import { buildEqlSearchRequest } from '../../../../../common/detection_engine/get_query_filter';
|
||||
import { buildEqlSearchRequest } from '../build_events_query';
|
||||
import { hasLargeValueItem } from '../../../../../common/detection_engine/utils';
|
||||
import { isOutdated } from '../../migrations/helpers';
|
||||
import { getIndexVersion } from '../../routes/index/get_index_version';
|
||||
|
|
|
@ -33,25 +33,12 @@ describe('getEventCount', () => {
|
|||
filter: [
|
||||
{ bool: { must: [], filter: [], should: [], must_not: [] } },
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: '2022-01-14T05:00:00.000Z',
|
||||
gte: '2022-01-13T05:00:00.000Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: '2022-01-14T05:00:00.000Z',
|
||||
gte: '2022-01-13T05:00:00.000Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ match_all: {} },
|
||||
|
@ -84,40 +71,34 @@ describe('getEventCount', () => {
|
|||
{ bool: { must: [], filter: [], should: [], must_not: [] } },
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
lte: '2022-01-14T05:00:00.000Z',
|
||||
gte: '2022-01-13T05:00:00.000Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: [
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'event.ingested': {
|
||||
'@timestamp': {
|
||||
lte: '2022-01-14T05:00:00.000Z',
|
||||
gte: '2022-01-13T05:00:00.000Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
lte: '2022-01-14T05:00:00.000Z',
|
||||
gte: '2022-01-13T05:00:00.000Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
{ bool: { must_not: { exists: { field: 'event.ingested' } } } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ bool: { must_not: { exists: { field: 'event.ingested' } } } },
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
{ match_all: {} },
|
||||
|
|
|
@ -279,6 +279,28 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
expect(signalsOrderedByEventId.length).equal(2);
|
||||
});
|
||||
|
||||
it('should generate 2 signals when timestamp override does not exist', async () => {
|
||||
const rule: EqlCreateSchema = {
|
||||
...getEqlRuleForSignalTesting(['myfa*']),
|
||||
timestamp_override: 'event.fakeingestfield',
|
||||
};
|
||||
const { id } = await createRule(supertest, log, rule);
|
||||
|
||||
await waitForRuleSuccessOrStatus(
|
||||
supertest,
|
||||
log,
|
||||
id,
|
||||
RuleExecutionStatus['partial failure']
|
||||
);
|
||||
await sleep(5000);
|
||||
await waitForSignalsToBePresent(supertest, log, 2, [id]);
|
||||
const signalsResponse = await getSignalsByIds(supertest, log, [id, id]);
|
||||
const signals = signalsResponse.hits.hits.map((hit) => hit._source);
|
||||
const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc');
|
||||
|
||||
expect(signalsOrderedByEventId.length).equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue