[SecuritySolution][Fix] Reasonformatter fix for threshold alerts. (#138909) (#139106)

## Issue

Fixes #137435

Reason formatter was not when working correctly in case of threshold alerts. In an ideal scenario, if threshold alert is grouped by a field, say `user.name` then that field should be available in the reason message.

It was missing as per the bug.

## Solution

For threshold alerts, `mergedDoc` in results in a different shape of object where the fields are contained in `_source` key instead of `fields` key.  This was corrected so that if `fields` key is not available in the `mergedDoc`, `_source` will act as the fallback key.

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

(cherry picked from commit ac0635bd9d)

Co-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>
This commit is contained in:
Kibana Machine 2022-08-18 12:19:11 -04:00 committed by GitHub
parent 8ce6bcf8ef
commit 9e1eb0ed84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 35 deletions

View file

@ -5,39 +5,50 @@
* 2.0.
*/
import { buildReasonMessageUtil } from './reason_formatters';
import type { SignalSourceHit } from './types';
import { buildReasonMessageForThresholdAlert, buildReasonMessageUtil } from './reason_formatters';
const mergedDoc = {
_index: 'index-1',
_id: 'id-1',
fields: {
'destination.address': ['9.99.99.9'],
'destination.port': ['6789'],
'event.category': ['test'],
'file.name': ['sample'],
'host.name': ['host'],
'process.name': ['doingThings.exe'],
'process.parent.name': ['didThings.exe'],
'source.address': ['1.11.11.1'],
'source.port': ['1234'],
'user.name': ['test-user'],
'@timestamp': '2021-08-11T02:28:59.101Z',
},
};
const genThresholdMergedDoc = (groupedKeys: Record<string, string>) => ({
_index: 'index-1',
_id: 'some-id',
_source: {
'@timestamp': '2022-08-16T11:01:09.848Z',
threshold_result: [Object],
...groupedKeys,
},
});
describe('reason_formatter', () => {
let name: string;
let ruleName: string;
let severity: string;
let mergedDoc: SignalSourceHit;
beforeAll(() => {
name = 'my-rule';
ruleName = 'my-rule';
severity = 'medium';
mergedDoc = {
_index: 'index-1',
_id: 'id-1',
fields: {
'destination.address': ['9.99.99.9'],
'destination.port': ['6789'],
'event.category': ['test'],
'file.name': ['sample'],
'host.name': ['host'],
'process.name': ['doingThings.exe'],
'process.parent.name': ['didThings.exe'],
'source.address': ['1.11.11.1'],
'source.port': ['1234'],
'user.name': ['test-user'],
'@timestamp': '2021-08-11T02:28:59.101Z',
},
};
});
describe('buildReasonMessageUtil', () => {
describe('when rule and mergedDoc are provided', () => {
it('should return the full reason message', () => {
expect(buildReasonMessageUtil({ name, severity, mergedDoc })).toMatchInlineSnapshot(
expect(
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, destination 9.99.99.9:6789, by test-user on host created medium alert my-rule."`
);
});
@ -52,7 +63,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: updatedMergedDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: updatedMergedDoc })
).toMatchInlineSnapshot(
`"item one, item two event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, destination 9.99.99.9:6789, by test-user on host created medium alert my-rule."`
);
@ -68,7 +79,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: updatedMergedDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: updatedMergedDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, destination 9.99.99.9:6789, by test-user created medium alert my-rule."`
);
@ -84,7 +95,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: updatedMergedDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: updatedMergedDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, destination 9.99.99.9:6789, on host created medium alert my-rule."`
);
@ -100,7 +111,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: noDestinationPortDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: noDestinationPortDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, destination 9.99.99.9 by test-user on host created medium alert my-rule."`
);
@ -115,7 +126,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: noDestinationPortDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: noDestinationPortDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1:1234, by test-user on host created medium alert my-rule."`
);
@ -131,7 +142,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: noSourcePortDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: noSourcePortDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, source 1.11.11.1 destination 9.99.99.9:6789, by test-user on host created medium alert my-rule."`
);
@ -146,7 +157,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: noSourcePortDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: noSourcePortDoc })
).toMatchInlineSnapshot(
`"test event with process doingThings.exe, parent process didThings.exe, file sample, destination 9.99.99.9:6789, by test-user on host created medium alert my-rule."`
);
@ -163,7 +174,7 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: updatedMergedDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: updatedMergedDoc })
).toMatchInlineSnapshot(
`"test event with file sample, source 1.11.11.1:1234, destination 9.99.99.9:6789, by test-user on host created medium alert my-rule."`
);
@ -180,14 +191,61 @@ describe('reason_formatter', () => {
},
};
expect(
buildReasonMessageUtil({ name, severity, mergedDoc: updatedMergedDoc })
buildReasonMessageUtil({ name: ruleName, severity, mergedDoc: updatedMergedDoc })
).toMatchInlineSnapshot(`"test event by test-user created medium alert my-rule."`);
});
});
describe('when only rule is provided', () => {
it('should return the reason message without host name or user name', () => {
expect(buildReasonMessageUtil({ name, severity })).toMatchInlineSnapshot(`""`);
expect(buildReasonMessageUtil({ name: ruleName, severity })).toMatchInlineSnapshot(`""`);
});
});
});
describe(`buildReasonMessageForThresholdAlert`, () => {
it('When thresold rule is grouped by user.name', () => {
const userName = 'Some User Name';
const thresholdMergedDoc = genThresholdMergedDoc({
'user.name': userName,
});
expect(
buildReasonMessageForThresholdAlert({
name: ruleName,
severity,
mergedDoc: thresholdMergedDoc,
})
).toEqual(`event by ${userName} created ${severity} alert ${ruleName}.`);
});
it('When threshold rule is grouped by host.name', () => {
const hostName = 'Some Host Name';
const thresholdMergedDoc = genThresholdMergedDoc({
'host.name': hostName,
});
expect(
buildReasonMessageForThresholdAlert({
name: ruleName,
severity,
mergedDoc: thresholdMergedDoc,
})
).toEqual(`event on ${hostName} created ${severity} alert ${ruleName}.`);
});
it('When threshold rule is grouped by host.name and user.name', () => {
const hostName = 'Some Host Name';
const userName = 'Some User Name';
const thresholdMergedDoc = genThresholdMergedDoc({
'host.name': hostName,
'user.name': userName,
});
expect(
buildReasonMessageForThresholdAlert({
name: ruleName,
severity,
mergedDoc: thresholdMergedDoc,
})
).toEqual(`event by ${userName} on ${hostName} created ${severity} alert ${ruleName}.`);
});
});
});

View file

@ -35,7 +35,7 @@ interface ReasonFields {
}
const getFieldsFromDoc = (mergedDoc: SignalSourceHit) => {
const reasonFields: ReasonFields = {};
const docToUse = mergedDoc?.fields || mergedDoc;
const docToUse = mergedDoc?.fields || mergedDoc?._source || mergedDoc;
reasonFields.destinationAddress = getOr(null, 'destination.address', docToUse);
reasonFields.destinationPort = getOr(null, 'destination.port', docToUse);

View file

@ -1019,7 +1019,7 @@ export default ({ getService }: FtrProviderContext) => {
},
],
[ALERT_WORKFLOW_STATUS]: 'open',
[ALERT_REASON]: `event created high alert Signal Testing Query.`,
[ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`,
[ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID],
[ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME],
[ALERT_DEPTH]: 1,