import { isRuleType , ruleTypeMappings } from '@kbn/securitysolution-rules' ; import { isString } from 'lodash/fp' ; import { LogMeta , SavedObjectMigrationMap , SavedObjectUnsanitizedDoc , SavedObjectMigrationFn , SavedObjectMigrationContext , SavedObjectAttributes , SavedObjectAttribute , SavedObjectReference , } from '../../../../../src/core/server' ; import { RawRule , RawAlertAction } from '../types' ; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server' ; import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server' ; import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations' ; const SIEM_APP_ID = 'securitySolution' ; const SIEM_SERVER_APP_ID = 'siem' ; export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0' ; export const FILEBEAT_7X_INDICATOR_PATH = 'threatintel.indicator' ; interface AlertLogMeta extends LogMeta { migrations : { alertDocument : SavedObjectUnsanitizedDoc < RawRule > ; } ; } type AlertMigration = ( doc : SavedObjectUnsanitizedDoc < RawRule > ) => SavedObjectUnsanitizedDoc < RawRule > ; function createEsoMigration ( encryptedSavedObjects , isMigrationNeededPredicate , migrationFunc ) { return encryptedSavedObjects . createMigration < RawRule , RawRule > ( { isMigrationNeededPredicate , migration : migrationFunc , shouldMigrateIfDecryptionFails : true , } ) ; } const SUPPORT_INCIDENTS_ACTION_TYPES = [ '.servicenow' , '.jira' , '.resilient' ] ; export const isAnyActionSupportIncidents = ( doc ) : boolean => doc . attributes . actions . some ( ( action ) => SUPPORT_INCIDENTS_ACTION_TYPES . includes ( action . actionTypeId ) ) ; export const isSiemSignalsRuleType = ( doc ) : boolean => doc . attributes . alertTypeId === 'siem.signals' ; param doc returns deprecated export const isSecuritySolutionLegacyNotification = ( doc ) : boolean => doc . attributes . alertTypeId === 'siem.notifications' ; export function getMigrations ( encryptedSavedObjects , isPreconfigured ) { const migrationWhenRBACWasIntroduced = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => true , pipeMigrations ( markAsLegacyAndChangeConsumer , setAlertIdAsDefaultDedupkeyOnPagerDutyActions , initializeExecutionStatus ) ) ; const migrationAlertUpdatedAtAndNotifyWhen = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => true , pipeMigrations ( setAlertUpdatedAtDate , setNotifyWhen ) ) ; const migrationActions7112 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => isAnyActionSupportIncidents ( doc ) , pipeMigrations ( restructureConnectorsThatSupportIncident ) ) ; const migrationSecurityRules713 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => isSiemSignalsRuleType ( doc ) , pipeMigrations ( removeNullsFromSecurityRules ) ) ; const migrationSecurityRules714 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => isSiemSignalsRuleType ( doc ) , pipeMigrations ( removeNullAuthorFromSecurityRules ) ) ; const migrationSecurityRules715 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => isSiemSignalsRuleType ( doc ) , pipeMigrations ( addExceptionListsToReferences ) ) ; const migrateRules716 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => true , pipeMigrations ( setLegacyId , getRemovePreconfiguredConnectorsFromReferencesFn ( isPreconfigured ) , addRuleIdsToLegacyNotificationReferences , extractRefsFromGeoContainmentAlert ) ) ; const migrationRules800 = createEsoMigration ( encryptedSavedObjects , ( doc ) : doc is SavedObjectUnsanitizedDoc < RawRule > => true , pipeMigrations ( addThreatIndicatorPathToThreatMatchRules , addSecuritySolutionAADRuleTypes , fixInventoryThresholdGroupId ) ) ; return { '7.10.0' : executeMigrationWithErrorHandling ( migrationWhenRBACWasIntroduced , '7.10.0' ) , '7.11.0' : executeMigrationWithErrorHandling ( migrationAlertUpdatedAtAndNotifyWhen , '7.11.0' ) , '7.11.2' : executeMigrationWithErrorHandling ( migrationActions7112 , '7.11.2' ) , '7.13.0' : executeMigrationWithErrorHandling ( migrationSecurityRules713 , '7.13.0' ) , '7.14.1' : executeMigrationWithErrorHandling ( migrationSecurityRules714 , '7.14.1' ) , '7.15.0' : executeMigrationWithErrorHandling ( migrationSecurityRules715 , '7.15.0' ) , '7.16.0' : executeMigrationWithErrorHandling ( migrateRules716 , '7.16.0' ) , '8.0.0' : executeMigrationWithErrorHandling ( migrationRules800 , '8.0.0' ) , } ; } function executeMigrationWithErrorHandling ( migrationFunc , version ) { return ( doc , context ) => { try { return migrationFunc ( doc , context ) ; } catch ( ex ) { context . log . error < AlertLogMeta > ( `encryptedSavedObject ${ version } migration failed for alert ${ doc . id } with error: ${ ex . message }` , { migrations : { alertDocument : doc , } , } ) ; throw ex ; } } ; } const setAlertUpdatedAtDate = ( doc ) : SavedObjectUnsanitizedDoc < RawRule > => { const updatedAt = doc . updated_at || doc . attributes . createdAt ; return { ... doc , attributes : { ... doc . attributes , updatedAt , } , } ; } ; const setNotifyWhen = ( doc ) : SavedObjectUnsanitizedDoc < RawRule > => { const notifyWhen = doc . attributes . throttle ? 'onThrottleInterval' : 'onActiveAlert' ; return { ... doc , attributes : { ... doc . attributes , notifyWhen , } , } ; } ; const consumersToChange = new Map ( Object . entries ( { alerting : 'alerts' , metrics : 'infrastructure' , [ SIEM_APP_ID ] : SIEM_SERVER_APP_ID , } ) ) ; function markAsLegacyAndChangeConsumer ( doc ) { const { attributes : { consumer } , } = doc ; return { ... doc , attributes : { ... doc . attributes , consumer : consumersToChange . get ( consumer ) ?? consumer , meta : { versionApiKeyLastmodified : LEGACY_LAST_MODIFIED_VERSION , } , } , } ; } function setAlertIdAsDefaultDedupkeyOnPagerDutyActions ( doc ) { const { attributes } = doc ; return { ... doc , attributes : { ... attributes , ... ( attributes . actions ? { actions : attributes . actions . map ( ( action ) => { if ( action . actionTypeId !== '.pagerduty' || action . params . eventAction === 'trigger' ) { return action ; } return { ... action , params : { ... action . params , dedupKey : action . params . dedupKey ?? '{{alertId}}' , } , } ; } ) , } : { } ) , } , } ; } function initializeExecutionStatus ( doc ) { const { attributes } = doc ; return { ... doc , attributes : { ... attributes , executionStatus : { status : 'pending' , lastExecutionDate : new Date ( ) . toISOString ( ) , error : null , } , } , } ; } function isEmptyObject ( obj ) { for ( const attr in obj ) { if ( Object . prototype . hasOwnProperty . call ( obj , attr ) ) { return false ; } } return true ; } function restructureConnectorsThatSupportIncident ( doc ) { const { actions } = doc . attributes ; const newActions = actions . reduce ( ( acc , action ) => { if ( [ '.servicenow' , '.jira' , '.resilient' ] . includes ( action . actionTypeId ) && action . params . subAction === 'pushToService' ) { const subActionParamsIncident = ( action . params ?. subActionParams as SavedObjectAttributes ) ?. incident ?? null ; if ( subActionParamsIncident != null && ! isEmptyObject ( subActionParamsIncident ) ) { return [ ... acc , action ] ; } if ( action . actionTypeId === '.servicenow' ) { const { title , comments , comment , description , severity , urgency , impact , short_description : shortDescription , } = action . params . subActionParams as { title : string ; description ? : string ; severity ? : string ; urgency ? : string ; impact ? : string ; comment ? : string ; comments ? : Array < { commentId : string ; comment : string ; } > ; short_description ? : string ; } ; return [ ... acc , { ... action , params : { subAction : 'pushToService' , subActionParams : { incident : { short_description : shortDescription ?? title , description , severity , urgency , impact , } , comments : [ ... ( comments ?? [ ] ) , ... ( comment != null ? [ { commentId : '1' , comment } ] : [ ] ) , ] , } , } , } , ] as RawAlertAction [ ] ; } else if ( action . actionTypeId === '.jira' ) { const { title , comments , description , issueType , priority , labels , parent , summary } = action . params . subActionParams as { title : string ; description : string ; issueType : string ; priority ? : string ; labels ? : string [ ] ; parent ? : string ; comments ? : unknown [ ] ; summary ? : string ; } ; return [ ... acc , { ... action , params : { subAction : 'pushToService' , subActionParams : { incident : { summary : summary ?? title , description , issueType , priority , labels , parent , } , comments , } , } , } , ] as RawAlertAction [ ] ; } else if ( action . actionTypeId === '.resilient' ) { const { title , comments , description , incidentTypes , severityCode , name } = action . params . subActionParams as { title : string ; description : string ; incidentTypes ? : number [ ] ; severityCode ? : number ; comments ? : unknown [ ] ; name ? : string ; } ; return [ ... acc , { ... action , params : { subAction : 'pushToService' , subActionParams : { incident : { name : name ?? title , description , incidentTypes , severityCode , } , comments , } , } , } , ] as RawAlertAction [ ] ; } } return [ ... acc , action ] ; } , [ ] as RawAlertAction [ ] ) ; return { ... doc , attributes : { ... doc . attributes , actions : newActions , } , } ; } function convertNullToUndefined ( attribute ) { return attribute != null ? attribute : undefined ; } function removeNullsFromSecurityRules ( doc ) { const { attributes : { params } , } = doc ; return { ... doc , attributes : { ... doc . attributes , params : { ... params , buildingBlockType : convertNullToUndefined ( params . buildingBlockType ) , note : convertNullToUndefined ( params . note ) , index : convertNullToUndefined ( params . index ) , language : convertNullToUndefined ( params . language ) , license : convertNullToUndefined ( params . license ) , outputIndex : convertNullToUndefined ( params . outputIndex ) , savedId : convertNullToUndefined ( params . savedId ) , timelineId : convertNullToUndefined ( params . timelineId ) , timelineTitle : convertNullToUndefined ( params . timelineTitle ) , meta : convertNullToUndefined ( params . meta ) , query : convertNullToUndefined ( params . query ) , filters : convertNullToUndefined ( params . filters ) , riskScoreMapping : params . riskScoreMapping != null ? params . riskScoreMapping : [ ] , ruleNameOverride : convertNullToUndefined ( params . ruleNameOverride ) , severityMapping : params . severityMapping != null ? params . severityMapping : [ ] , threat : params . threat != null ? params . threat : [ ] , threshold : params . threshold != null && typeof params . threshold === 'object' && ! Array . isArray ( params . threshold ) ? { field : Array . isArray ( params . threshold . field ) ? params . threshold . field : params . threshold . field === '' || params . threshold . field == null ? [ ] : [ params . threshold . field ] , value : params . threshold . value , cardinality : params . threshold . cardinality != null ? params . threshold . cardinality : [ ] , } : undefined , timestampOverride : convertNullToUndefined ( params . timestampOverride ) , exceptionsList : params . exceptionsList != null ? params . exceptionsList : params . exceptions_list != null ? params . exceptions_list : params . lists != null ? params . lists : [ ] , threatFilters : convertNullToUndefined ( params . threatFilters ) , machineLearningJobId : params . machineLearningJobId == null ? undefined : Array . isArray ( params . machineLearningJobId ) ? params . machineLearningJobId : [ params . machineLearningJobId ] , } , } , } ; } param doc returns function removeNullAuthorFromSecurityRules ( doc ) { const { attributes : { params } , } = doc ; return { ... doc , attributes : { ... doc . attributes , params : { ... params , author : params . author != null ? params . author : [ ] , } , } , } ; } param doc returns function addExceptionListsToReferences ( doc ) { const { attributes : { params : { exceptionsList } , } , references , } = doc ; if ( ! Array . isArray ( exceptionsList ) ) { return doc ; } else { const exceptionsToTransform = removeMalformedExceptionsList ( exceptionsList ) ; const newReferences = exceptionsToTransform . flatMap < SavedObjectReference > ( ( exceptionItem , index ) => { const existingReferenceFound = references ?. find ( ( reference ) => { return ( reference . id === exceptionItem . id && ( ( reference . type === 'exception-list' && exceptionItem . namespace_type === 'single' ) || ( reference . type === 'exception-list-agnostic' && exceptionItem . namespace_type === 'agnostic' ) ) ) ; } ) ; if ( existingReferenceFound ) { return [ ] ; } else { return [ { name : `param:exceptionsList_${ index }` , id : String ( exceptionItem . id ) , type : exceptionItem . namespace_type === 'agnostic' ? 'exception-list-agnostic' : 'exception-list' , } , ] ; } } ) ; if ( references == null && newReferences . length === 0 ) { return doc ; } else { return { ... doc , references : [ ... ( references ?? [ ] ) , ... newReferences ] } ; } } } param exceptionsList returns function removeMalformedExceptionsList ( exceptionsList ) { if ( ! Array . isArray ( exceptionsList ) ) { return [ ] ; } else { return exceptionsList . flatMap ( ( exceptionItem ) => { if ( ! ( exceptionItem instanceof Object ) || ! isString ( exceptionItem . id ) ) { return [ ] ; } else { return [ exceptionItem ] ; } } ) ; } } deprecated param doc returns function addRuleIdsToLegacyNotificationReferences ( doc ) { const { attributes : { params : { ruleAlertId } , } , references , } = doc ; if ( ! isSecuritySolutionLegacyNotification ( doc ) || ! isString ( ruleAlertId ) ) { return doc ; } else { const existingReferences = references ?? [ ] ; const existingReferenceFound = existingReferences . find ( ( reference ) => { return reference . id === ruleAlertId && reference . type === 'alert' ; } ) ; if ( existingReferenceFound ) { return doc ; } else { const savedObjectReference = { id : ruleAlertId , name : 'param:alert_0' , type : 'alert' , } ; const newReferences = [ ... existingReferences , savedObjectReference ] ; return { ... doc , references : newReferences } ; } } } function setLegacyId ( doc ) { const { id } = doc ; return { ... doc , attributes : { ... doc . attributes , legacyId : id , } , } ; } function addSecuritySolutionAADRuleTypes ( doc ) { const ruleType = doc . attributes . params . type ; return isSiemSignalsRuleType ( doc ) && isRuleType ( ruleType ) ? { ... doc , attributes : { ... doc . attributes , alertTypeId : ruleTypeMappings [ ruleType ] , enabled : false , params : { ... doc . attributes . params , outputIndex : '' , } , } , } : doc ; } function addThreatIndicatorPathToThreatMatchRules ( doc ) { return isSiemSignalsRuleType ( doc ) && doc . attributes . params ?. type === 'threat_match' && ! doc . attributes . params . threatIndicatorPath ? { ... doc , attributes : { ... doc . attributes , params : { ... doc . attributes . params , threatIndicatorPath : FILEBEAT_7X_INDICATOR_PATH , } , } , } : doc ; } function getRemovePreconfiguredConnectorsFromReferencesFn ( isPreconfigured ) { return ( doc ) => { return removePreconfiguredConnectorsFromReferences ( doc , isPreconfigured ) ; } ; } function removePreconfiguredConnectorsFromReferences ( doc , isPreconfigured ) { const { attributes : { actions } , references , } = doc ; const connectorReferences = ( references ?? [ ] ) . filter ( ( ref ) => ref . name . startsWith ( 'action_' ) ) ; if ( connectorReferences . length > 0 ) { const restReferences = ( references ?? [ ] ) . filter ( ( ref ) => ! ref . name . startsWith ( 'action_' ) ) ; const updatedConnectorReferences = [ ] ; const updatedActions = [ ] ; connectorReferences . forEach ( ( connectorRef ) => { const correspondingAction = getCorrespondingAction ( actions , connectorRef . name ) ; if ( correspondingAction ) { if ( isPreconfigured ( connectorRef . id ) ) { updatedActions . push ( { ... correspondingAction , actionRef : `preconfigured:${ connectorRef . id }` , } ) ; } else { updatedActions . push ( correspondingAction ) ; updatedConnectorReferences . push ( connectorRef ) ; } } else { updatedConnectorReferences . push ( connectorRef ) ; } } ) ; return { ... doc , attributes : { ... doc . attributes , actions : [ ... updatedActions ] , } , references : [ ... updatedConnectorReferences , ... restReferences ] , } ; } return doc ; } function fixInventoryThresholdGroupId ( doc ) { if ( doc . attributes . alertTypeId === 'metrics.alert.inventory.threshold' ) { const { attributes : { actions } , } = doc ; const updatedActions = actions ? actions . map ( ( action ) => { if ( action . group === 'metrics.invenotry_threshold.fired' ) { return { ... action , group : 'metrics.inventory_threshold.fired' , } ; } else { return action ; } } ) : [ ] ; return { ... doc , attributes : { ... doc . attributes , actions : updatedActions , } , } ; } else { return doc ; } } function getCorrespondingAction ( actions , connectorRef ) { if ( ! Array . isArray ( actions ) ) { return null ; } else { return actions . find ( ( action ) => ( action as RawAlertAction ) ?. actionRef === connectorRef ) as RawAlertAction ; } } function pipeMigrations ( ... migrations ) { return ( doc ) => migrations . reduce ( ( migratedDoc , nextMigration ) => nextMigration ( migratedDoc ) , doc ) ; }