From b289217b0d4217a0ea8197589fc336c41ee92710 Mon Sep 17 00:00:00 2001 From: Ying Date: Wed, 14 Dec 2022 15:52:57 -0500 Subject: [PATCH] Moving security context variables to server. Adding summaryBuilder function to action variable definition --- x-pack/plugins/alerting/common/rule.ts | 25 ++++ .../server/task_runner/execution_handler.ts | 10 +- .../task_runner/transform_action_params.ts | 39 ++--- .../create_persistence_rule_type_wrapper.ts | 1 - .../create_security_rule_type_wrapper.ts | 64 +------- .../rule_types/eql/create_eql_alert_type.ts | 3 +- .../create_indicator_match_alert_type.ts | 3 +- .../rule_types/ml/create_ml_alert_type.ts | 3 +- .../new_terms/create_new_terms_alert_type.ts | 3 +- .../query/create_query_alert_type.ts | 3 +- .../threshold/create_threshold_alert_type.ts | 3 +- .../rule_types/utils/helpers.ts | 139 ++++++++++++++++++ 12 files changed, 207 insertions(+), 89 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/helpers.ts diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index b13c996e5bf7..60753dc685d5 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -187,11 +187,36 @@ export interface AlertsHealth { }; } +export interface SummarizedAlertsWithAll { + new: { + count: number; + data: unknown[]; + }; + ongoing: { + count: number; + data: unknown[]; + }; + recovered: { + count: number; + data: unknown[]; + }; + all: { + count: number; + data: unknown[]; + }; +} + export interface ActionVariable { name: string; description: string; deprecated?: boolean; useWithTripleBracesInTemplates?: boolean; + + // If this callback function is defined, this + // action variable will be available for summary + // alerts and will be generated from the list of summary + // alerts + summaryBuilder?: (alerts: SummarizedAlertsWithAll) => unknown; } export interface RuleMonitoringHistory extends SavedObjectAttributes { diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 8c97d591bd22..27ff79c5ba10 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -19,7 +19,11 @@ import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store'; import { injectActionParams } from './inject_action_params'; import { ExecutionHandlerOptions, RuleTaskInstance } from './types'; import { TaskRunnerContext } from './task_runner_factory'; -import { transformActionParams, transformSummaryActionParams } from './transform_action_params'; +import { + transformActionParams, + transformSummaryActionParams, + transformSummaryContext, +} from './transform_action_params'; import { Alert } from '../alert'; import { NormalizedRuleType } from '../rule_type_registry'; import { @@ -217,6 +221,10 @@ export class ExecutionHandler< actionsPlugin, actionTypeId, kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl, + summaryContext: transformSummaryContext( + this.ruleType.actionVariables?.context, + summarizedAlerts + ), ruleUrl: this.buildRuleUrl(spaceId), }), }), diff --git a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts index 8ac45165a4c9..0c67344f0677 100644 --- a/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +++ b/x-pack/plugins/alerting/server/task_runner/transform_action_params.ts @@ -12,6 +12,8 @@ import { AlertInstanceContext, RuleTypeParams, SanitizedRule, + ActionVariable, + SummarizedAlertsWithAll, } from '../types'; interface TransformActionParamsOptions { @@ -34,25 +36,6 @@ interface TransformActionParamsOptions { ruleUrl?: string; } -interface SummarizedAlertsWithAll { - new: { - count: number; - data: unknown[]; - }; - ongoing: { - count: number; - data: unknown[]; - }; - recovered: { - count: number; - data: unknown[]; - }; - all: { - count: number; - data: unknown[]; - }; -} - export function transformActionParams({ actionsPlugin, alertId, @@ -122,11 +105,13 @@ export function transformSummaryActionParams({ actionParams, ruleUrl, kibanaBaseUrl, + summaryContext, }: { alerts: SummarizedAlertsWithAll; rule: SanitizedRule; ruleTypeId: string; actionsPlugin: ActionsPluginStartContract; + summaryContext: AlertInstanceContext; actionId: string; actionTypeId: string; spaceId: string; @@ -137,6 +122,7 @@ export function transformSummaryActionParams({ const variables = { kibanaBaseUrl, date: new Date().toISOString(), + context: summaryContext, rule: { params: rule.params, id: rule.id, @@ -155,3 +141,18 @@ export function transformSummaryActionParams({ variables ); } + +export function transformSummaryContext( + context: ActionVariable[] | undefined, + alerts: SummarizedAlertsWithAll +): AlertInstanceContext { + return (context ?? []).reduce((acc, currContext: ActionVariable) => { + if (currContext.summaryBuilder) { + return { + ...acc, + [currContext.name]: currContext.summaryBuilder(alerts), + }; + } + return acc; + }, {}); +} diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts index 3c4bc838506c..bc898ae65c90 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts @@ -137,7 +137,6 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }; }) .filter((_, idx) => response.body.items[idx].create?.status === 201); - console.log(`persistence alerts ${createdAlerts.length}`); createdAlerts.forEach((alert) => options.services.alertFactory.create(alert._id).scheduleActions('default', {}) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 898e67ab52f7..f4f3fa830372 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -11,7 +11,6 @@ import agent from 'elastic-apm-node'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { TIMESTAMP } from '@kbn/rule-data-utils'; import { createPersistenceRuleTypeWrapper } from '@kbn/rule-registry-plugin/server'; -import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; import { buildExceptionFilter } from '@kbn/lists-plugin/server/services/exception_lists'; import { @@ -25,10 +24,6 @@ import { import { DEFAULT_MAX_SIGNALS, DEFAULT_SEARCH_AFTER_PAGE_SIZE } from '../../../../common/constants'; import type { CreateSecurityRuleTypeWrapper } from './types'; import { getListClient } from './utils/get_list_client'; -// eslint-disable-next-line no-restricted-imports -import type { NotificationRuleTypeParams } from '../rule_actions_legacy'; -// eslint-disable-next-line no-restricted-imports -import { getNotificationResultsLink } from '../rule_actions_legacy'; import { createResultObject } from './utils'; import { bulkCreateFactory, wrapHitsFactory, wrapSequencesFactory } from './factories'; import { RuleExecutionStatus } from '../../../../common/detection_engine/rule_monitoring'; @@ -71,15 +66,8 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = let runState = state; let inputIndex: string[] = []; let runtimeMappings: estypes.MappingRuntimeFields | undefined; - const { - from, - maxSignals, - meta, - ruleId, - timestampOverride, - timestampOverrideFallbackDisabled, - to, - } = params; + const { from, maxSignals, timestampOverride, timestampOverrideFallbackDisabled, to } = + params; const { alertWithPersistence, savedObjectsClient, @@ -110,7 +98,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const { actions, - name, schedule: { interval }, } = completeRule.ruleConfig; @@ -127,12 +114,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = let wroteWarningStatus = false; let hasError = false; - const notificationRuleParams: NotificationRuleTypeParams = { - ...params, - name, - id: rule.id, - }; - const primaryTimestamp = timestampOverride ?? TIMESTAMP; const secondaryTimestamp = primaryTimestamp !== TIMESTAMP && !timestampOverrideFallbackDisabled @@ -383,47 +364,6 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const createdSignalsCount = result.createdSignals.length; - if (actions.length) { - const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); - const toInMs = parseScheduleDates('now')?.format('x'); - const resultsLink = getNotificationResultsLink({ - from: fromInMs, - to: toInMs, - id: rule.id, - kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) - ?.kibana_siem_app_url, - }); - - ruleExecutionLogger.debug(`Found ${createdSignalsCount} signals for notification.`); - - // if (completeRule.ruleConfig.throttle != null) { - // // NOTE: Since this is throttled we have to call it even on an error condition, otherwise it will "reset" the throttle and fire early - // await scheduleThrottledNotificationActions({ - // alertInstance: services.alertFactory.create(rule.id), - // throttle: completeRule.ruleConfig.throttle ?? '', - // startedAt, - // id: rule.id, - // kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined) - // ?.kibana_siem_app_url, - // outputIndex: ruleDataClient.indexNameWithNamespace(spaceId), - // ruleId, - // esClient: services.scopedClusterClient.asCurrentUser, - // notificationRuleParams, - // signals: result.createdSignals, - // logger, - // }); - // } else if (createdSignalsCount) { - // const alertInstance = services.alertFactory.create(rule.id); - // scheduleNotificationActions({ - // alertInstance, - // signalsCount: createdSignalsCount, - // signals: result.createdSignals, - // resultsLink, - // ruleParams: notificationRuleParams, - // }); - // } - } - if (result.success) { ruleExecutionLogger.debug('[+] Signal Rule execution completed.'); ruleExecutionLogger.debug( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts index 6e31a9753d38..33800aa30140 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts @@ -14,6 +14,7 @@ import { eqlRuleParams } from '../../rule_schema'; import { eqlExecutor } from '../../signals/executors/eql'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; +import { getActionContext } from '../utils/helpers'; export const createEqlAlertType = ( createOptions: CreateRuleOptions @@ -55,7 +56,7 @@ export const createEqlAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('eql'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index 796900829d6e..30ae2bd8efee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -14,6 +14,7 @@ import { threatRuleParams } from '../../rule_schema'; import { threatMatchExecutor } from '../../signals/executors/threat_match'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; +import { getActionContext } from '../utils/helpers'; export const createIndicatorMatchAlertType = ( createOptions: CreateRuleOptions @@ -56,7 +57,7 @@ export const createIndicatorMatchAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('threat_match'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index c652de348484..8b36b3bca774 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -13,6 +13,7 @@ import type { MachineLearningRuleParams } from '../../rule_schema'; import { machineLearningRuleParams } from '../../rule_schema'; import { mlExecutor } from '../../signals/executors/ml'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; +import { getActionContext } from '../utils/helpers'; export const createMlAlertType = ( createOptions: CreateRuleOptions @@ -43,7 +44,7 @@ export const createMlAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('machine_learning'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts index acfb9f725d3f..d57786203236 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts @@ -41,6 +41,7 @@ import { getUnprocessedExceptionsWarnings, } from '../../signals/utils'; import { createEnrichEventsFunction } from '../../signals/enrichments'; +import { getActionContext } from '../utils/helpers'; export const createNewTermsAlertType = ( createOptions: CreateRuleOptions @@ -86,7 +87,7 @@ export const createNewTermsAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('new_terms'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index 89a43f896a4d..b9d2220e17ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -14,6 +14,7 @@ import { unifiedQueryRuleParams } from '../../rule_schema'; import { queryExecutor } from '../../signals/executors/query'; import type { CreateQueryRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; +import { getActionContext } from '../utils/helpers'; export interface QueryRuleState { suppressionGroupHistory?: BucketHistory[]; @@ -68,7 +69,7 @@ export const createQueryAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('query'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts index b465abed1597..b0338813195d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts @@ -15,6 +15,7 @@ import { thresholdExecutor } from '../../signals/executors/threshold'; import type { ThresholdAlertState } from '../../signals/types'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; +import { getActionContext } from '../utils/helpers'; export const createThresholdAlertType = ( createOptions: CreateRuleOptions @@ -56,7 +57,7 @@ export const createThresholdAlertType = ( ], defaultActionGroupId: 'default', actionVariables: { - context: [{ name: 'server', description: 'the server' }], + context: getActionContext('threshold'), }, minimumLicenseRequired: 'basic', isExportable: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/helpers.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/helpers.ts new file mode 100644 index 000000000000..9c4b81e4f6fc --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/helpers.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { get } from 'lodash'; +import type { ActionVariable, SummarizedAlertsWithAll } from '@kbn/alerting-plugin/common'; +import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; +import type { DetectionAlert860 } from '../../../../../common/detection_engine/schemas/alerts/8.6.0'; +// eslint-disable-next-line no-restricted-imports +import { getNotificationResultsLink } from '../../rule_actions_legacy'; +import type { RuleParams } from '../../rule_schema'; + +const commonRuleParamsKeys = [ + 'id', + 'name', + 'description', + 'false_positives', + 'rule_id', + 'max_signals', + 'risk_score', + 'output_index', + 'references', + 'severity', + 'timeline_id', + 'timeline_title', + 'threat', + 'type', + 'version', +]; + +const queryRuleParams = ['index', 'filters', 'language', 'query', 'saved_id', 'response_actions']; +const machineLearningRuleParams = ['anomaly_threshold', 'machine_learning_job_id']; +const thresholdRuleParams = ['threshold', ...queryRuleParams]; + +const getRuleSpecificRuleParamKeys = (ruleType: Type) => { + switch (ruleType) { + case 'machine_learning': + return machineLearningRuleParams; + case 'threshold': + return thresholdRuleParams; + case 'new_terms': + case 'threat_match': + case 'query': + case 'saved_query': + case 'eql': + return queryRuleParams; + } +}; + +const transformRuleKeysToActionVariables = ( + actionMessageRuleParams: string[] +): ActionVariable[] => { + return [ + { + name: 'results_link', + description: 'context.results_link', + summaryBuilder: (alerts: SummarizedAlertsWithAll) => { + // detection alerts are stored as new alerts + const signals = alerts.new.data as DetectionAlert860[]; + + if (signals.length === 0) return; + + // Get rule info from first signal + const ruleId = signals[0][`kibana.alert.rule.uuid`] as string; + const ruleParameters = signals[0][`kibana.alert.rule.parameters`] as RuleParams; + + // get the time bounds for this alert array + const timestampMillis: number[] = signals + .map((signal: DetectionAlert860) => { + const parsedTime = parseScheduleDates(signal['@timestamp']); + if (parsedTime) { + return parsedTime.valueOf(); + } + return null; + }) + .filter((timeInMillis: number | null) => null != timeInMillis) + .sort() as number[]; + + const link = getNotificationResultsLink({ + from: new Date(timestampMillis[0]).toISOString(), + to: new Date(timestampMillis[timestampMillis.length - 1]).toISOString(), + id: ruleId, + kibanaSiemAppUrl: (ruleParameters?.meta as { kibana_siem_app_url?: string } | undefined) + ?.kibana_siem_app_url, + }); + + return link; + }, + useWithTripleBracesInTemplates: true, + }, + { + name: 'alerts', + description: 'context.alerts', + summaryBuilder: (alerts: SummarizedAlertsWithAll) => { + return alerts.new.data; + }, + }, + ...actionMessageRuleParams.map((param: string) => { + const extendedParam = `rule.${param}`; + return { + name: extendedParam, + description: `context.${extendedParam}`, + summaryBuilder: (alerts: SummarizedAlertsWithAll) => { + // detection alerts are stored as new alerts + const signals = alerts.new.data as DetectionAlert860[]; + + if (signals.length === 0) return; + + // Get rule info from first signal + const ruleParameters = signals[0][`kibana.alert.rule.parameters`] as RuleParams; + const val = get(ruleParameters, param); + return val; + }, + }; + }), + ]; +}; + +export const getActionMessageRuleParams = (ruleType: Type): string[] => { + const ruleParamsKeys = [ + ...commonRuleParamsKeys, + ...getRuleSpecificRuleParamKeys(ruleType), + ].sort(); + + return ruleParamsKeys; +}; + +export const getActionContext = (ruleType: Type | undefined): ActionVariable[] => { + if (!ruleType) { + return []; + } + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + + return transformRuleKeysToActionVariables(actionMessageRuleParams); +};