[Security Solution][Alerts] Fix bug when suppression has both created and updated alerts (#150236)

## Summary

A bug in the `alertWithSuppression` logic caused rules to fail (with
`cannot read property create of undefined`) when the rule found alerts
to both create and update. The bulk response items length is equal to
the number of updated + new alerts, but the `duplicateAlertUpdates`
array length is actually 2x the number of updated alerts because it has
an update instruction in addition to each updated alert.

The fix is simply to shift the index into the bulk response by
`duplicateAlerts.length` instead of `duplicateAlertUpdates.length`.
This commit is contained in:
Marshall Main 2023-02-07 11:54:56 -08:00 committed by GitHub
parent c8b75b3b72
commit d6f68c2440
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 2 deletions

View file

@ -329,14 +329,17 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
createdAlerts: augmentedAlerts
.map((alert, idx) => {
const responseItem =
bulkResponse.body.items[idx + duplicateAlertUpdates.length].create;
bulkResponse.body.items[idx + duplicateAlerts.length].create;
return {
_id: responseItem?._id ?? '',
_index: responseItem?._index ?? '',
...alert._source,
};
})
.filter((_, idx) => bulkResponse.body.items[idx].create?.status === 201),
.filter(
(_, idx) =>
bulkResponse.body.items[idx + duplicateAlerts.length].create?.status === 201
),
errors: errorAggregator(bulkResponse.body, [409]),
};
} else {

View file

@ -1285,6 +1285,95 @@ export default ({ getService }: FtrProviderContext) => {
});
});
it('should create and update alerts in the same rule run without errors', async () => {
const id = uuidv4();
const timestamp = '2020-10-28T06:00:00.000Z';
// agent-1 should create an alert on the first rule run, then the second rule run should update that
// alert and make a new alert for agent-2
const firstDoc = {
id,
'@timestamp': timestamp,
agent: {
name: 'agent-1',
},
};
const laterTimestamp = '2020-10-28T07:00:00.000Z';
const secondDoc = {
id,
'@timestamp': laterTimestamp,
agent: {
name: 'agent-1',
},
};
const thirdDoc = {
id,
'@timestamp': laterTimestamp,
agent: {
name: 'agent-2',
},
};
await indexDocuments([firstDoc, secondDoc, thirdDoc]);
const rule: QueryRuleCreateProps = {
...getRuleForSignalTesting(['ecs_compliant']),
query: `id:${id}`,
alert_suppression: {
group_by: ['agent.name'],
duration: {
value: 300,
unit: 'm',
},
},
from: 'now-1h',
interval: '1h',
timestamp_override: 'event.ingested',
max_signals: 150,
};
const { previewId, logs } = await previewRule({
supertest,
rule,
timeframeEnd: new Date('2020-10-28T07:30:00.000Z'),
invocationCount: 2,
});
const previewAlerts = await getPreviewAlerts({
es,
previewId,
size: 10,
sort: ['agent.name', ALERT_ORIGINAL_TIME],
});
expect(previewAlerts.length).to.eql(2);
expect(previewAlerts[0]._source).to.eql({
...previewAlerts[0]._source,
[ALERT_SUPPRESSION_TERMS]: [
{
field: 'agent.name',
value: 'agent-1',
},
],
[ALERT_ORIGINAL_TIME]: timestamp,
[ALERT_SUPPRESSION_START]: timestamp,
[ALERT_SUPPRESSION_END]: laterTimestamp,
[ALERT_SUPPRESSION_DOCS_COUNT]: 1,
});
expect(previewAlerts[1]._source).to.eql({
...previewAlerts[1]._source,
[ALERT_SUPPRESSION_TERMS]: [
{
field: 'agent.name',
value: 'agent-2',
},
],
[ALERT_ORIGINAL_TIME]: laterTimestamp,
[ALERT_SUPPRESSION_START]: laterTimestamp,
[ALERT_SUPPRESSION_END]: laterTimestamp,
[ALERT_SUPPRESSION_DOCS_COUNT]: 0,
});
for (const logEntry of logs) {
expect(logEntry.errors.length).to.eql(0);
}
});
describe('with host risk index', async () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk');