mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Open alerts with an associated template in the template view (#123333) (#123689)
* Open alerts with a template, with a template
* Add default values back instead of template derived ones
* Use data providers over filters always, set timeline description to alert id
* Remove prepopulated description from non threshold alerts
* Open any event in timeline, use correct timestamp
* Remove unneeded @timestamp, make sure alertsEcsData is not empty array
* Add basic getField tests
* Explicity check if alertGroupId is an array instead of using length
* Always use a valid date for time range
* Only use filter if more than 1 alert is present
* Possibly controversial change to calculate threshold time range with a template, fix test that should never have passed
* Create threshold timeline in separate function
* Use better type for createTimeline passed to createThresholdTimeline
* Invert negation as suggested in pr comment
* Use template timeline filters/query/data providers for threshold alerts
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit cef886f073
)
Co-authored-by: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com>
This commit is contained in:
parent
1b46b68464
commit
2325ed6a22
5 changed files with 264 additions and 127 deletions
|
@ -15,6 +15,7 @@ import {
|
|||
mockEcsDataWithAlert,
|
||||
mockTimelineDetails,
|
||||
mockTimelineResult,
|
||||
mockAADEcsDataWithAlert,
|
||||
} from '../../../common/mock/';
|
||||
import { CreateTimeline, UpdateTimelineLoading } from './types';
|
||||
import { Ecs } from '../../../../common/ecs';
|
||||
|
@ -268,6 +269,9 @@ describe('alert actions', () => {
|
|||
updateTimelineIsLoading,
|
||||
searchStrategyClient,
|
||||
});
|
||||
const defaultTimelinePropsWithoutNote = { ...defaultTimelineProps };
|
||||
|
||||
delete defaultTimelinePropsWithoutNote.ruleNote;
|
||||
|
||||
expect(updateTimelineIsLoading).toHaveBeenCalledWith({
|
||||
id: TimelineId.active,
|
||||
|
@ -278,7 +282,17 @@ describe('alert actions', () => {
|
|||
isLoading: false,
|
||||
});
|
||||
expect(createTimeline).toHaveBeenCalledTimes(1);
|
||||
expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps);
|
||||
expect(createTimeline).toHaveBeenCalledWith({
|
||||
...defaultTimelinePropsWithoutNote,
|
||||
timeline: {
|
||||
...defaultTimelinePropsWithoutNote.timeline,
|
||||
dataProviders: [],
|
||||
kqlQuery: {
|
||||
filterQuery: null,
|
||||
},
|
||||
resolveTimelineConfig: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -289,8 +303,7 @@ describe('alert actions', () => {
|
|||
signal: {
|
||||
rule: {
|
||||
...mockEcsDataWithAlert.signal?.rule,
|
||||
// @ts-expect-error
|
||||
timeline_id: null,
|
||||
timeline_id: [''],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -362,6 +375,7 @@ describe('alert actions', () => {
|
|||
...defaultTimelineProps,
|
||||
timeline: {
|
||||
...defaultTimelineProps.timeline,
|
||||
resolveTimelineConfig: undefined,
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
|
@ -424,14 +438,53 @@ describe('alert actions', () => {
|
|||
});
|
||||
|
||||
test('it uses original_time and threshold_result.from for threshold alerts', async () => {
|
||||
const ecsDataMock = getThresholdDetectionAlertAADMock();
|
||||
const ecsDataMockWithNoTemplateTimeline = getThresholdDetectionAlertAADMock({
|
||||
...mockAADEcsDataWithAlert,
|
||||
kibana: {
|
||||
alert: {
|
||||
...mockAADEcsDataWithAlert.kibana?.alert,
|
||||
rule: {
|
||||
...mockAADEcsDataWithAlert.kibana?.alert?.rule,
|
||||
parameters: {
|
||||
...mockAADEcsDataWithAlert.kibana?.alert?.rule?.parameters,
|
||||
threshold: {
|
||||
field: ['destination.ip'],
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
name: ['mock threshold rule'],
|
||||
saved_id: [],
|
||||
type: ['threshold'],
|
||||
uuid: ['c5ba41ab-aaf3-4f43-971b-bdf9434ce0ea'],
|
||||
timeline_id: undefined,
|
||||
timeline_title: undefined,
|
||||
},
|
||||
threshold_result: {
|
||||
count: 99,
|
||||
from: '2021-01-10T21:11:45.839Z',
|
||||
cardinality: [
|
||||
{
|
||||
field: 'source.ip',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
terms: [
|
||||
{
|
||||
field: 'destination.ip',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const expectedFrom = '2021-01-10T21:11:45.839Z';
|
||||
const expectedTo = '2021-01-10T21:12:45.839Z';
|
||||
|
||||
await sendAlertToTimelineAction({
|
||||
createTimeline,
|
||||
ecsData: ecsDataMock,
|
||||
ecsData: ecsDataMockWithNoTemplateTimeline,
|
||||
updateTimelineIsLoading,
|
||||
searchStrategyClient,
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ import {
|
|||
SendAlertToTimelineActionProps,
|
||||
ThresholdAggregationData,
|
||||
UpdateAlertStatusActionProps,
|
||||
CreateTimelineProps,
|
||||
} from './types';
|
||||
import { Ecs } from '../../../../common/ecs';
|
||||
import {
|
||||
|
@ -121,11 +122,9 @@ export const updateAlertStatusAction = async ({
|
|||
export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => {
|
||||
if (Array.isArray(ecs)) {
|
||||
const timestamps = ecs.reduce<number[]>((acc, item) => {
|
||||
if (item.timestamp != null) {
|
||||
const dateTimestamp = new Date(item.timestamp);
|
||||
if (!acc.includes(dateTimestamp.valueOf())) {
|
||||
return [...acc, dateTimestamp.valueOf()];
|
||||
}
|
||||
const dateTimestamp = item.timestamp ? new Date(item.timestamp) : new Date();
|
||||
if (!acc.includes(dateTimestamp.valueOf())) {
|
||||
return [...acc, dateTimestamp.valueOf()];
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
@ -137,12 +136,12 @@ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => {
|
|||
const ecsData = ecs as Ecs;
|
||||
const ruleFrom = getField(ecsData, ALERT_RULE_FROM);
|
||||
const elapsedTimeRule = moment.duration(
|
||||
moment().diff(dateMath.parse(ruleFrom != null ? ruleFrom[0] : 'now-0s'))
|
||||
moment().diff(dateMath.parse(ruleFrom != null ? ruleFrom[0] : 'now-1d'))
|
||||
);
|
||||
const from = moment(ecsData?.timestamp ?? new Date())
|
||||
const from = moment(ecsData.timestamp ?? new Date())
|
||||
.subtract(elapsedTimeRule)
|
||||
.toISOString();
|
||||
const to = moment(ecsData?.timestamp ?? new Date()).toISOString();
|
||||
const to = moment(ecsData.timestamp ?? new Date()).toISOString();
|
||||
|
||||
return { to, from };
|
||||
};
|
||||
|
@ -258,17 +257,18 @@ export const getThresholdAggregationData = (ecsData: Ecs | Ecs[]): ThresholdAggr
|
|||
);
|
||||
};
|
||||
|
||||
export const isEqlRuleWithGroupId = (ecsData: Ecs) => {
|
||||
export const isEqlRuleWithGroupId = (ecsData: Ecs): boolean => {
|
||||
const ruleType = getField(ecsData, ALERT_RULE_TYPE);
|
||||
const groupId = getField(ecsData, ALERT_GROUP_ID);
|
||||
return ruleType?.length && ruleType[0] === 'eql' && groupId?.length;
|
||||
const isEql = ruleType === 'eql' || (Array.isArray(ruleType) && ruleType[0] === 'eql');
|
||||
return isEql && groupId?.length > 0;
|
||||
};
|
||||
|
||||
export const isThresholdRule = (ecsData: Ecs) => {
|
||||
export const isThresholdRule = (ecsData: Ecs): boolean => {
|
||||
const ruleType = getField(ecsData, ALERT_RULE_TYPE);
|
||||
return (
|
||||
ruleType === 'threshold' ||
|
||||
(Array.isArray(ruleType) && ruleType.length && ruleType[0] === 'threshold')
|
||||
(Array.isArray(ruleType) && ruleType.length > 0 && ruleType[0] === 'threshold')
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -303,50 +303,51 @@ export const buildAlertsKqlFilter = (
|
|||
];
|
||||
};
|
||||
|
||||
export const buildTimelineDataProviderOrFilter = (
|
||||
alertsIds: string[],
|
||||
const buildTimelineDataProviderOrFilter = (
|
||||
alertIds: string[],
|
||||
_id: string
|
||||
): { filters: Filter[]; dataProviders: DataProvider[] } => {
|
||||
if (!isEmpty(alertsIds)) {
|
||||
if (!isEmpty(alertIds) && Array.isArray(alertIds) && alertIds.length > 1) {
|
||||
return {
|
||||
filters: buildAlertsKqlFilter('_id', alertIds),
|
||||
dataProviders: [],
|
||||
filters: buildAlertsKqlFilter('_id', alertsIds),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
filters: [],
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${_id}`,
|
||||
name: _id,
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '_id',
|
||||
value: _id,
|
||||
operator: ':' as const,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
filters: [],
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${_id}`,
|
||||
name: _id,
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '_id',
|
||||
value: _id,
|
||||
operator: ':' as const,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const buildEqlDataProviderOrFilter = (
|
||||
alertsIds: string[],
|
||||
const buildEqlDataProviderOrFilter = (
|
||||
alertIds: string[],
|
||||
ecs: Ecs[] | Ecs
|
||||
): { filters: Filter[]; dataProviders: DataProvider[] } => {
|
||||
if (!isEmpty(alertsIds) && Array.isArray(ecs)) {
|
||||
if (!isEmpty(alertIds) && Array.isArray(ecs) && ecs.length > 1) {
|
||||
return {
|
||||
dataProviders: [],
|
||||
filters: buildAlertsKqlFilter(
|
||||
'signal.group.id',
|
||||
ALERT_GROUP_ID,
|
||||
ecs.reduce<string[]>((acc, ecsData) => {
|
||||
const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID);
|
||||
const alertGroupId = alertGroupIdField?.length
|
||||
const alertGroupId = Array.isArray(alertGroupIdField)
|
||||
? alertGroupIdField[0]
|
||||
: 'unknown-group-id';
|
||||
: alertGroupIdField;
|
||||
if (!acc.includes(alertGroupId)) {
|
||||
return [...acc, alertGroupId];
|
||||
}
|
||||
|
@ -354,16 +355,19 @@ export const buildEqlDataProviderOrFilter = (
|
|||
}, [])
|
||||
),
|
||||
};
|
||||
} else if (!Array.isArray(ecs)) {
|
||||
const alertGroupIdField: string[] = getField(ecs, ALERT_GROUP_ID);
|
||||
const queryMatchField = getFieldKey(ecs, ALERT_GROUP_ID);
|
||||
const alertGroupId = alertGroupIdField?.length ? alertGroupIdField[0] : 'unknown-group-id';
|
||||
} else if (!Array.isArray(ecs) || ecs.length === 1) {
|
||||
const ecsData = Array.isArray(ecs) ? ecs[0] : ecs;
|
||||
const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID);
|
||||
const queryMatchField = getFieldKey(ecsData, ALERT_GROUP_ID);
|
||||
const alertGroupId = Array.isArray(alertGroupIdField)
|
||||
? alertGroupIdField[0]
|
||||
: alertGroupIdField;
|
||||
return {
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${alertGroupId}`,
|
||||
name: ecs._id,
|
||||
name: ecsData._id,
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
|
@ -380,6 +384,49 @@ export const buildEqlDataProviderOrFilter = (
|
|||
return { filters: [], dataProviders: [] };
|
||||
};
|
||||
|
||||
const createThresholdTimeline = (
|
||||
ecsData: Ecs,
|
||||
createTimeline: ({ from, timeline, to }: CreateTimelineProps) => void,
|
||||
noteContent: string,
|
||||
templateValues: { filters?: Filter[]; query?: string; dataProviders?: DataProvider[] }
|
||||
) => {
|
||||
const { thresholdFrom, thresholdTo, dataProviders } = getThresholdAggregationData(ecsData);
|
||||
const params = getField(ecsData, ALERT_RULE_PARAMETERS);
|
||||
const filters = getFiltersFromRule(params.filters ?? ecsData.signal?.rule?.filters) ?? [];
|
||||
const language = params.language ?? ecsData.signal?.rule?.language ?? 'kuery';
|
||||
const query = params.query ?? ecsData.signal?.rule?.query ?? '';
|
||||
const indexNames = params.index ?? ecsData.signal?.rule?.index ?? [];
|
||||
|
||||
return createTimeline({
|
||||
from: thresholdFrom,
|
||||
notes: null,
|
||||
timeline: {
|
||||
...timelineDefaults,
|
||||
description: `_id: ${ecsData._id}`,
|
||||
filters: templateValues.filters ?? filters,
|
||||
dataProviders: templateValues.dataProviders ?? dataProviders,
|
||||
id: TimelineId.active,
|
||||
indexNames,
|
||||
dateRange: {
|
||||
start: thresholdFrom,
|
||||
end: thresholdTo,
|
||||
},
|
||||
eventType: 'all',
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: language,
|
||||
expression: templateValues.query ?? query,
|
||||
},
|
||||
serializedQuery: templateValues.query ?? query,
|
||||
},
|
||||
},
|
||||
},
|
||||
to: thresholdTo,
|
||||
ruleNote: noteContent,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendAlertToTimelineAction = async ({
|
||||
createTimeline,
|
||||
ecsData: ecs,
|
||||
|
@ -395,12 +442,15 @@ export const sendAlertToTimelineAction = async ({
|
|||
const ruleNote = getField(ecsData, ALERT_RULE_NOTE);
|
||||
const noteContent = Array.isArray(ruleNote) && ruleNote.length > 0 ? ruleNote[0] : '';
|
||||
const ruleTimelineId = getField(ecsData, ALERT_RULE_TIMELINE_ID);
|
||||
const timelineId =
|
||||
Array.isArray(ruleTimelineId) && ruleTimelineId.length > 0 ? ruleTimelineId[0] : '';
|
||||
const timelineId = !isEmpty(ruleTimelineId)
|
||||
? Array.isArray(ruleTimelineId)
|
||||
? ruleTimelineId[0]
|
||||
: ruleTimelineId
|
||||
: '';
|
||||
const { to, from } = determineToAndFrom({ ecs });
|
||||
|
||||
// For now we do not want to populate the template timeline if we have alertIds
|
||||
if (!isEmpty(timelineId) && isEmpty(alertIds)) {
|
||||
if (!isEmpty(timelineId)) {
|
||||
try {
|
||||
updateTimelineIsLoading({ id: TimelineId.active, isLoading: true });
|
||||
const [responseTimeline, eventDataResp] = await Promise.all([
|
||||
|
@ -440,81 +490,67 @@ export const sendAlertToTimelineAction = async ({
|
|||
eventData,
|
||||
timeline.timelineType
|
||||
);
|
||||
|
||||
return createTimeline({
|
||||
from,
|
||||
timeline: {
|
||||
...timeline,
|
||||
title: '',
|
||||
timelineType: TimelineType.default,
|
||||
templateTimelineId: null,
|
||||
status: TimelineStatus.draft,
|
||||
dataProviders,
|
||||
eventType: 'all',
|
||||
// threshold with template
|
||||
if (isThresholdRule(ecsData)) {
|
||||
createThresholdTimeline(ecsData, createTimeline, noteContent, {
|
||||
filters,
|
||||
dateRange: {
|
||||
start: from,
|
||||
end: to,
|
||||
},
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery',
|
||||
expression: query,
|
||||
},
|
||||
serializedQuery: convertKueryToElasticSearchQuery(query),
|
||||
query,
|
||||
dataProviders,
|
||||
});
|
||||
} else {
|
||||
return createTimeline({
|
||||
from,
|
||||
timeline: {
|
||||
...timeline,
|
||||
title: '',
|
||||
timelineType: TimelineType.default,
|
||||
templateTimelineId: null,
|
||||
status: TimelineStatus.draft,
|
||||
dataProviders,
|
||||
eventType: 'all',
|
||||
filters,
|
||||
dateRange: {
|
||||
start: from,
|
||||
end: to,
|
||||
},
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery',
|
||||
expression: query,
|
||||
},
|
||||
serializedQuery: convertKueryToElasticSearchQuery(query),
|
||||
},
|
||||
},
|
||||
noteIds: notes?.map((n) => n.noteId) ?? [],
|
||||
show: true,
|
||||
},
|
||||
noteIds: notes?.map((n) => n.noteId) ?? [],
|
||||
show: true,
|
||||
},
|
||||
to,
|
||||
ruleNote: noteContent,
|
||||
notes: notes ?? null,
|
||||
});
|
||||
to,
|
||||
ruleNote: noteContent,
|
||||
notes: notes ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
updateTimelineIsLoading({ id: TimelineId.active, isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
if (isThresholdRule(ecsData)) {
|
||||
const { thresholdFrom, thresholdTo, dataProviders } = getThresholdAggregationData(ecsData);
|
||||
|
||||
const params = getField(ecsData, ALERT_RULE_PARAMETERS);
|
||||
const filters = getFiltersFromRule(params.filters ?? ecsData.signal?.rule?.filters) ?? [];
|
||||
const language = params.language ?? ecsData.signal?.rule?.language ?? 'kuery';
|
||||
const query = params.query ?? ecsData.signal?.rule?.query ?? '';
|
||||
const indexNames = params.index ?? ecsData.signal?.rule?.index ?? [];
|
||||
|
||||
return createTimeline({
|
||||
from: thresholdFrom,
|
||||
notes: null,
|
||||
timeline: {
|
||||
...timelineDefaults,
|
||||
description: `_id: ${ecsData._id}`,
|
||||
filters,
|
||||
dataProviders,
|
||||
id: TimelineId.active,
|
||||
indexNames,
|
||||
dateRange: {
|
||||
start: thresholdFrom,
|
||||
end: thresholdTo,
|
||||
},
|
||||
eventType: 'all',
|
||||
kqlQuery: {
|
||||
filterQuery: {
|
||||
kuery: {
|
||||
kind: language,
|
||||
expression: query,
|
||||
},
|
||||
serializedQuery: query,
|
||||
return createTimeline({
|
||||
from,
|
||||
notes: null,
|
||||
timeline: {
|
||||
...timelineDefaults,
|
||||
id: TimelineId.active,
|
||||
indexNames: [],
|
||||
dateRange: {
|
||||
start: from,
|
||||
end: to,
|
||||
},
|
||||
eventType: 'all',
|
||||
},
|
||||
},
|
||||
to: thresholdTo,
|
||||
ruleNote: noteContent,
|
||||
});
|
||||
to,
|
||||
});
|
||||
}
|
||||
} else if (isThresholdRule(ecsData)) {
|
||||
createThresholdTimeline(ecsData, createTimeline, noteContent, {});
|
||||
} else {
|
||||
let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id);
|
||||
if (isEqlRuleWithGroupId(ecsData)) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { EuiContextMenuItem } from '@elastic/eui';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
@ -84,7 +85,7 @@ export const useInvestigateInTimeline = ({
|
|||
if (onInvestigateInTimelineAlertClick) {
|
||||
onInvestigateInTimelineAlertClick();
|
||||
}
|
||||
if (alertsEcsData != null) {
|
||||
if (!isEmpty(alertsEcsData) && alertsEcsData !== null) {
|
||||
await sendAlertToTimelineAction({
|
||||
createTimeline,
|
||||
ecsData: alertsEcsData,
|
||||
|
|
|
@ -8,12 +8,15 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
import { Capabilities } from '../../../../src/core/public';
|
||||
import { CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants';
|
||||
import { mockEcsDataWithAlert } from './common/mock';
|
||||
import { ALERT_RULE_UUID, ALERT_RULE_NAME, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
parseRoute,
|
||||
getHostRiskIndex,
|
||||
isSubPluginAvailable,
|
||||
getSubPluginRoutesByCapabilities,
|
||||
RedirectRoute,
|
||||
getField,
|
||||
} from './helpers';
|
||||
import { StartedSubPlugins } from './types';
|
||||
|
||||
|
@ -274,3 +277,52 @@ describe('RedirectRoute', () => {
|
|||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('public helpers getField', () => {
|
||||
it('should return the same value for signal.rule fields as for kibana.alert.rule fields', () => {
|
||||
const signalRuleName = getField(mockEcsDataWithAlert, 'signal.rule.name');
|
||||
const aadRuleName = getField(mockEcsDataWithAlert, ALERT_RULE_NAME);
|
||||
const aadRuleId = getField(mockEcsDataWithAlert, ALERT_RULE_UUID);
|
||||
const signalRuleId = getField(mockEcsDataWithAlert, 'signal.rule.id');
|
||||
expect(signalRuleName).toEqual(aadRuleName);
|
||||
expect(signalRuleId).toEqual(aadRuleId);
|
||||
});
|
||||
|
||||
it('should handle flattened rule parameters correctly', () => {
|
||||
const mockAlertWithParameters = {
|
||||
...mockEcsDataWithAlert,
|
||||
'kibana.alert.rule.parameters': {
|
||||
description: '24/7',
|
||||
risk_score: '21',
|
||||
severity: 'low',
|
||||
timeline_id: '1234-2136-11ea-9864-ebc8cc1cb8c2',
|
||||
timeline_title: 'Untitled timeline',
|
||||
meta: {
|
||||
from: '1000m',
|
||||
kibana_siem_app_url: 'https://localhost:5601/app/security',
|
||||
},
|
||||
author: [],
|
||||
false_positives: [],
|
||||
from: 'now-300s',
|
||||
rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea',
|
||||
max_signals: 100,
|
||||
risk_score_mapping: [],
|
||||
severity_mapping: [],
|
||||
threat: [],
|
||||
to: 'now',
|
||||
references: ['www.test.co'],
|
||||
version: '1',
|
||||
exceptions_list: [],
|
||||
immutable: false,
|
||||
type: 'query',
|
||||
language: 'kuery',
|
||||
index: ['auditbeat-*'],
|
||||
query: 'user.name: root or user.name: admin',
|
||||
filters: [],
|
||||
},
|
||||
};
|
||||
const signalQuery = getField(mockAlertWithParameters, 'signal.rule.query');
|
||||
const aadQuery = getField(mockAlertWithParameters, `${ALERT_RULE_PARAMETERS}.query`);
|
||||
expect(signalQuery).toEqual(aadQuery);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -262,14 +262,9 @@ export const getField = (ecsData: Ecs, field: string) => {
|
|||
const paramsField = parts.slice(0, parts.length - 1).join('.');
|
||||
const params = get(paramsField, ecsData);
|
||||
const value = get(parts[parts.length - 1], params);
|
||||
if (isEmpty(value)) {
|
||||
return [];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const value = get(aadField, ecsData) ?? get(siemSignalsField, ecsData);
|
||||
if (isEmpty(value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue