Skip to content

Commit

Permalink
Stop IM rule execution if there are no events (#123811)
Browse files Browse the repository at this point in the history
* Add event count check

* Fix linter

* Make tuple required
  • Loading branch information
nkhristinin authored Jan 28, 2022
1 parent f697ffe commit e774ab4
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createThreatSignal } from './create_threat_signal';
import { SearchAfterAndBulkCreateReturnType } from '../types';
import { buildExecutionIntervalValidator, combineConcurrentResults } from './utils';
import { buildThreatEnrichment } from './build_threat_enrichment';
import { getEventCount } from './get_event_count';

export const createThreatSignals = async ({
alertId,
Expand Down Expand Up @@ -62,6 +63,23 @@ export const createThreatSignals = async ({
warningMessages: [],
};

const eventCount = await getEventCount({
esClient: services.scopedClusterClient.asCurrentUser,
index: inputIndex,
exceptionItems,
tuple,
query,
language,
filters,
});

logger.debug(`Total event count: ${eventCount}`);

if (eventCount === 0) {
logger.debug(buildRuleMessage('Indicator matching rule has completed'));
return results;
}

let threatListCount = await getThreatListCount({
esClient: services.scopedClusterClient.asCurrentUser,
exceptionItems,
Expand All @@ -70,6 +88,7 @@ export const createThreatSignals = async ({
language: threatLanguage,
index: threatIndex,
});

logger.debug(buildRuleMessage(`Total indicator items: ${threatListCount}`));

let threatList = await getThreatList({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* 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 moment from 'moment';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { getEventCount } from './get_event_count';

describe('getEventCount', () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();

beforeEach(() => {
jest.clearAllMocks();
});

it('can respect tuple', () => {
getEventCount({
esClient,
query: '*:*',
language: 'kuery',
filters: [],
exceptionItems: [],
index: ['test-index'],
tuple: { to: moment('2022-01-14'), from: moment('2022-01-13'), maxSignals: 1337 },
});

expect(esClient.count).toHaveBeenCalledWith({
body: {
query: {
bool: {
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,
},
},
],
},
},
{ match_all: {} },
],
},
},
},
ignore_unavailable: true,
index: ['test-index'],
});
});

it('can override timestamp', () => {
getEventCount({
esClient,
query: '*:*',
language: 'kuery',
filters: [],
exceptionItems: [],
index: ['test-index'],
tuple: { to: moment('2022-01-14'), from: moment('2022-01-13'), maxSignals: 1337 },
timestampOverride: 'event.ingested',
});

expect(esClient.count).toHaveBeenCalledWith({
body: {
query: {
bool: {
filter: [
{ bool: { must: [], filter: [], should: [], must_not: [] } },
{
bool: {
filter: [
{
bool: {
should: [
{
range: {
'event.ingested': {
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' } } } },
],
},
},
],
minimum_should_match: 1,
},
},
],
},
},
{ match_all: {} },
],
},
},
},
ignore_unavailable: true,
index: ['test-index'],
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 { EventCountOptions } from './types';
import { getQueryFilter } from '../../../../../common/detection_engine/get_query_filter';
import { buildEventsSearchQuery } from '../build_events_query';

export const getEventCount = async ({
esClient,
query,
language,
filters,
index,
exceptionItems,
tuple,
timestampOverride,
}: EventCountOptions): Promise<number> => {
const filter = getQueryFilter(query, language ?? 'kuery', filters, index, exceptionItems);
const eventSearchQueryBodyQuery = buildEventsSearchQuery({
index,
from: tuple.from.toISOString(),
to: tuple.to.toISOString(),
filter,
size: 0,
timestampOverride,
searchAfterSortIds: undefined,
}).body.query;
const { body: response } = await esClient.count({
body: { query: eventSearchQueryBodyQuery },
ignore_unavailable: true,
index,
});
return response.count;
};
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,14 @@ export interface BuildThreatEnrichmentOptions {
threatLanguage: ThreatLanguageOrUndefined;
threatQuery: ThreatQuery;
}

export interface EventCountOptions {
esClient: ElasticsearchClient;
exceptionItems: ExceptionListItemSchema[];
index: string[];
language: ThreatLanguageOrUndefined;
query: string;
filters: unknown[];
tuple: RuleRangeTuple;
timestampOverride?: string;
}

0 comments on commit e774ab4

Please sign in to comment.