Skip to content

Commit

Permalink
[SecuritySolution] Fixes for alert ids in the alert flyout (elastic#1…
Browse files Browse the repository at this point in the history
…26490)

* fix: show dns.question.name for network events

There are no events with event.category == 'dns'. DNS events are actually 'network' events with dns.network == 'dns'.
To simplify the field logic, we'll show the 'dns.question.name' field for all network events.

* fix: show threat_match fields

For newly-generated events, the threat indicator values can be found under 'kibana.alert.rule.parameters`.  This adds a temporary fix for that.

* fix: use legacy fields where necessary

Threat match and ML alert fields of migrated alerts are not migrated into the alert rule's parameters. We therefore read them by their legacy id.

* fix: remove unused import

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
2 people authored and lucasfcosta committed Mar 8, 2022
1 parent a5afa92 commit 917f8e9
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 13 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/security_solution/common/ecs/event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export enum EventCategory {
PROCESS = 'process',
FILE = 'file',
NETWORK = 'network',
DNS = 'dns',
REGISTRY = 'registry',
MALWARE = 'malware',
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,16 @@ describe('AlertSummaryView', () => {
});
});

test('DNS event renders the correct summary rows', () => {
test('DNS network event renders the correct summary rows', () => {
const renderProps = {
...props,
data: [
...(mockAlertDetailsData.map((item) => {
if (item.category === 'event' && item.field === 'event.category') {
return {
...item,
values: ['dns'],
originalValue: ['dns'],
values: ['network'],
originalValue: ['network'],
};
}
return item;
Expand Down Expand Up @@ -324,6 +324,39 @@ describe('AlertSummaryView', () => {
});
});

test('[legacy] Machine learning events show correct fields', () => {
const enhancedData = [
...mockAlertDetailsData.map((item) => {
if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') {
return {
...item,
values: ['machine_learning'],
originalValue: ['machine_learning'],
};
}
return item;
}),
{
category: 'signal',
field: 'signal.rule.machine_learning_job_id',
values: ['i_am_the_ml_job_id'],
},
{ category: 'signal', field: 'signal.rule.anomaly_threshold', values: [2] },
] as TimelineEventsDetailsItem[];
const renderProps = {
...props,
data: enhancedData,
};
const { getByText } = render(
<TestProvidersComponent>
<AlertSummaryView {...renderProps} />
</TestProvidersComponent>
);
['i_am_the_ml_job_id', 'signal.rule.anomaly_threshold'].forEach((fieldId) => {
expect(getByText(fieldId));
});
});

test('Threat match events show correct fields', () => {
const enhancedData = [
...mockAlertDetailsData.map((item) => {
Expand All @@ -338,10 +371,51 @@ describe('AlertSummaryView', () => {
}),
{
category: 'kibana',
field: 'kibana.alert.rule.threat_index',
field: 'kibana.alert.rule.parameters.threat_index',
values: ['threat_index*'],
},
{
category: 'kibana',
field: 'kibana.alert.rule.parameters.threat_query',
values: ['*query*'],
},
] as TimelineEventsDetailsItem[];
const renderProps = {
...props,
data: enhancedData,
};
const { getByText } = render(
<TestProvidersComponent>
<AlertSummaryView {...renderProps} />
</TestProvidersComponent>
);
['threat_index*', '*query*'].forEach((fieldId) => {
expect(getByText(fieldId));
});
});

test('[legacy] Threat match events show correct fields', () => {
const enhancedData = [
...mockAlertDetailsData.map((item) => {
if (item.category === 'kibana' && item.field === 'kibana.alert.rule.type') {
return {
...item,
values: ['threat_match'],
originalValue: ['threat_match'],
};
}
return item;
}),
{
category: 'signal',
field: 'signal.rule.threat_index',
values: ['threat_index*'],
},
{ category: 'kibana', field: 'kibana.alert.rule.threat_query', values: ['*query*'] },
{
category: 'signal',
field: 'signal.rule.threat_query',
values: ['*query*'],
},
] as TimelineEventsDetailsItem[];
const renderProps = {
...props,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { find, isEmpty, uniqBy } from 'lodash/fp';
import { ALERT_RULE_NAMESPACE, ALERT_RULE_PARAMETERS, ALERT_RULE_TYPE } from '@kbn/rule-data-utils';
import { ALERT_RULE_PARAMETERS, ALERT_RULE_TYPE } from '@kbn/rule-data-utils';

import * as i18n from './translations';
import { BrowserFields } from '../../../../common/search_strategy/index_fields';
Expand Down Expand Up @@ -62,10 +62,9 @@ function getFieldsByCategory({
{ id: 'destination.port' },
{ id: 'source.address' },
{ id: 'source.port' },
{ id: 'dns.question.name' },
{ id: 'process.name' },
];
case EventCategory.DNS:
return [{ id: 'dns.question.name' }, { id: 'process.name' }];
case EventCategory.REGISTRY:
return [{ id: 'registry.key' }, { id: 'registry.value' }, { id: 'process.name' }];
case EventCategory.MALWARE:
Expand Down Expand Up @@ -146,18 +145,22 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] {
return [
{
id: `${ALERT_RULE_PARAMETERS}.machine_learning_job_id`,
legacyId: 'signal.rule.machine_learning_job_id',
},
{
id: `${ALERT_RULE_PARAMETERS}.anomaly_threshold`,
legacyId: 'signal.rule.anomaly_threshold',
},
];
case 'threat_match':
return [
{
id: `${ALERT_RULE_NAMESPACE}.threat_index`,
id: `${ALERT_RULE_PARAMETERS}.threat_index`,
legacyId: 'signal.rule.threat_index',
},
{
id: `${ALERT_RULE_NAMESPACE}.threat_query`,
id: `${ALERT_RULE_PARAMETERS}.threat_query`,
legacyId: 'signal.rule.threat_query',
},
];
default:
Expand Down Expand Up @@ -251,11 +254,18 @@ export const getSummaryRows = ({

return data != null
? tableFields.reduce<SummaryRow[]>((acc, field) => {
const item = data.find((d) => d.field === field.id);
if (!item || isEmpty(item?.values)) {
const item = data.find(
(d) => d.field === field.id || (field.legacyId && d.field === field.legacyId)
);
if (!item || isEmpty(item.values)) {
return acc;
}

// If we found the data by its legacy id we swap the ids to display the correct one
if (item.field === field.legacyId) {
field.id = field.legacyId;
}

const linkValueField =
field.linkField != null && data.find((d) => d.field === field.linkField);
const description = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type EnrichedFieldInfoWithValues = EnrichedFieldInfo & { values: string[]

export interface EventSummaryField {
id: string;
legacyId?: string;
label?: string;
linkField?: string;
fieldType?: string;
Expand Down

0 comments on commit 917f8e9

Please sign in to comment.