Skip to content

Commit b5f0bc9

Browse files
authored
[Security Solution] adds wrapSequences method (RAC) (#102106)
adds wrapSequences method
1 parent 00c1807 commit b5f0bc9

11 files changed

Lines changed: 107 additions & 52 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ describe('eql_executor', () => {
7878
logger,
7979
searchAfterSize,
8080
bulkCreate: jest.fn(),
81+
wrapHits: jest.fn(),
82+
wrapSequences: jest.fn(),
8183
});
8284
expect(response.warningMessages.length).toEqual(1);
8385
});

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

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,19 @@ import { isOutdated } from '../../migrations/helpers';
2121
import { getIndexVersion } from '../../routes/index/get_index_version';
2222
import { MIN_EQL_RULE_INDEX_VERSION } from '../../routes/index/get_signals_template';
2323
import { EqlRuleParams } from '../../schemas/rule_schemas';
24-
import { buildSignalFromEvent, buildSignalGroupFromSequence } from '../build_bulk_body';
2524
import { getInputIndex } from '../get_input_output_index';
26-
import { filterDuplicateSignals } from '../filter_duplicate_signals';
25+
2726
import {
2827
AlertAttributes,
2928
BulkCreate,
29+
WrapHits,
30+
WrapSequences,
3031
EqlSignalSearchResponse,
3132
RuleRangeTuple,
3233
SearchAfterAndBulkCreateReturnType,
33-
WrappedSignalHit,
34+
SimpleHit,
3435
} from '../types';
35-
import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../utils';
36+
import { createSearchAfterReturnType, makeFloatString } from '../utils';
3637

