From 1c6d7724fac017f94f2601179c16cc38302e0b3e Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 18 Jan 2022 13:44:48 -1000 Subject: [PATCH 01/15] Open alerts with a template, with a template --- .../components/alerts_table/actions.test.tsx | 6 ++-- .../components/alerts_table/actions.tsx | 36 +++++++++---------- .../security_solution/public/helpers.tsx | 7 +--- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index a1b1a8ede750c..e5fd9ea82f759 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -289,8 +289,7 @@ describe('alert actions', () => { signal: { rule: { ...mockEcsDataWithAlert.signal?.rule, - // @ts-expect-error - timeline_id: null, + timeline_id: [''], }, }, }; @@ -362,6 +361,7 @@ describe('alert actions', () => { ...defaultTimelineProps, timeline: { ...defaultTimelineProps.timeline, + resolveTimelineConfig: undefined, dataProviders: [ { and: [], @@ -370,7 +370,7 @@ describe('alert actions', () => { id: 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-my-group-id', kqlQuery: '', name: '1', - queryMatch: { field: 'signal.group.id', operator: ':', value: 'my-group-id' }, + queryMatch: { field: 'signal.group.id', operator: ':', value: ['my-group-id'] }, }, ], }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 7033f9cfb2fea..f4cc057332459 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -258,17 +258,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') ); }; @@ -288,7 +289,7 @@ export const buildAlertsKqlFilter = ( }, }, meta: { - alias: 'Alert Ids', + alias: `Alert Ids: ${alertIds.join()}`, negate: false, disabled: false, type: 'phrases', @@ -333,9 +334,9 @@ export const buildTimelineDataProviderOrFilter = ( }; }; -export const buildEqlDataProviderOrFilter = ( +const buildEqlDataProviderOrFilter = ( alertsIds: string[], - ecs: Ecs[] | Ecs + ecs: Ecs ): { filters: Filter[]; dataProviders: DataProvider[] } => { if (!isEmpty(alertsIds) && Array.isArray(ecs)) { return { @@ -344,9 +345,7 @@ export const buildEqlDataProviderOrFilter = ( 'signal.group.id', ecs.reduce((acc, ecsData) => { const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID); - const alertGroupId = alertGroupIdField?.length - ? alertGroupIdField[0] - : 'unknown-group-id'; + const alertGroupId = alertGroupIdField?.length ? alertGroupIdField : 'unknown-group-id'; if (!acc.includes(alertGroupId)) { return [...acc, alertGroupId]; } @@ -355,9 +354,9 @@ export const buildEqlDataProviderOrFilter = ( ), }; } else if (!Array.isArray(ecs)) { - const alertGroupIdField: string[] = getField(ecs, ALERT_GROUP_ID); + const alertGroupIdField = getField(ecs, ALERT_GROUP_ID); const queryMatchField = getFieldKey(ecs, ALERT_GROUP_ID); - const alertGroupId = alertGroupIdField?.length ? alertGroupIdField[0] : 'unknown-group-id'; + const alertGroupId = alertGroupIdField?.length ? alertGroupIdField : 'unknown-group-id'; return { dataProviders: [ { @@ -395,12 +394,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) && isThresholdRule(ecsData) === false) { try { updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ @@ -446,8 +448,6 @@ export const sendAlertToTimelineAction = async ({ timeline: { ...timeline, title: '', - timelineType: TimelineType.default, - templateTimelineId: null, status: TimelineStatus.draft, dataProviders, eventType: 'all', @@ -518,7 +518,7 @@ export const sendAlertToTimelineAction = async ({ } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) { - const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecs); + const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecsData); dataProviders = tempEql.dataProviders; filters = tempEql.filters; } diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index 40feb9e0b3eda..13aa6197953b2 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -258,14 +258,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; }; From 0e616ba9cc5f3b1652e08a0f7829514948052bc0 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 19 Jan 2022 05:55:03 -1000 Subject: [PATCH 02/15] Add default values back instead of template derived ones --- .../public/detections/components/alerts_table/actions.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index f4cc057332459..ad2c4a4cfe63a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -448,6 +448,8 @@ export const sendAlertToTimelineAction = async ({ timeline: { ...timeline, title: '', + timelineType: TimelineType.default, + templateTimelineId: null, status: TimelineStatus.draft, dataProviders, eventType: 'all', From 5be2880db1730cc4ea7810e4a9eecacd81e1b84b Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 19 Jan 2022 09:23:57 -1000 Subject: [PATCH 03/15] Use data providers over filters always, set timeline description to alert id --- .../components/alerts_table/actions.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index ad2c4a4cfe63a..103b90fa902a5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -310,8 +310,22 @@ export const buildTimelineDataProviderOrFilter = ( ): { filters: Filter[]; dataProviders: DataProvider[] } => { if (!isEmpty(alertsIds)) { return { - dataProviders: [], - filters: buildAlertsKqlFilter('_id', alertsIds), + filters: [], + dataProviders: alertsIds.map((id) => { + return { + 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 { @@ -531,6 +545,7 @@ export const sendAlertToTimelineAction = async ({ timeline: { ...timelineDefaults, dataProviders, + description: `_id: ${ecsData._id}`, id: TimelineId.active, indexNames: [], dateRange: { From 2c32e9f4ef3f173d831eaa672d148fc29dbd38ab Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 19 Jan 2022 09:35:28 -1000 Subject: [PATCH 04/15] Remove prepopulated description from non threshold alerts --- .../public/detections/components/alerts_table/actions.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 103b90fa902a5..f5bf0af5a7d43 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -545,7 +545,6 @@ export const sendAlertToTimelineAction = async ({ timeline: { ...timelineDefaults, dataProviders, - description: `_id: ${ecsData._id}`, id: TimelineId.active, indexNames: [], dateRange: { From 597b4c92ba7e5f4e1958fa3a246a0871230b9256 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 11:43:23 -1000 Subject: [PATCH 05/15] Open any event in timeline, use correct timestamp --- .../security_solution/common/ecs/index.ts | 1 + .../components/alerts_table/actions.test.tsx | 2 +- .../components/alerts_table/actions.tsx | 89 +++++++++---------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts index c3e589447313a..815aef3638f4e 100644 --- a/x-pack/plugins/security_solution/common/ecs/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -37,6 +37,7 @@ import { Target } from './target_type'; export interface Ecs { _id: string; _index?: string; + '@timestamp'?: string[]; agent?: AgentEcs; auditd?: AuditdEcs; destination?: DestinationEcs; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index e5fd9ea82f759..5a791de41f00a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -370,7 +370,7 @@ describe('alert actions', () => { id: 'send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-timeline-1-alert-id-my-group-id', kqlQuery: '', name: '1', - queryMatch: { field: 'signal.group.id', operator: ':', value: ['my-group-id'] }, + queryMatch: { field: 'signal.group.id', operator: ':', value: 'my-group-id' }, }, ], }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index f5bf0af5a7d43..68e24c0bf3ed6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -121,8 +121,15 @@ export const updateAlertStatusAction = async ({ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { if (Array.isArray(ecs)) { const timestamps = ecs.reduce((acc, item) => { - if (item.timestamp != null) { - const dateTimestamp = new Date(item.timestamp); + const addedTimestamp = item.timestamp; + const ecsTimestamp = item['@timestamp'] && item['@timestamp'][0]; + if (addedTimestamp != null) { + const dateTimestamp = new Date(addedTimestamp); + if (!acc.includes(dateTimestamp.valueOf())) { + return [...acc, dateTimestamp.valueOf()]; + } + } else if (ecsTimestamp != null) { + const dateTimestamp = new Date(ecsTimestamp); if (!acc.includes(dateTimestamp.valueOf())) { return [...acc, dateTimestamp.valueOf()]; } @@ -137,12 +144,15 @@ 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 addedTimestamp = ecsData.timestamp; + const ecsTimestamp = ecsData['@timestamp'] && ecsData['@timestamp'][0]; + const timestampToUse = ecsTimestamp ? ecsTimestamp : addedTimestamp; + const from = moment(timestampToUse ?? new Date()) .subtract(elapsedTimeRule) .toISOString(); - const to = moment(ecsData?.timestamp ?? new Date()).toISOString(); + const to = moment(timestampToUse ?? new Date()).toISOString(); return { to, from }; }; @@ -289,7 +299,7 @@ export const buildAlertsKqlFilter = ( }, }, meta: { - alias: `Alert Ids: ${alertIds.join()}`, + alias: 'Alert Ids', negate: false, disabled: false, type: 'phrases', @@ -304,62 +314,50 @@ export const buildAlertsKqlFilter = ( ]; }; -export const buildTimelineDataProviderOrFilter = ( - alertsIds: string[], +const buildTimelineDataProviderOrFilter = ( + alertIds: string[], _id: string ): { filters: Filter[]; dataProviders: DataProvider[] } => { - if (!isEmpty(alertsIds)) { + if (!isEmpty(alertIds)) { + return { + filters: buildAlertsKqlFilter('_id', alertIds), + dataProviders: [], + }; + } else { return { filters: [], - dataProviders: alertsIds.map((id) => { - return { + dataProviders: [ + { and: [], - id: `send-alert-to-timeline-action-default-draggable-event-details-value-formatted-field-value-${TimelineId.active}-alert-id-${id}`, - name: id, + 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, + 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, - }, - }, - ], - }; }; const buildEqlDataProviderOrFilter = ( - alertsIds: string[], - ecs: Ecs + 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((acc, ecsData) => { const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID); - const alertGroupId = alertGroupIdField?.length ? alertGroupIdField : 'unknown-group-id'; + const alertGroupId = + alertGroupIdField?.length > 1 ? alertGroupIdField : alertGroupIdField[0]; if (!acc.includes(alertGroupId)) { return [...acc, alertGroupId]; } @@ -367,16 +365,17 @@ const buildEqlDataProviderOrFilter = ( }, []) ), }; - } else if (!Array.isArray(ecs)) { - const alertGroupIdField = getField(ecs, ALERT_GROUP_ID); - const queryMatchField = getFieldKey(ecs, ALERT_GROUP_ID); - const alertGroupId = alertGroupIdField?.length ? alertGroupIdField : '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 = alertGroupIdField?.length > 1 ? alertGroupIdField : alertGroupIdField[0]; 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: '', @@ -534,7 +533,7 @@ export const sendAlertToTimelineAction = async ({ } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) { - const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecsData); + const tempEql = buildEqlDataProviderOrFilter(alertIds ?? [], ecs); dataProviders = tempEql.dataProviders; filters = tempEql.filters; } From 6f72615d56394088b540b64ca91a88303f9b6c92 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 17:37:51 -0500 Subject: [PATCH 06/15] Remove unneeded @timestamp, make sure alertsEcsData is not empty array --- .../security_solution/common/ecs/index.ts | 1 - .../components/alerts_table/actions.tsx | 22 +++++-------------- .../use_investigate_in_timeline.tsx | 3 ++- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts index 815aef3638f4e..c3e589447313a 100644 --- a/x-pack/plugins/security_solution/common/ecs/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -37,7 +37,6 @@ import { Target } from './target_type'; export interface Ecs { _id: string; _index?: string; - '@timestamp'?: string[]; agent?: AgentEcs; auditd?: AuditdEcs; destination?: DestinationEcs; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 68e24c0bf3ed6..76d131f7a1a25 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -121,18 +121,9 @@ export const updateAlertStatusAction = async ({ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { if (Array.isArray(ecs)) { const timestamps = ecs.reduce((acc, item) => { - const addedTimestamp = item.timestamp; - const ecsTimestamp = item['@timestamp'] && item['@timestamp'][0]; - if (addedTimestamp != null) { - const dateTimestamp = new Date(addedTimestamp); - if (!acc.includes(dateTimestamp.valueOf())) { - return [...acc, dateTimestamp.valueOf()]; - } - } else if (ecsTimestamp != null) { - const dateTimestamp = new Date(ecsTimestamp); - if (!acc.includes(dateTimestamp.valueOf())) { - return [...acc, dateTimestamp.valueOf()]; - } + const dateTimestamp = new Date(item.timestamp ?? ''); + if (!acc.includes(dateTimestamp.valueOf())) { + return [...acc, dateTimestamp.valueOf()]; } return acc; }, []); @@ -146,13 +137,10 @@ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { const elapsedTimeRule = moment.duration( moment().diff(dateMath.parse(ruleFrom != null ? ruleFrom[0] : 'now-1d')) ); - const addedTimestamp = ecsData.timestamp; - const ecsTimestamp = ecsData['@timestamp'] && ecsData['@timestamp'][0]; - const timestampToUse = ecsTimestamp ? ecsTimestamp : addedTimestamp; - const from = moment(timestampToUse ?? new Date()) + const from = moment(ecsData.timestamp ?? new Date()) .subtract(elapsedTimeRule) .toISOString(); - const to = moment(timestampToUse ?? new Date()).toISOString(); + const to = moment(ecsData.timestamp ?? new Date()).toISOString(); return { to, from }; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index db1760d86f624..c1cbe657415a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -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, From c8a86daf3045b9500c3df9666ff0d65bf8f0b35f Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 19:01:53 -0500 Subject: [PATCH 07/15] Add basic getField tests --- .../security_solution/public/helpers.test.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/x-pack/plugins/security_solution/public/helpers.test.tsx b/x-pack/plugins/security_solution/public/helpers.test.tsx index 5ba5d882c16d0..dd380fc3ccd39 100644 --- a/x-pack/plugins/security_solution/public/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/helpers.test.tsx @@ -8,11 +8,14 @@ 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, isSubPluginAvailable, getSubPluginRoutesByCapabilities, RedirectRoute, + getField, } from './helpers'; import { StartedSubPlugins } from './types'; @@ -267,3 +270,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); + }); +}); From f5901244cdc083fda6090d1c3123649f421c7f9f Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 19:45:42 -0500 Subject: [PATCH 08/15] Explicity check if alertGroupId is an array instead of using length --- .../detections/components/alerts_table/actions.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 76d131f7a1a25..986af961801a8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -344,8 +344,9 @@ const buildEqlDataProviderOrFilter = ( ALERT_GROUP_ID, ecs.reduce((acc, ecsData) => { const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID); - const alertGroupId = - alertGroupIdField?.length > 1 ? alertGroupIdField : alertGroupIdField[0]; + const alertGroupId = !Array.isArray(alertGroupIdField) + ? alertGroupIdField + : alertGroupIdField[0]; if (!acc.includes(alertGroupId)) { return [...acc, alertGroupId]; } @@ -357,7 +358,9 @@ const buildEqlDataProviderOrFilter = ( const ecsData = Array.isArray(ecs) ? ecs[0] : ecs; const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID); const queryMatchField = getFieldKey(ecsData, ALERT_GROUP_ID); - const alertGroupId = alertGroupIdField?.length > 1 ? alertGroupIdField : alertGroupIdField[0]; + const alertGroupId = !Array.isArray(alertGroupIdField) + ? alertGroupIdField + : alertGroupIdField[0]; return { dataProviders: [ { From 6fa41a9ecb18526565bbf14017b472d024a8ca0b Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 20:07:43 -0500 Subject: [PATCH 09/15] Always use a valid date for time range --- .../public/detections/components/alerts_table/actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 986af961801a8..6889a3b113066 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -121,7 +121,7 @@ export const updateAlertStatusAction = async ({ export const determineToAndFrom = ({ ecs }: { ecs: Ecs[] | Ecs }) => { if (Array.isArray(ecs)) { const timestamps = ecs.reduce((acc, item) => { - const dateTimestamp = new Date(item.timestamp ?? ''); + const dateTimestamp = item.timestamp ? new Date(item.timestamp) : new Date(); if (!acc.includes(dateTimestamp.valueOf())) { return [...acc, dateTimestamp.valueOf()]; } From 59ab4aad66ca2ac8ea18de8df0d6885a3bed2b16 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 20:11:45 -0500 Subject: [PATCH 10/15] Only use filter if more than 1 alert is present --- .../public/detections/components/alerts_table/actions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 6889a3b113066..a92a38560926f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -306,7 +306,7 @@ const buildTimelineDataProviderOrFilter = ( alertIds: string[], _id: string ): { filters: Filter[]; dataProviders: DataProvider[] } => { - if (!isEmpty(alertIds)) { + if (!isEmpty(alertIds) && Array.isArray(alertIds) && alertIds.length > 1) { return { filters: buildAlertsKqlFilter('_id', alertIds), dataProviders: [], From 6fd6265af0a49aa34dcf85876e547eb456d22be5 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 22:34:46 -0500 Subject: [PATCH 11/15] Possibly controversial change to calculate threshold time range with a template, fix test that should never have passed --- .../components/alerts_table/actions.test.tsx | 15 +- .../components/alerts_table/actions.tsx | 138 ++++++++++++------ 2 files changed, 110 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 5a791de41f00a..8b1ecb4275e72 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -268,6 +268,9 @@ describe('alert actions', () => { updateTimelineIsLoading, searchStrategyClient, }); + const defaultTimelinePropsWithoutNote = { ...defaultTimelineProps }; + + delete defaultTimelinePropsWithoutNote.ruleNote; expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: TimelineId.active, @@ -278,7 +281,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, + }, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index a92a38560926f..15bd2d785bf24 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -406,7 +406,7 @@ export const sendAlertToTimelineAction = async ({ const { to, from } = determineToAndFrom({ ecs }); // For now we do not want to populate the template timeline if we have alertIds - if (!isEmpty(timelineId) && isThresholdRule(ecsData) === false) { + if (!isEmpty(timelineId)) { try { updateTimelineIsLoading({ id: TimelineId.active, isLoading: true }); const [responseTimeline, eventDataResp] = await Promise.all([ @@ -435,56 +435,110 @@ export const sendAlertToTimelineAction = async ({ true, timelineTemplate.timelineType ?? TimelineType.default ); - const query = replaceTemplateFieldFromQuery( - timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', - eventData, - timeline.timelineType - ); - const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], eventData); - const dataProviders = replaceTemplateFieldFromDataProviders( - timeline.dataProviders ?? [], - eventData, - timeline.timelineType - ); - return createTimeline({ - from, - timeline: { - ...timeline, - title: '', - timelineType: TimelineType.default, - templateTimelineId: null, - status: TimelineStatus.draft, - dataProviders, - eventType: 'all', - filters, - dateRange: { - start: from, - end: to, + // threshold with template + 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, + }, + }, }, - kqlQuery: { - filterQuery: { - kuery: { - kind: timeline.kqlQuery?.filterQuery?.kuery?.kind ?? 'kuery', - expression: query, + to: thresholdTo, + ruleNote: noteContent, + }); + } else { + const query = replaceTemplateFieldFromQuery( + timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', + eventData, + timeline.timelineType + ); + const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], eventData); + const dataProviders = replaceTemplateFieldFromDataProviders( + timeline.dataProviders ?? [], + eventData, + timeline.timelineType + ); + 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), }, - 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 }); + return createTimeline({ + from, + notes: null, + timeline: { + ...timelineDefaults, + id: TimelineId.active, + indexNames: [], + dateRange: { + start: from, + end: to, + }, + eventType: 'all', + }, + to, + }); } - } - - if (isThresholdRule(ecsData)) { + } else if (isThresholdRule(ecsData)) { const { thresholdFrom, thresholdTo, dataProviders } = getThresholdAggregationData(ecsData); const params = getField(ecsData, ALERT_RULE_PARAMETERS); From 9b2b89e3fd4a2a4d4fe6cc5796496c1a35068f44 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 22:38:31 -0500 Subject: [PATCH 12/15] Create threshold timeline in separate function --- .../components/alerts_table/actions.tsx | 114 +++++++----------- 1 file changed, 41 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 15bd2d785bf24..431ad00e4b0dc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -383,6 +383,45 @@ const buildEqlDataProviderOrFilter = ( return { filters: [], dataProviders: [] }; }; +const createThresholdTimeline = (ecsData: Ecs, createTimeline: Function, noteContent: string) => { + 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, + }, + }, + }, + to: thresholdTo, + ruleNote: noteContent, + }); +}; + export const sendAlertToTimelineAction = async ({ createTimeline, ecsData: ecs, @@ -438,43 +477,7 @@ export const sendAlertToTimelineAction = async ({ // threshold with template 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, - }, - }, - }, - to: thresholdTo, - ruleNote: noteContent, - }); + createThresholdTimeline(ecsData, createTimeline, noteContent); } else { const query = replaceTemplateFieldFromQuery( timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', @@ -539,42 +542,7 @@ export const sendAlertToTimelineAction = async ({ }); } } else 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, - }, - }, - }, - to: thresholdTo, - ruleNote: noteContent, - }); + createThresholdTimeline(ecsData, createTimeline, noteContent); } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) { From 5e5ab708f0dee45f249097bbdd1c0683d44e5a07 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 22:44:11 -0500 Subject: [PATCH 13/15] Use better type for createTimeline passed to createThresholdTimeline --- .../public/detections/components/alerts_table/actions.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 431ad00e4b0dc..d9be467797fc8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -38,6 +38,7 @@ import { SendAlertToTimelineActionProps, ThresholdAggregationData, UpdateAlertStatusActionProps, + CreateTimelineProps, } from './types'; import { Ecs } from '../../../../common/ecs'; import { @@ -383,7 +384,11 @@ const buildEqlDataProviderOrFilter = ( return { filters: [], dataProviders: [] }; }; -const createThresholdTimeline = (ecsData: Ecs, createTimeline: Function, noteContent: string) => { +const createThresholdTimeline = ( + ecsData: Ecs, + createTimeline: ({ from, timeline, to }: CreateTimelineProps) => void, + noteContent: string +) => { const { thresholdFrom, thresholdTo, dataProviders } = getThresholdAggregationData(ecsData); const params = getField(ecsData, ALERT_RULE_PARAMETERS); From 84dd174656f571996423267de9e38ac8aa7b7279 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 22:45:17 -0500 Subject: [PATCH 14/15] Invert negation as suggested in pr comment --- .../detections/components/alerts_table/actions.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index d9be467797fc8..94c6d33fac05c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -345,9 +345,9 @@ const buildEqlDataProviderOrFilter = ( ALERT_GROUP_ID, ecs.reduce((acc, ecsData) => { const alertGroupIdField = getField(ecsData, ALERT_GROUP_ID); - const alertGroupId = !Array.isArray(alertGroupIdField) - ? alertGroupIdField - : alertGroupIdField[0]; + const alertGroupId = Array.isArray(alertGroupIdField) + ? alertGroupIdField[0] + : alertGroupIdField; if (!acc.includes(alertGroupId)) { return [...acc, alertGroupId]; } @@ -359,9 +359,9 @@ const buildEqlDataProviderOrFilter = ( 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 - : alertGroupIdField[0]; + const alertGroupId = Array.isArray(alertGroupIdField) + ? alertGroupIdField[0] + : alertGroupIdField; return { dataProviders: [ { From 12c42d3742fd8cf8cda042153bb4ca9cde252bc4 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Mon, 24 Jan 2022 23:30:37 -0500 Subject: [PATCH 15/15] Use template timeline filters/query/data providers for threshold alerts --- .../components/alerts_table/actions.test.tsx | 44 ++++++++++++++++++- .../components/alerts_table/actions.tsx | 43 +++++++++--------- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx index 8b1ecb4275e72..ad95f89c850f6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx @@ -15,6 +15,7 @@ import { mockEcsDataWithAlert, mockTimelineDetails, mockTimelineResult, + mockAADEcsDataWithAlert, } from '../../../common/mock/'; import { CreateTimeline, UpdateTimelineLoading } from './types'; import { Ecs } from '../../../../common/ecs'; @@ -437,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, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 94c6d33fac05c..c12133089e02a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -387,10 +387,10 @@ const buildEqlDataProviderOrFilter = ( const createThresholdTimeline = ( ecsData: Ecs, createTimeline: ({ from, timeline, to }: CreateTimelineProps) => void, - noteContent: string + 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'; @@ -403,8 +403,8 @@ const createThresholdTimeline = ( timeline: { ...timelineDefaults, description: `_id: ${ecsData._id}`, - filters, - dataProviders, + filters: templateValues.filters ?? filters, + dataProviders: templateValues.dataProviders ?? dataProviders, id: TimelineId.active, indexNames, dateRange: { @@ -416,9 +416,9 @@ const createThresholdTimeline = ( filterQuery: { kuery: { kind: language, - expression: query, + expression: templateValues.query ?? query, }, - serializedQuery: query, + serializedQuery: templateValues.query ?? query, }, }, }, @@ -479,22 +479,25 @@ export const sendAlertToTimelineAction = async ({ true, timelineTemplate.timelineType ?? TimelineType.default ); - + const query = replaceTemplateFieldFromQuery( + timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', + eventData, + timeline.timelineType + ); + const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], eventData); + const dataProviders = replaceTemplateFieldFromDataProviders( + timeline.dataProviders ?? [], + eventData, + timeline.timelineType + ); // threshold with template if (isThresholdRule(ecsData)) { - createThresholdTimeline(ecsData, createTimeline, noteContent); + createThresholdTimeline(ecsData, createTimeline, noteContent, { + filters, + query, + dataProviders, + }); } else { - const query = replaceTemplateFieldFromQuery( - timeline.kqlQuery?.filterQuery?.kuery?.expression ?? '', - eventData, - timeline.timelineType - ); - const filters = replaceTemplateFieldFromMatchFilters(timeline.filters ?? [], eventData); - const dataProviders = replaceTemplateFieldFromDataProviders( - timeline.dataProviders ?? [], - eventData, - timeline.timelineType - ); return createTimeline({ from, timeline: { @@ -547,7 +550,7 @@ export const sendAlertToTimelineAction = async ({ }); } } else if (isThresholdRule(ecsData)) { - createThresholdTimeline(ecsData, createTimeline, noteContent); + createThresholdTimeline(ecsData, createTimeline, noteContent, {}); } else { let { dataProviders, filters } = buildTimelineDataProviderOrFilter(alertIds ?? [], ecsData._id); if (isEqlRuleWithGroupId(ecsData)) {