Skip to content

Commit d9554ff

Browse files
[SIEM][Detection Engine] Silence 409 errors on signal creation (#53859)
* Remove punctuation from translation We already had a colon on both uses of this key, resulting in '::' on the page. * Ignore 409 errors from our signal creation In my experience these are always due to a rule being run multiple times on the same document, generating a duplicate signal with a (correctly) duplicate id. Only if we encounter non-409 errors do we log a message to the user. * Hide 409 errors during signal creation These are expected and potentially confusing to the user. Instead, we only show unexpected errors. Co-authored-by: Elastic Machine <[email protected]>
1 parent 47e5342 commit d9554ff

File tree

4 files changed

+71
-31
lines changed

4 files changed

+71
-31
lines changed

x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle',
1111
});
1212

1313
export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', {
14-
defaultMessage: 'Last signal:',
14+
defaultMessage: 'Last signal',
1515
});
1616

1717
export const TOTAL_SIGNAL = i18n.translate('xpack.siem.detectionEngine.totalSignalTitle', {

x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,28 @@ export const sampleBulkCreateDuplicateResult = {
144144
],
145145
};
146146

147+
export const sampleBulkCreateErrorResult = {
148+
...sampleBulkCreateDuplicateResult,
149+
items: [
150+
...sampleBulkCreateDuplicateResult.items,
151+
{
152+
create: {
153+
_index: 'test',
154+
_type: '_doc',
155+
_id: '5',
156+
status: 500,
157+
error: {
158+
type: 'internal_server_error',
159+
reason: '[4]: internal server error',
160+
index_uuid: 'cXmq4Rt3RGGswDTTwZFzvA',
161+
shard: '0',
162+
index: 'test',
163+
},
164+
},
165+
},
166+
],
167+
};
168+
147169
export const sampleDocSearchResultsNoSortId = (
148170
someUuid: string = sampleIdGuid
149171
): SignalSearchResponse => ({

x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
sampleDocSearchResultsNoSortIdNoVersion,
1414
sampleEmptyDocSearchResults,
1515
sampleBulkCreateDuplicateResult,
16+
sampleBulkCreateErrorResult,
1617
} from './__mocks__/es_results';
1718
import { savedObjectsClientMock } from 'src/core/server/mocks';
1819
import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants';
@@ -206,7 +207,8 @@ describe('singleBulkCreate', () => {
206207
});
207208
expect(successfulsingleBulkCreate).toEqual(true);
208209
});
209-
test('create successful bulk create when bulk create has errors', async () => {
210+
211+
test('create successful bulk create when bulk create has duplicate errors', async () => {
210212
const sampleParams = sampleRuleAlertParams();
211213
const sampleSearchResult = sampleDocSearchResultsNoSortId;
212214
mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult);
@@ -224,6 +226,30 @@ describe('singleBulkCreate', () => {
224226
enabled: true,
225227
tags: ['some fake tag 1', 'some fake tag 2'],
226228
});
229+
230+
expect(mockLogger.error).not.toHaveBeenCalled();
231+
expect(successfulsingleBulkCreate).toEqual(true);
232+
});
233+
234+
test('create successful bulk create when bulk create has multiple error statuses', async () => {
235+
const sampleParams = sampleRuleAlertParams();
236+
const sampleSearchResult = sampleDocSearchResultsNoSortId;
237+
mockService.callCluster.mockReturnValue(sampleBulkCreateErrorResult);
238+
const successfulsingleBulkCreate = await singleBulkCreate({
239+
someResult: sampleSearchResult(),
240+
ruleParams: sampleParams,
241+
services: mockService,
242+
logger: mockLogger,
243+
id: sampleRuleGuid,
244+
signalsIndex: DEFAULT_SIGNALS_INDEX,
245+
name: 'rule-name',
246+
createdBy: 'elastic',
247+
updatedBy: 'elastic',
248+
interval: '5m',
249+
enabled: true,
250+
tags: ['some fake tag 1', 'some fake tag 2'],
251+
});
252+
227253
expect(mockLogger.error).toHaveBeenCalled();
228254
expect(successfulsingleBulkCreate).toEqual(true);
229255
});

x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import { countBy, isEmpty } from 'lodash';
78
import { performance } from 'perf_hooks';
89
import { AlertServices } from '../../../../../alerting/server/types';
910
import { SignalSearchResponse, BulkResponse } from './types';
@@ -68,39 +69,30 @@ export const singleBulkCreate = async ({
6869
},
6970
buildBulkBody({ doc, ruleParams, id, name, createdBy, updatedBy, interval, enabled, tags }),
7071
]);
71-
const time1 = performance.now();
72-
const firstResult: BulkResponse = await services.callCluster('bulk', {
72+
const start = performance.now();
73+
const response: BulkResponse = await services.callCluster('bulk', {
7374
index: signalsIndex,
7475
refresh: false,
7576
body: bulkBody,
7677
});
77-
const time2 = performance.now();
78-
logger.debug(
79-
`individual bulk process time took: ${Number(time2 - time1).toFixed(2)} milliseconds`
80-
);
81-
logger.debug(`took property says bulk took: ${firstResult.took} milliseconds`);
82-
if (firstResult.errors) {
83-
// go through the response status errors and see what
84-
// types of errors they are, count them up, and log them.
85-
const errorCountMap = firstResult.items.reduce((acc: { [key: string]: number }, item) => {
86-
if (item.create.error) {
87-
const responseStatusKey = item.create.status.toString();
88-
acc[responseStatusKey] = acc[responseStatusKey] ? acc[responseStatusKey] + 1 : 1;
89-
}
90-
return acc;
91-
}, {});
92-
/*
93-
the logging output below should look like
94-
{'409': 55}
95-
which is read as "there were 55 counts of 409 errors returned from bulk create"
96-
*/
97-
logger.error(
98-
`[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify(
99-
errorCountMap,
100-
null,
101-
2
102-
)}`
103-
);
78+
const end = performance.now();
79+
logger.debug(`individual bulk process time took: ${Number(end - start).toFixed(2)} milliseconds`);
80+
logger.debug(`took property says bulk took: ${response.took} milliseconds`);
81+
82+
if (response.errors) {
83+
const itemsWithErrors = response.items.filter(item => item.create.error);
84+
const errorCountsByStatus = countBy(itemsWithErrors, item => item.create.status);
85+
delete errorCountsByStatus['409']; // Duplicate signals are expected
86+
87+
if (!isEmpty(errorCountsByStatus)) {
88+
logger.error(
89+
`[-] bulkResponse had errors with response statuses:counts of...\n${JSON.stringify(
90+
errorCountsByStatus,
91+
null,
92+
2
93+
)}`
94+
);
95+
}
10496
}
10597
return true;
10698
};

0 commit comments

Comments
 (0)