Skip to content

Commit f0838e6

Browse files
[Security Solution][Detections] Pull gap detection logic out in preparation for sharing between rule types (#91966)
* Pull gap detection logic out in preparation for sharing between rule types * Remove comments and unused import * Remove unncessary function, cleanup, comment * Update comment * Address PR comments * remove unneeded mocks * Undo change to parseInterval * Remove another unneeded mock Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 45bea2b commit f0838e6

10 files changed

Lines changed: 279 additions & 467 deletions

File tree

x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
* 2.0.
66
*/
77

8-
import moment from 'moment';
98
import {
109
sampleRuleAlertParams,
1110
sampleEmptyDocSearchResults,
@@ -23,9 +22,10 @@ import { alertsMock, AlertServicesMock } from '../../../../../alerts/server/mock
2322
import uuid from 'uuid';
2423
import { listMock } from '../../../../../lists/server/mocks';
2524
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
26-
import { BulkResponse } from './types';
25+
import { BulkResponse, RuleRangeTuple } from './types';
2726
import { SearchListItemArraySchema } from '../../../../../lists/common/schemas';
2827
import { getSearchListItemResponseMock } from '../../../../../lists/common/schemas/response/search_list_item_schema.mock';
28+
import { getRuleRangeTuples } from './utils';
2929

3030
const buildRuleMessage = buildRuleMessageFactory({
3131
id: 'fake id',
@@ -39,16 +39,26 @@ describe('searchAfterAndBulkCreate', () => {
3939
let inputIndexPattern: string[] = [];
4040
let listClient = listMock.getListClient();
4141
const someGuids = Array.from({ length: 13 }).map(() => uuid.v4());
42+
const sampleParams = sampleRuleAlertParams(30);
43+
let tuples: RuleRangeTuple[];
4244
beforeEach(() => {
4345
jest.clearAllMocks();
4446
listClient = listMock.getListClient();
4547
listClient.searchListItemByValues = jest.fn().mockResolvedValue([]);
4648
inputIndexPattern = ['auditbeat-*'];
4749
mockService = alertsMock.createAlertServices();
50+
({ tuples } = getRuleRangeTuples({
51+
logger: mockLogger,
52+
previousStartedAt: new Date(),
53+
from: sampleParams.from,
54+
to: sampleParams.to,
55+
interval: '5m',
56+
maxSignals: sampleParams.maxSignals,
57+
buildRuleMessage,
58+
}));
4859
});
4960

5061
test('should return success with number of searches less than max signals', async () => {
51-
const sampleParams = sampleRuleAlertParams(30);
5262
mockService.callCluster
5363
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3)))
5464
.mockResolvedValueOnce({
@@ -112,11 +122,9 @@ describe('searchAfterAndBulkCreate', () => {
112122
},
113123
},
114124
];
115-
116125
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
117126
ruleParams: sampleParams,
118-
gap: null,
119-
previousStartedAt: new Date(),
127+
tuples,
120128
listClient,
121129
exceptionsList: [exceptionItem],
122130
services: mockService,
@@ -147,7 +155,6 @@ describe('searchAfterAndBulkCreate', () => {
147155
});
148156

149157
test('should return success with number of searches less than max signals with gap', async () => {
150-
const sampleParams = sampleRuleAlertParams(30);
151158
mockService.callCluster
152159
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3)))
153160
.mockResolvedValueOnce({
@@ -201,8 +208,7 @@ describe('searchAfterAndBulkCreate', () => {
201208
];
202209
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
203210
ruleParams: sampleParams,
204-
gap: moment.duration(2, 'm'),
205-
previousStartedAt: moment().subtract(10, 'm').toDate(),
211+
tuples,
206212
listClient,
207213
exceptionsList: [exceptionItem],
208214
services: mockService,
@@ -233,7 +239,6 @@ describe('searchAfterAndBulkCreate', () => {
233239
});
234240

235241
test('should return success when no search results are in the allowlist', async () => {
236-
const sampleParams = sampleRuleAlertParams(30);
237242
mockService.callCluster
238243
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3)))
239244
.mockResolvedValueOnce({
@@ -278,8 +283,7 @@ describe('searchAfterAndBulkCreate', () => {
278283
];
279284
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
280285
ruleParams: sampleParams,
281-
gap: null,
282-
previousStartedAt: new Date(),
286+
tuples,
283287
listClient,
284288
exceptionsList: [exceptionItem],
285289
services: mockService,
@@ -305,7 +309,7 @@ describe('searchAfterAndBulkCreate', () => {
305309
});
306310
expect(success).toEqual(true);
307311
expect(mockService.callCluster).toHaveBeenCalledTimes(3);
308-
expect(createdSignalsCount).toEqual(4); // should not create any signals because all events were in the allowlist
312+
expect(createdSignalsCount).toEqual(4);
309313
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
310314
});
311315

@@ -316,7 +320,6 @@ describe('searchAfterAndBulkCreate', () => {
316320
{ ...getSearchListItemResponseMock(), value: ['3.3.3.3'] },
317321
];
318322
listClient.searchListItemByValues = jest.fn().mockResolvedValue(searchListItems);
319-
const sampleParams = sampleRuleAlertParams(30);
320323
mockService.callCluster
321324
.mockResolvedValueOnce(
322325
repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3), [
@@ -342,8 +345,7 @@ describe('searchAfterAndBulkCreate', () => {
342345
];
343346
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
344347
ruleParams: sampleParams,
345-
gap: null,
346-
previousStartedAt: new Date(),
348+
tuples,
347349
listClient,
348350
exceptionsList: [exceptionItem],
349351
services: mockService,
@@ -382,7 +384,6 @@ describe('searchAfterAndBulkCreate', () => {
382384
];
383385

384386
listClient.searchListItemByValues = jest.fn().mockResolvedValue(searchListItems);
385-
const sampleParams = sampleRuleAlertParams(30);
386387
mockService.callCluster.mockResolvedValueOnce(
387388
repeatedSearchResultsWithNoSortId(4, 4, someGuids.slice(0, 3), [
388389
'1.1.1.1',
@@ -406,8 +407,7 @@ describe('searchAfterAndBulkCreate', () => {
406407
];
407408
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
408409
ruleParams: sampleParams,
409-
gap: null,
410-
previousStartedAt: new Date(),
410+
tuples,
411411
listClient,
412412
exceptionsList: [exceptionItem],
413413
services: mockService,
@@ -437,13 +437,12 @@ describe('searchAfterAndBulkCreate', () => {
437437
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
438438
// I don't like testing log statements since logs change but this is the best
439439
// way I can think of to ensure this section is getting hit with this test case.
440-
expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[8][0]).toContain(
440+
expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[7][0]).toContain(
441441
'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"'
442442
);
443443
});
444444

445445
test('should return success when no sortId present but search results are in the allowlist', async () => {
446-
const sampleParams = sampleRuleAlertParams(30);
447446
mockService.callCluster
448447
.mockResolvedValueOnce(repeatedSearchResultsWithNoSortId(4, 4, someGuids.slice(0, 3)))
449448
.mockResolvedValueOnce({
@@ -487,8 +486,7 @@ describe('searchAfterAndBulkCreate', () => {
487486
];
488487
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
489488
ruleParams: sampleParams,
490-
gap: null,
491-
previousStartedAt: new Date(),
489+
tuples,
492490
listClient,
493491
exceptionsList: [exceptionItem],
494492
services: mockService,
@@ -514,17 +512,16 @@ describe('searchAfterAndBulkCreate', () => {
514512
});
515513
expect(success).toEqual(true);
516514
expect(mockService.callCluster).toHaveBeenCalledTimes(2);
517-
expect(createdSignalsCount).toEqual(4); // should not create any signals because all events were in the allowlist
515+
expect(createdSignalsCount).toEqual(4);
518516
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
519517
// I don't like testing log statements since logs change but this is the best
520518
// way I can think of to ensure this section is getting hit with this test case.
521-
expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[15][0]).toContain(
519+
expect(((mockLogger.debug as unknown) as jest.Mock).mock.calls[14][0]).toContain(
522520
'ran out of sort ids to sort on name: "fake name" id: "fake id" rule id: "fake rule id" signals index: "fakeindex"'
523521
);
524522
});
525523

526524
test('should return success when no exceptions list provided', async () => {
527-
const sampleParams = sampleRuleAlertParams(30);
528525
mockService.callCluster
529526
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3)))
530527
.mockResolvedValueOnce({
@@ -565,8 +562,7 @@ describe('searchAfterAndBulkCreate', () => {
565562
);
566563
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
567564
ruleParams: sampleParams,
568-
gap: null,
569-
previousStartedAt: new Date(),
565+
tuples,
570566
listClient,
571567
exceptionsList: [],
572568
services: mockService,
@@ -592,7 +588,7 @@ describe('searchAfterAndBulkCreate', () => {
592588
});
593589
expect(success).toEqual(true);
594590
expect(mockService.callCluster).toHaveBeenCalledTimes(3);
595-
expect(createdSignalsCount).toEqual(4); // should not create any signals because all events were in the allowlist
591+
expect(createdSignalsCount).toEqual(4);
596592
expect(lastLookBackDate).toEqual(new Date('2020-04-20T21:27:45+0000'));
597593
});
598594

@@ -609,15 +605,13 @@ describe('searchAfterAndBulkCreate', () => {
609605
},
610606
},
611607
];
612-
const sampleParams = sampleRuleAlertParams(10);
613608
mockService.callCluster
614609
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3)))
615610
.mockRejectedValue(new Error('bulk failed')); // Added this recently
616611
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
617612
listClient,
618613
exceptionsList: [exceptionItem],
619-
gap: null,
620-
previousStartedAt: new Date(),
614+
tuples,
621615
ruleParams: sampleParams,
622616
services: mockService,
623617
logger: mockLogger,
@@ -659,7 +653,6 @@ describe('searchAfterAndBulkCreate', () => {
659653
},
660654
},
661655
];
662-
const sampleParams = sampleRuleAlertParams(30);
663656
mockService.callCluster.mockResolvedValueOnce(sampleEmptyDocSearchResults());
664657
listClient.searchListItemByValues = jest.fn(({ value }) =>
665658
Promise.resolve(
@@ -672,8 +665,7 @@ describe('searchAfterAndBulkCreate', () => {
672665
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
673666
listClient,
674667
exceptionsList: [exceptionItem],
675-
gap: null,
676-
previousStartedAt: new Date(),
668+
tuples,
677669
ruleParams: sampleParams,
678670
services: mockService,
679671
logger: mockLogger,
@@ -702,7 +694,6 @@ describe('searchAfterAndBulkCreate', () => {
702694
});
703695

704696
test('if returns false when singleSearchAfter throws an exception', async () => {
705-
const sampleParams = sampleRuleAlertParams(10);
706697
mockService.callCluster
707698
.mockResolvedValueOnce({
708699
took: 100,
@@ -741,8 +732,7 @@ describe('searchAfterAndBulkCreate', () => {
741732
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
742733
listClient,
743734
exceptionsList: [exceptionItem],
744-
gap: null,
745-
previousStartedAt: new Date(),
735+
tuples,
746736
ruleParams: sampleParams,
747737
services: mockService,
748738
logger: mockLogger,
@@ -771,7 +761,6 @@ describe('searchAfterAndBulkCreate', () => {
771761
});
772762

773763
test('it returns error array when singleSearchAfter returns errors', async () => {
774-
const sampleParams = sampleRuleAlertParams(30);
775764
const bulkItem: BulkResponse = {
776765
took: 100,
777766
errors: true,
@@ -832,16 +821,14 @@ describe('searchAfterAndBulkCreate', () => {
832821
],
833822
})
834823
.mockResolvedValueOnce(sampleDocSearchResultsNoSortIdNoHits());
835-
836824
const {
837825
success,
838826
createdSignalsCount,
839827
lastLookBackDate,
840828
errors,
841829
} = await searchAfterAndBulkCreate({
842830
ruleParams: sampleParams,
843-
gap: null,
844-
previousStartedAt: new Date(),
831+
tuples,
845832
listClient,
846833
exceptionsList: [],
847834
services: mockService,
@@ -873,7 +860,6 @@ describe('searchAfterAndBulkCreate', () => {
873860
});
874861

875862
it('invokes the enrichment callback with signal search results', async () => {
876-
const sampleParams = sampleRuleAlertParams(30);
877863
mockService.callCluster
878864
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3)))
879865
.mockResolvedValueOnce({
@@ -917,8 +903,7 @@ describe('searchAfterAndBulkCreate', () => {
917903
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
918904
enrichment: mockEnrichment,
919905
ruleParams: sampleParams,
920-
gap: moment.duration(2, 'm'),
921-
previousStartedAt: moment().subtract(10, 'm').toDate(),
906+
tuples,
922907
listClient,
923908
exceptionsList: [],
924909
services: mockService,

x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ import {
1717
createSearchResultReturnType,
1818
createSearchAfterReturnTypeFromResponse,
1919
createTotalHitsFromSearchResult,
20-
getSignalTimeTuples,
2120
mergeReturns,
2221
mergeSearchResults,
2322
} from './utils';
2423
import { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType } from './types';
2524

2625
// search_after through documents and re-index using bulk endpoint.
2726
export const searchAfterAndBulkCreate = async ({
28-
gap,
29-
previousStartedAt,
27+
tuples: totalToFromTuples,
3028
ruleParams,
3129
exceptionsList,
3230
services,
@@ -64,16 +62,6 @@ export const searchAfterAndBulkCreate = async ({
6462
// to ensure we don't exceed maxSignals
6563
let signalsCreatedCount = 0;
6664

67-
const totalToFromTuples = getSignalTimeTuples({
68-
logger,
69-
ruleParamsFrom: ruleParams.from,
70-
ruleParamsTo: ruleParams.to,
71-
ruleParamsMaxSignals: ruleParams.maxSignals,
72-
gap,
73-
previousStartedAt,
74-
interval,
75-
buildRuleMessage,
76-
});
7765
const tuplesToBeLogged = [...totalToFromTuples];
7866
logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`));
7967

0 commit comments

Comments
 (0)