From 0852428713843a81518a92b2fba6dc1957aa13f2 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:30:46 +0000 Subject: [PATCH] fixes --- .../create_persistence_rule_type_wrapper.ts | 104 +++++++++++++----- .../indicator_match_alert_suppression.ts | 8 +- 2 files changed, 82 insertions(+), 30 deletions(-) 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 2b8c5906c67af..5bf323b25c602 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 @@ -32,6 +32,17 @@ import { CreatePersistenceRuleTypeWrapper } from './persistence_types'; import { errorAggregator } from './utils'; import { AlertWithSuppressionFields870 } from '../../common/schemas/8.7.0'; +/** + * alerts returned from BE have date type coerce to ISO strings + */ +type BackendAlertWithSuppressionFields870 = Omit< + AlertWithSuppressionFields870, + typeof ALERT_SUPPRESSION_START | typeof ALERT_SUPPRESSION_END +> & { + [ALERT_SUPPRESSION_START]: string; + [ALERT_SUPPRESSION_END]: string; +}; + export const ALERT_GROUP_INDEX = `${ALERT_NAMESPACE}.group.index` as const; const augmentAlerts = ({ @@ -121,23 +132,22 @@ const filterDuplicateAlerts = async ({ */ const suppressAlertsInMemory = < T extends { - [ALERT_SUPPRESSION_DOCS_COUNT]: number; - [ALERT_INSTANCE_ID]: string; - [ALERT_SUPPRESSION_START]: Date; - [ALERT_SUPPRESSION_END]: Date; - }, - A extends { _id: string; - _source: T; + _source: { + [ALERT_SUPPRESSION_DOCS_COUNT]: number; + [ALERT_INSTANCE_ID]: string; + [ALERT_SUPPRESSION_START]: Date; + [ALERT_SUPPRESSION_END]: Date; + }; } >( - alerts: A[] + alerts: T[] ): { - alertCandidates: A[]; - suppressedAlerts: A[]; + alertCandidates: T[]; + suppressedAlerts: T[]; } => { const idsMap: Record = {}; - const suppressedAlerts: A[] = []; + const suppressedAlerts: T[] = []; const filteredAlerts = sortBy(alerts, (alert) => alert._source[ALERT_SUPPRESSION_START]).filter( (alert) => { @@ -176,6 +186,49 @@ const suppressAlertsInMemory = < }; }; +/** + * Compare existing alert suppression date props with alert to suppressed alert values + **/ +const isExistingDateGtEqThanAlert = ( + existingAlert: estypes.SearchHit>, + alert: { _id: string; _source: T }, + property: typeof ALERT_SUPPRESSION_END | typeof ALERT_SUPPRESSION_START +) => { + const existingDate = existingAlert?._source?.[property]; + + return existingDate && existingDate >= alert._source[ALERT_SUPPRESSION_END].toISOString(); +}; + +interface SuppressionBoundaries { + [ALERT_SUPPRESSION_END]: Date; + [ALERT_SUPPRESSION_START]: Date; +} + +/** + * returns updated suppression time boundaries + */ +const getUpdatedSuppressionBoundaries = ( + existingAlert: estypes.SearchHit>, + alert: { _id: string; _source: T }, + executionId: string +): Partial => { + const boundaries: Partial = {}; + + if (!isExistingDateGtEqThanAlert(existingAlert, alert, ALERT_SUPPRESSION_END)) { + boundaries[ALERT_SUPPRESSION_END] = alert._source[ALERT_SUPPRESSION_END]; + } + // start date can only be updated for alert created in the same rule execution + // it can happen when alert was created in first bulk created, but some of the alerts can be suppressed in the next bulk create request + if ( + existingAlert?._source?.[ALERT_RULE_EXECUTION_UUID] === executionId && + isExistingDateGtEqThanAlert(existingAlert, alert, ALERT_SUPPRESSION_START) + ) { + boundaries[ALERT_SUPPRESSION_START] = alert._source[ALERT_SUPPRESSION_START]; + } + + return boundaries; +}; + export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper = ({ logger, ruleDataClient, formatAlert }) => (type) => { @@ -379,17 +432,18 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }, }; - // We use AlertWithSuppressionFields870 explicitly here as the type instead of + // We use BackendAlertWithSuppressionFields870 explicitly here as the type instead of // AlertWithSuppressionFieldsLatest since we're reading alerts rather than writing, // so future versions of Kibana may read 8.7.0 version alerts and need to update them const response = await ruleDataClient .getReader({ namespace: options.spaceId }) - .search>( - suppressionAlertSearchRequest - ); + .search< + typeof suppressionAlertSearchRequest, + BackendAlertWithSuppressionFields870<{}> + >(suppressionAlertSearchRequest); const existingAlertsByInstanceId = response.hits.hits.reduce< - Record>> + Record>> >((acc, hit) => { acc[hit._source[ALERT_INSTANCE_ID]] = hit; return acc; @@ -406,16 +460,8 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper ) { return true; } - const suppressionEnd = existingAlert?._source?.[ALERT_SUPPRESSION_END]; - const suppressionEndDate = - typeof suppressionEnd === 'string' - ? suppressionEnd - : suppressionEnd?.toISOString(); - const isSuppressedAlready = - suppressionEndDate && - suppressionEndDate >= alert._source[ALERT_SUPPRESSION_END].toISOString(); - - return !isSuppressedAlready; + + return !isExistingDateGtEqThanAlert(existingAlert, alert, ALERT_SUPPRESSION_END); }); if (nonSuppressedAlerts.length === 0) { @@ -455,8 +501,12 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }, { doc: { + ...getUpdatedSuppressionBoundaries( + existingAlert, + alert, + options.executionId + ), [ALERT_LAST_DETECTED]: currentTimeOverride ?? new Date(), - [ALERT_SUPPRESSION_END]: alert._source[ALERT_SUPPRESSION_END], [ALERT_SUPPRESSION_DOCS_COUNT]: existingDocsCount + alert._source[ALERT_SUPPRESSION_DOCS_COUNT] + 1, }, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/indicator_match_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/indicator_match_alert_suppression.ts index 9055d69b1c15f..9cce698140018 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/indicator_match_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/execution_logic/indicator_match_alert_suppression.ts @@ -2013,8 +2013,8 @@ export default ({ getService }: FtrProviderContext) => { const id = uuidv4(); const timestamp = '2020-10-28T06:45:00.000Z'; - await eventsFiller({ id, count: 20 * eventsCount, timestamp: [timestamp] }); - await threatsFiller({ id, count: 20 * threatsCount, timestamp }); + await eventsFiller({ id, count: 100 * eventsCount, timestamp: [timestamp] }); + await threatsFiller({ id, count: 100 * threatsCount, timestamp }); await indexGeneratedSourceDocuments({ docsCount: 700, @@ -2174,7 +2174,7 @@ export default ({ getService }: FtrProviderContext) => { docsCount: 150, seed: (index) => ({ id, - '@timestamp': `2020-10-28T06:50:00.${index}Z`, + '@timestamp': `2020-10-28T06:50:00.${100 + index}Z`, host: { name: `host-a`, }, @@ -2242,6 +2242,8 @@ export default ({ getService }: FtrProviderContext) => { value: ['agent-a'], }, ], + [ALERT_SUPPRESSION_START]: '2020-10-28T06:50:00.100Z', + [ALERT_SUPPRESSION_END]: '2020-10-28T06:50:00.249Z', // the largest suppression end boundary [ALERT_SUPPRESSION_DOCS_COUNT]: 149, });