3738
export const eqlExecutor = async ({
3839
rule,
@@ -43,6 +44,8 @@ export const eqlExecutor = async ({
4344
logger,
4445
searchAfterSize,
4546
bulkCreate,
47+
wrapHits,
48+
wrapSequences,
4649
}: {
4750
rule: SavedObject<AlertAttributes<EqlRuleParams>>;
4851
tuple: RuleRangeTuple;
@@ -52,6 +55,8 @@ export const eqlExecutor = async ({
5255
logger: Logger;
5356
searchAfterSize: number;
5457
bulkCreate: BulkCreate;
58+
wrapHits: WrapHits;
59+
wrapSequences: WrapSequences;
5560
}): Promise<SearchAfterAndBulkCreateReturnType> => {
5661
const result = createSearchAfterReturnType();
5762
const ruleParams = rule.attributes.params;
@@ -104,27 +109,18 @@ export const eqlExecutor = async ({
104109
const eqlSignalSearchEnd = performance.now();
105110
const eqlSearchDuration = makeFloatString(eqlSignalSearchEnd - eqlSignalSearchStart);
106111
result.searchAfterTimes = [eqlSearchDuration];
107-
let newSignals: WrappedSignalHit[] | undefined;
112+
let newSignals: SimpleHit[] | undefined;
108113
if (response.hits.sequences !== undefined) {
109-
newSignals = response.hits.sequences.reduce(
110-
(acc: WrappedSignalHit[], sequence) =>
111-
acc.concat(buildSignalGroupFromSequence(sequence, rule, ruleParams.outputIndex)),
112-
[]
113-
);
114+
newSignals = wrapSequences(response.hits.sequences);
114115
} else if (response.hits.events !== undefined) {
115-
newSignals = filterDuplicateSignals(
116-
rule.id,
117-
response.hits.events.map((event) =>
118-
wrapSignal(buildSignalFromEvent(event, rule, true), ruleParams.outputIndex)
119-
)
120-
);
116+
newSignals = wrapHits(response.hits.events);
121117
} else {
122118
throw new Error(
123119
'eql query response should have either `sequences` or `events` but had neither'
124120
);
125121
}
126122

127-
if (newSignals.length > 0) {
123+
if (newSignals?.length) {
128124
const insertResult = await bulkCreate(newSignals);
129125
result.bulkCreateTimes.push(insertResult.bulkCreateDuration);
130126
result.createdSignalsCount += insertResult.createdItemsCount;

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ const mockSignals = [
3636
];
3737

3838
describe('filterDuplicateSignals', () => {
39-
it('filters duplicate signals', () => {
40-
expect(filterDuplicateSignals(mockRuleId1, mockSignals).length).toEqual(1);
41-
});
39+
describe('detection engine implementation', () => {
40+
it('filters duplicate signals', () => {
41+
expect(filterDuplicateSignals(mockRuleId1, mockSignals, false).length).toEqual(1);
42+
});
4243

43-
it('does not filter non-duplicate signals', () => {
44-
expect(filterDuplicateSignals(mockRuleId3, mockSignals).length).toEqual(2);
44+
it('does not filter non-duplicate signals', () => {
45+
expect(filterDuplicateSignals(mockRuleId3, mockSignals, false).length).toEqual(2);
46+
});
4547
});
4648
});

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,26 @@
55
* 2.0.
66
*/
77

8-
import { WrappedSignalHit } from './types';
8+
import { SimpleHit, WrappedSignalHit } from './types';
99

10-
export const filterDuplicateSignals = (ruleId: string, signals: WrappedSignalHit[]) => {
11-
return signals.filter(
12-
(doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId)
13-
);
10+
const isWrappedSignalHit = (
11+
signals: SimpleHit[],
12+
isRuleRegistryEnabled: boolean
13+
): signals is WrappedSignalHit[] => {
14+
return !isRuleRegistryEnabled;
15+
};
16+
17+
export const filterDuplicateSignals = (
18+
ruleId: string,
19+
signals: SimpleHit[],
20+
isRuleRegistryEnabled: boolean
21+
) => {
22+
if (isWrappedSignalHit(signals, isRuleRegistryEnabled)) {
23+
return signals.filter(
24+
(doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId)
25+
);
26+
} else {
27+
// TODO: filter duplicate signals for RAC
28+
return [];
29+
}
1430
};

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ describe('searchAfterAndBulkCreate', () => {
6666
buildRuleMessage,
6767
false
6868
);
69-
wrapHits = wrapHitsFactory({ ruleSO, signalsIndex: DEFAULT_SIGNALS_INDEX });
69+
wrapHits = wrapHitsFactory({
70+
ruleSO,
71+
signalsIndex: DEFAULT_SIGNALS_INDEX,
72+
});
7073
});
7174

7275
test('should return success with number of searches less than max signals', async () => {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
} from '../schemas/rule_schemas';
6868
import { bulkCreateFactory } from './bulk_create_factory';
6969
import { wrapHitsFactory } from './wrap_hits_factory';
70+
import { wrapSequencesFactory } from './wrap_sequences_factory';
7071

7172
export const signalRulesAlertType = ({
7273
logger,
@@ -233,6 +234,11 @@ export const signalRulesAlertType = ({
233234
signalsIndex: params.outputIndex,
234235
});
235236

237+
const wrapSequences = wrapSequencesFactory({
238+
ruleSO: savedObject,
239+
signalsIndex: params.outputIndex,
240+
});
241+
236242
if (isMlRule(type)) {
237243
const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams);
238244
for (const tuple of tuples) {
@@ -313,6 +319,8 @@ export const signalRulesAlertType = ({
313319
searchAfterSize,
314320
bulkCreate,
315321
logger,
322+
wrapHits,
323+
wrapSequences,
316324
});
317325
}
318326
} else {

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
BaseHit,
2626
RuleAlertAction,
2727
SearchTypes,
28+
EqlSequence,
2829
} from '../../../../common/detection_engine/types';
2930
import { ListClient } from '../../../../../lists/server';
3031
import { Logger, SavedObject } from '../../../../../../../src/core/server';
@@ -257,9 +258,11 @@ export type SignalsEnrichment = (signals: SignalSearchResponse) => Promise<Signa
257258

258259
export type BulkCreate = <T>(docs: Array<BaseHit<T>>) => Promise<GenericBulkCreateResponse<T>>;
259260

260-
export type WrapHits = (
261-
hits: Array<estypes.SearchHit<unknown>>
262-
) => Array<BaseHit<{ '@timestamp': string }>>;
261+
export type SimpleHit = BaseHit<{ '@timestamp': string }>;
262+
263+
export type WrapHits = (hits: Array<estypes.SearchHit<SignalSource>>) => SimpleHit[];
264+
265+
export type WrapSequences = (sequences: Array<EqlSequence<SignalSource>>) => SimpleHit[];
263266

264267
export interface SearchAfterAndBulkCreateParams {
265268
tuple: {

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

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

8-
import {
9-
SearchAfterAndBulkCreateParams,
10-
SignalSourceHit,
11-
WrapHits,
12-
WrappedSignalHit,
13-
} from './types';
8+
import { SearchAfterAndBulkCreateParams, WrapHits, WrappedSignalHit } from './types';
149
import { generateId } from './utils';
1510
import { buildBulkBody } from './build_bulk_body';
1611
import { filterDuplicateSignals } from './filter_duplicate_signals';
@@ -25,11 +20,15 @@ export const wrapHitsFactory = ({
2520
const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [
2621
{
2722
_index: signalsIndex,
28-
// TODO: bring back doc._version
29-
_id: generateId(doc._index, doc._id, '', ruleSO.attributes.params.ruleId ?? ''),
30-
_source: buildBulkBody(ruleSO, doc as SignalSourceHit),
23+
_id: generateId(
24+
doc._index,
25+
doc._id,
26+
String(doc._version),
27+
ruleSO.attributes.params.ruleId ?? ''
28+
),
29+
_source: buildBulkBody(ruleSO, doc),
3130
},
3231
]);
3332

34-
return filterDuplicateSignals(ruleSO.id, wrappedDocs);
33+
return filterDuplicateSignals(ruleSO.id, wrappedDocs, false);
3534
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { SearchAfterAndBulkCreateParams, WrappedSignalHit, WrapSequences } from './types';
9+
import { buildSignalGroupFromSequence } from './build_bulk_body';
10+
11+
export const wrapSequencesFactory = ({
12+
ruleSO,
13+
signalsIndex,
14+
}: {
15+
ruleSO: SearchAfterAndBulkCreateParams['ruleSO'];
16+
signalsIndex: string;
17+
}): WrapSequences => (sequences) =>
18+
sequences.reduce(
19+
(acc: WrappedSignalHit[], sequence) => [
20+
...acc,
21+
...buildSignalGroupFromSequence(sequence, ruleSO, signalsIndex),
22+
],
23+
[]
24+
);

x-pack/plugins/security_solution/server/plugin.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
208208
});
209209

210210
// TODO: Once we are past experimental phase this check can be removed along with legacy registration of rules
211+
const isRuleRegistryEnabled = experimentalFeatures.ruleRegistryEnabled;
212+
211213
let ruleDataClient: RuleDataClient | null = null;
212-
if (experimentalFeatures.ruleRegistryEnabled) {
214+
if (isRuleRegistryEnabled) {
213215
const { ruleDataService } = plugins.ruleRegistry;
214216
const start = () => core.getStartServices().then(([coreStart]) => coreStart);
215217

@@ -293,7 +295,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
293295
const ruleTypes = [
294296
SIGNALS_ID,
295297
NOTIFICATIONS_ID,
296-
...(experimentalFeatures.ruleRegistryEnabled ? referenceRuleTypes : []),
298+
...(isRuleRegistryEnabled ? referenceRuleTypes : []),
297299
];
298300

299301
plugins.features.registerKibanaFeature({

0 commit comments

Comments
 (0)