diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index 9ac9c4a057ee..a3cd83f6ba67 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -217,3 +217,13 @@ export interface ShardFailure { }; shard: number; } + +export function isSerializedSearchSource( + maybeSerializedSearchSource: unknown +): maybeSerializedSearchSource is SerializedSearchSourceFields { + return ( + typeof maybeSerializedSearchSource === 'object' && + maybeSerializedSearchSource !== null && + !Array.isArray(maybeSerializedSearchSource) + ); +} diff --git a/src/plugins/discover/server/saved_objects/search_migrations.test.ts b/src/plugins/discover/server/saved_objects/search_migrations.test.ts index 9563bd6dc86c..fcce5d41fe90 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.test.ts @@ -350,6 +350,7 @@ Object { testMigrateMatchAllQuery(migrationFn); }); }); + it('should apply search source migrations within saved search', () => { const savedSearch = { attributes: { @@ -379,4 +380,27 @@ Object { }, }); }); + + it('should not apply search source migrations within saved search when searchSourceJSON is not an object', () => { + const savedSearch = { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '5', + }, + }, + } as SavedObjectUnsanitizedDoc; + + const versionToTest = '9.1.2'; + const migrations = getAllMigrations({ + [versionToTest]: (state) => ({ ...state, migrated: true }), + }); + + expect(migrations[versionToTest](savedSearch, {} as SavedObjectMigrationContext)).toEqual({ + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '5', + }, + }, + }); + }); }); diff --git a/src/plugins/discover/server/saved_objects/search_migrations.ts b/src/plugins/discover/server/saved_objects/search_migrations.ts index 95da82fa38ac..2fb49628f53b 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.ts @@ -17,7 +17,7 @@ import type { import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; import { DEFAULT_QUERY_LANGUAGE } from '@kbn/data-plugin/server'; import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; -import type { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { isSerializedSearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; export interface SavedSearchMigrationAttributes extends SavedObjectAttributes { kibanaSavedObjectMeta: { @@ -135,27 +135,31 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) = /** * This creates a migration map that applies search source migrations */ -const getSearchSourceMigrations = (searchSourceMigrations: MigrateFunctionsObject) => +const getSearchSourceMigrations = ( + searchSourceMigrations: MigrateFunctionsObject +): MigrateFunctionsObject => mapValues( searchSourceMigrations, (migrate: MigrateFunction): MigrateFunction => (state) => { - const _state = state as unknown as { attributes: SavedSearchMigrationAttributes }; + const _state = state as { attributes: SavedSearchMigrationAttributes }; - const parsedSearchSourceJSON = _state.attributes.kibanaSavedObjectMeta.searchSourceJSON; - - if (!parsedSearchSourceJSON) return _state; - - return { - ..._state, - attributes: { - ..._state.attributes, - kibanaSavedObjectMeta: { - ..._state.attributes.kibanaSavedObjectMeta, - searchSourceJSON: JSON.stringify(migrate(JSON.parse(parsedSearchSourceJSON))), + const parsedSearchSourceJSON = JSON.parse( + _state.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + if (isSerializedSearchSource(parsedSearchSourceJSON)) { + return { + ..._state, + attributes: { + ..._state.attributes, + kibanaSavedObjectMeta: { + ..._state.attributes.kibanaSavedObjectMeta, + searchSourceJSON: JSON.stringify(migrate(parsedSearchSourceJSON)), + }, }, - }, - }; + }; + } + return _state; } ); @@ -171,6 +175,6 @@ export const getAllMigrations = ( ): SavedObjectMigrationMap => { return mergeSavedObjectMigrationMaps( searchMigrations, - getSearchSourceMigrations(searchSourceMigrations) as unknown as SavedObjectMigrationMap + getSearchSourceMigrations(searchSourceMigrations) as SavedObjectMigrationMap ); }; diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts index 99dbf548e6f4..19f117ec18cc 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts @@ -2470,6 +2470,31 @@ describe('migration visualization', () => { }); }); + it('should not apply search source migrations within visualization when searchSourceJSON is not an object', () => { + const visualizationDoc = { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '5', + }, + }, + } as SavedObjectUnsanitizedDoc; + + const versionToTest = '1.2.4'; + const visMigrations = getAllMigrations({ + [versionToTest]: (state) => ({ ...state, migrated: true }), + }); + + expect( + visMigrations[versionToTest](visualizationDoc, {} as SavedObjectMigrationContext) + ).toEqual({ + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: '5', + }, + }, + }); + }); + describe('8.1.0 pie - labels and addLegend migration', () => { const getDoc = (addLegend: boolean, lastLevel: boolean = false) => ({ attributes: { diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts index 4b729afa6230..d236ad83c853 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts @@ -11,7 +11,11 @@ import type { SavedObjectMigrationFn, SavedObjectMigrationMap } from '@kbn/core/ import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; -import { DEFAULT_QUERY_LANGUAGE, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { + DEFAULT_QUERY_LANGUAGE, + isSerializedSearchSource, + SerializedSearchSourceFields, +} from '@kbn/data-plugin/common'; import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; import { commonAddSupportOfDualIndexSelectionModeInTSVB, @@ -1215,27 +1219,31 @@ const visualizationSavedObjectTypeMigrations = { /** * This creates a migration map that applies search source migrations to legacy visualization SOs */ -const getVisualizationSearchSourceMigrations = (searchSourceMigrations: MigrateFunctionsObject) => +const getVisualizationSearchSourceMigrations = ( + searchSourceMigrations: MigrateFunctionsObject +): MigrateFunctionsObject => mapValues( searchSourceMigrations, (migrate: MigrateFunction): MigrateFunction => (state) => { - const _state = state as unknown as { attributes: VisualizationSavedObjectAttributes }; + const _state = state as { attributes: VisualizationSavedObjectAttributes }; - const parsedSearchSourceJSON = _state.attributes.kibanaSavedObjectMeta.searchSourceJSON; - - if (!parsedSearchSourceJSON) return _state; - - return { - ..._state, - attributes: { - ..._state.attributes, - kibanaSavedObjectMeta: { - ..._state.attributes.kibanaSavedObjectMeta, - searchSourceJSON: JSON.stringify(migrate(JSON.parse(parsedSearchSourceJSON))), + const parsedSearchSourceJSON = JSON.parse( + _state.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + if (isSerializedSearchSource(parsedSearchSourceJSON)) { + return { + ..._state, + attributes: { + ..._state.attributes, + kibanaSavedObjectMeta: { + ..._state.attributes.kibanaSavedObjectMeta, + searchSourceJSON: JSON.stringify(migrate(parsedSearchSourceJSON)), + }, }, - }, - }; + }; + } + return _state; } ); @@ -1244,7 +1252,5 @@ export const getAllMigrations = ( ): SavedObjectMigrationMap => mergeSavedObjectMigrationMaps( visualizationSavedObjectTypeMigrations, - getVisualizationSearchSourceMigrations( - searchSourceMigrations - ) as unknown as SavedObjectMigrationMap + getVisualizationSearchSourceMigrations(searchSourceMigrations) as SavedObjectMigrationMap ); diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index b342eddaa0c1..5eba1353df21 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -21,6 +21,7 @@ import { eventLogMock } from '@kbn/event-log-plugin/server/mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { monitoringCollectionMock } from '@kbn/monitoring-collection-plugin/server/mocks'; +import { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; import { spacesMock } from '@kbn/spaces-plugin/server/mocks'; const generateAlertingConfig = (): AlertingConfig => ({ @@ -66,6 +67,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), monitoringCollection: monitoringCollectionMock.createSetup(), + data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, }; let plugin: AlertingPlugin; @@ -207,6 +209,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), monitoringCollection: monitoringCollectionMock.createSetup(), + data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, }); const startContract = plugin.start(coreMock.createStart(), { @@ -246,6 +249,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), monitoringCollection: monitoringCollectionMock.createSetup(), + data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, }); const startContract = plugin.start(coreMock.createStart(), { @@ -296,6 +300,7 @@ describe('Alerting Plugin', () => { actions: actionsMock.createSetup(), statusService: statusServiceMock.createSetupContract(), monitoringCollection: monitoringCollectionMock.createSetup(), + data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, }); const startContract = plugin.start(coreMock.createStart(), { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 6589b1537f76..063c221ea98d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -10,6 +10,7 @@ import { BehaviorSubject } from 'rxjs'; import { pick } from 'lodash'; import { UsageCollectionSetup, UsageCounter } from '@kbn/usage-collection-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart, @@ -140,6 +141,7 @@ export interface AlertingPluginsSetup { eventLog: IEventLogService; statusService: StatusServiceSetup; monitoringCollection: MonitoringCollectionSetup; + data: DataPluginSetup; } export interface AlertingPluginsStart { @@ -247,12 +249,16 @@ export class AlertingPlugin { // Usage counter for telemetry this.usageCounter = plugins.usageCollection?.createUsageCounter(ALERTS_FEATURE_ID); + const getSearchSourceMigrations = plugins.data.search.searchSource.getAllMigrations.bind( + plugins.data.search.searchSource + ); setupSavedObjects( core.savedObjects, plugins.encryptedSavedObjects, this.ruleTypeRegistry, this.logger, - plugins.actions.isPreconfiguredConnector + plugins.actions.isPreconfiguredConnector, + getSearchSourceMigrations ); initializeApiKeyInvalidator( diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 85e4dc5a8e05..6566fee15d4a 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -12,6 +12,7 @@ import type { SavedObjectsServiceSetup, } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; +import { MigrateFunctionsObject } from '@kbn/kibana-utils-plugin/common'; import { alertMappings } from './mappings'; import { getMigrations } from './migrations'; import { transformRulesForExport } from './transform_rule_for_export'; @@ -51,14 +52,15 @@ export function setupSavedObjects( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, ruleTypeRegistry: RuleTypeRegistry, logger: Logger, - isPreconfigured: (connectorId: string) => boolean + isPreconfigured: (connectorId: string) => boolean, + getSearchSourceMigrations: () => MigrateFunctionsObject ) { savedObjects.registerType({ name: 'alert', hidden: true, namespaceType: 'multiple-isolated', convertToMultiNamespaceTypeVersion: '8.0.0', - migrations: getMigrations(encryptedSavedObjects, isPreconfigured), + migrations: getMigrations(encryptedSavedObjects, getSearchSourceMigrations(), isPreconfigured), mappings: alertMappings, management: { displayName: 'rule', diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 921412d4e79e..c83d0a95dfdc 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { getMigrations, isAnyActionSupportIncidents } from './migrations'; import { RawRule } from '../types'; -import { SavedObjectUnsanitizedDoc } from '@kbn/core/server'; +import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { migrationMocks } from '@kbn/core/server/mocks'; import { RuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; @@ -25,7 +25,7 @@ describe('successful migrations', () => { }); describe('7.10.0', () => { test('marks alerts as legacy', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({}); expect(migration710(alert, migrationContext)).toMatchObject({ ...alert, @@ -39,7 +39,7 @@ describe('successful migrations', () => { }); test('migrates the consumer for metrics', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ consumer: 'metrics', }); @@ -56,7 +56,7 @@ describe('successful migrations', () => { }); test('migrates the consumer for siem', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ consumer: 'securitySolution', }); @@ -73,7 +73,7 @@ describe('successful migrations', () => { }); test('migrates the consumer for alerting', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ consumer: 'alerting', }); @@ -90,7 +90,7 @@ describe('successful migrations', () => { }); test('migrates PagerDuty actions to set a default dedupkey of the AlertId', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ actions: [ { @@ -127,7 +127,7 @@ describe('successful migrations', () => { }); test('skips PagerDuty actions with a specified dedupkey', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ actions: [ { @@ -165,7 +165,7 @@ describe('successful migrations', () => { }); test('skips PagerDuty actions with an eventAction of "trigger"', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ actions: [ { @@ -204,7 +204,7 @@ describe('successful migrations', () => { }); test('creates execution status', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData(); const dateStart = Date.now(); const migratedAlert = migration710(alert, migrationContext); @@ -232,7 +232,7 @@ describe('successful migrations', () => { describe('7.11.0', () => { test('add updatedAt field to alert - set to SavedObject updated_at attribute', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.0']; + const migration711 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.11.0']; const alert = getMockData({}, true); expect(migration711(alert, migrationContext)).toEqual({ ...alert, @@ -245,7 +245,7 @@ describe('successful migrations', () => { }); test('add updatedAt field to alert - set to createdAt when SavedObject updated_at is not defined', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.0']; + const migration711 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.11.0']; const alert = getMockData({}); expect(migration711(alert, migrationContext)).toEqual({ ...alert, @@ -258,7 +258,7 @@ describe('successful migrations', () => { }); test('add notifyWhen=onActiveAlert when throttle is null', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.0']; + const migration711 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.11.0']; const alert = getMockData({}); expect(migration711(alert, migrationContext)).toEqual({ ...alert, @@ -271,7 +271,7 @@ describe('successful migrations', () => { }); test('add notifyWhen=onActiveAlert when throttle is set', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.0']; + const migration711 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.11.0']; const alert = getMockData({ throttle: '5m' }); expect(migration711(alert, migrationContext)).toEqual({ ...alert, @@ -286,7 +286,9 @@ describe('successful migrations', () => { describe('7.11.2', () => { test('transforms connectors that support incident correctly', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -428,7 +430,9 @@ describe('successful migrations', () => { }); test('it transforms only subAction=pushToService', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -447,7 +451,9 @@ describe('successful migrations', () => { }); test('it does not transforms other connectors', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -526,7 +532,9 @@ describe('successful migrations', () => { }); test('it does not transforms alerts when the right structure connectors is already applied', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -563,7 +571,9 @@ describe('successful migrations', () => { }); test('if incident attribute is an empty object, copy back the related attributes from subActionParams back to incident', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -625,7 +635,9 @@ describe('successful migrations', () => { }); test('custom action does not get migrated/loss', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ actions: [ { @@ -654,7 +666,7 @@ describe('successful migrations', () => { describe('7.13.0', () => { test('security solution alerts get migrated and remove null values', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -748,7 +760,7 @@ describe('successful migrations', () => { }); test('non-null values in security solution alerts are not modified', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -815,7 +827,7 @@ describe('successful migrations', () => { }); test('security solution threshold alert with string in threshold.field is migrated to array', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -846,7 +858,7 @@ describe('successful migrations', () => { }); test('security solution threshold alert with empty string in threshold.field is migrated to empty array', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -877,7 +889,7 @@ describe('successful migrations', () => { }); test('security solution threshold alert with array in threshold.field and cardinality is left alone', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -919,7 +931,7 @@ describe('successful migrations', () => { }); test('security solution ML alert with string in machineLearningJobId is converted to an array', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -945,7 +957,7 @@ describe('successful migrations', () => { }); test('security solution ML alert with an array in machineLearningJobId is preserved', () => { - const migration713 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration713 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.13.0']; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -973,7 +985,9 @@ describe('successful migrations', () => { describe('7.14.1', () => { test('security solution author field is migrated to array if it is undefined', () => { - const migration7141 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.14.1']; + const migration7141 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.14.1' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: {}, @@ -991,7 +1005,9 @@ describe('successful migrations', () => { }); test('security solution author field does not override existing values if they exist', () => { - const migration7141 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.14.1']; + const migration7141 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.14.1' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -1015,7 +1031,9 @@ describe('successful migrations', () => { describe('7.15.0', () => { test('security solution is migrated to saved object references if it has 1 exceptionsList', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -1044,7 +1062,9 @@ describe('successful migrations', () => { }); test('security solution is migrated to saved object references if it has 2 exceptionsLists', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -1084,7 +1104,9 @@ describe('successful migrations', () => { }); test('security solution is migrated to saved object references if it has 3 exceptionsLists', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -1135,7 +1157,9 @@ describe('successful migrations', () => { }); test('security solution does not change anything if exceptionsList is missing', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = getMockData({ alertTypeId: 'siem.signals', params: { @@ -1147,7 +1171,9 @@ describe('successful migrations', () => { }); test('security solution will keep existing references if we do not have an exceptionsList but we do already have references', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1177,7 +1203,9 @@ describe('successful migrations', () => { }); test('security solution keep any foreign references if they exist but still migrate other references', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1242,7 +1270,9 @@ describe('successful migrations', () => { }); test('security solution is idempotent and if re-run on the same migrated data will keep the same items', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1282,7 +1312,9 @@ describe('successful migrations', () => { }); test('security solution will migrate with only missing data if we have partially migrated data', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1331,7 +1363,9 @@ describe('successful migrations', () => { }); test('security solution will not migrate if exception list if it is invalid data', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1345,7 +1379,9 @@ describe('successful migrations', () => { }); test('security solution will migrate valid data if it is mixed with invalid data', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1387,7 +1423,9 @@ describe('successful migrations', () => { }); test('security solution will not migrate if exception list is invalid data but will keep existing references', () => { - const migration7150 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.15.0']; + const migration7150 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.15.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.signals', @@ -1419,7 +1457,7 @@ describe('successful migrations', () => { describe('7.16.0', () => { test('add legacyId field to alert - set to SavedObject id attribute', () => { - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const alert = getMockData({}, true); expect(migration716(alert, migrationContext)).toEqual({ ...alert, @@ -1434,7 +1472,7 @@ describe('successful migrations', () => { isPreconfigured.mockReset(); isPreconfigured.mockReturnValueOnce(true); isPreconfigured.mockReturnValueOnce(false); - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const rule = { ...getMockData({ actions: [ @@ -1510,7 +1548,7 @@ describe('successful migrations', () => { isPreconfigured.mockReturnValueOnce(true); isPreconfigured.mockReturnValueOnce(false); isPreconfigured.mockReturnValueOnce(false); - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const rule = { ...getMockData({ actions: [ @@ -1593,7 +1631,7 @@ describe('successful migrations', () => { test('does nothing to rules with no references', () => { isPreconfigured.mockReset(); - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const rule = { ...getMockData({ actions: [ @@ -1629,7 +1667,7 @@ describe('successful migrations', () => { test('does nothing to rules with no action references', () => { isPreconfigured.mockReset(); - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const rule = { ...getMockData({ actions: [ @@ -1671,7 +1709,7 @@ describe('successful migrations', () => { test('does nothing to rules with references but no actions', () => { isPreconfigured.mockReset(); - const migration716 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration716 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.16.0']; const rule = { ...getMockData({ actions: [], @@ -1699,7 +1737,9 @@ describe('successful migrations', () => { }); test('security solution is migrated to saved object references if it has a "ruleAlertId"', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = getMockData({ alertTypeId: 'siem.notifications', params: { @@ -1724,7 +1764,9 @@ describe('successful migrations', () => { }); test('security solution does not migrate anything if its type is not siem.notifications', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = getMockData({ alertTypeId: 'other-type', params: { @@ -1741,7 +1783,9 @@ describe('successful migrations', () => { }); }); test('security solution does not change anything if "ruleAlertId" is missing', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = getMockData({ alertTypeId: 'siem.notifications', params: {}, @@ -1757,7 +1801,9 @@ describe('successful migrations', () => { }); test('security solution will keep existing references if we do not have a "ruleAlertId" but we do already have references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.notifications', @@ -1789,7 +1835,9 @@ describe('successful migrations', () => { }); test('security solution will keep any foreign references if they exist but still migrate other "ruleAlertId" references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.notifications', @@ -1828,7 +1876,9 @@ describe('successful migrations', () => { }); test('security solution is idempotent and if re-run on the same migrated data will keep the same items "ruleAlertId" references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.notifications', @@ -1862,7 +1912,9 @@ describe('successful migrations', () => { }); test('security solution will not migrate "ruleAlertId" if it is invalid data', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.notifications', @@ -1882,7 +1934,9 @@ describe('successful migrations', () => { }); test('security solution will not migrate "ruleAlertId" if it is invalid data but will keep existing references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: 'siem.notifications', @@ -1916,7 +1970,9 @@ describe('successful migrations', () => { }); test('geo-containment alert migration extracts boundary and index references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: '.geo-containment', @@ -1944,7 +2000,9 @@ describe('successful migrations', () => { }); test('geo-containment alert migration should preserve foreign references', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: '.geo-containment', @@ -1984,7 +2042,9 @@ describe('successful migrations', () => { }); test('geo-containment alert migration ignores other alert-types', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const alert = { ...getMockData({ alertTypeId: '.foo', @@ -2008,13 +2068,13 @@ describe('successful migrations', () => { describe('8.0.0', () => { test('no op migration for rules SO', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData({}, true); expect(migration800(alert, migrationContext)).toEqual(alert); }); test('add threatIndicatorPath default value to threat match rules if missing', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData( { params: { type: 'threat_match' }, alertTypeId: 'siem.signals' }, true @@ -2025,7 +2085,7 @@ describe('successful migrations', () => { }); test('doesnt change threatIndicatorPath value in threat match rules if value is present', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData( { params: { type: 'threat_match', threatIndicatorPath: 'custom.indicator.path' }, @@ -2039,7 +2099,7 @@ describe('successful migrations', () => { }); test('doesnt change threatIndicatorPath value in other rules', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData({ params: { type: 'eql' }, alertTypeId: 'siem.signals' }, true); expect(migration800(alert, migrationContext).attributes.params.threatIndicatorPath).toEqual( undefined @@ -2047,7 +2107,7 @@ describe('successful migrations', () => { }); test('doesnt change threatIndicatorPath value if not a siem.signals rule', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData( { params: { type: 'threat_match' }, alertTypeId: 'not.siem.signals' }, true @@ -2058,7 +2118,7 @@ describe('successful migrations', () => { }); test('doesnt change AAD rule params if not a siem.signals rule', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['8.0.0']; const alert = getMockData( { params: { outputIndex: 'output-index', type: 'query' }, alertTypeId: 'not.siem.signals' }, true @@ -2073,7 +2133,9 @@ describe('successful migrations', () => { test.each(Object.keys(ruleTypeMappings) as RuleType[])( 'changes AAD rule params accordingly if rule is a siem.signals %p rule', (ruleType) => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.0.0' + ]; const alert = getMockData( { params: { outputIndex: 'output-index', type: ruleType }, alertTypeId: 'siem.signals' }, true @@ -2118,7 +2180,7 @@ describe('successful migrations', () => { ); test('Does not update rule tags if rule has already been enabled', () => { - const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migrations = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured); const migration800 = migrations['8.0.0']; const migration801 = migrations['8.0.1']; @@ -2141,7 +2203,7 @@ describe('successful migrations', () => { }); test('Does not update rule tags if rule was already disabled before upgrading to 8.0', () => { - const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migrations = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured); const migration800 = migrations['8.0.0']; const migration801 = migrations['8.0.1']; @@ -2161,7 +2223,7 @@ describe('successful migrations', () => { }); test('Updates rule tags if rule was auto-disabled in 8.0 upgrade and not reenabled', () => { - const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migrations = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured); const migration800 = migrations['8.0.0']; const migration801 = migrations['8.0.1']; @@ -2181,7 +2243,7 @@ describe('successful migrations', () => { }); test('Updates rule tags correctly if tags are undefined', () => { - const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migrations = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured); const migration801 = migrations['8.0.1']; const alert = { @@ -2204,7 +2266,7 @@ describe('successful migrations', () => { }); test('Updates rule tags correctly if tags are null', () => { - const migrations = getMigrations(encryptedSavedObjectsSetup, isPreconfigured); + const migrations = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured); const migration801 = migrations['8.0.1']; const alert = { @@ -2231,7 +2293,9 @@ describe('successful migrations', () => { describe('8.2.0', () => { test('migrates params to mapped_params', () => { - const migration820 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.2.0']; + const migration820 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.2.0' + ]; const alert = getMockData( { params: { @@ -2254,8 +2318,29 @@ describe('successful migrations', () => { }); describe('8.3.0', () => { + test('migrates es_query alert params', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.3.0' + ]; + const alert = getMockData( + { + params: { esQuery: '{ "query": "test-query" }' }, + alertTypeId: '.es-query', + }, + true + ); + const migratedAlert820 = migration830(alert, migrationContext); + + expect(migratedAlert820.attributes.params).toEqual({ + esQuery: '{ "query": "test-query" }', + searchType: 'esQuery', + }); + }); + test('removes internal tags', () => { - const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const migration830 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.3.0' + ]; const alert = getMockData( { tags: [ @@ -2274,7 +2359,9 @@ describe('successful migrations', () => { }); test('do not remove internal tags if rule is not Security solution rule', () => { - const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const migration830 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.3.0' + ]; const alert = getMockData( { tags: ['__internal_immutable:false', 'tag-1'], @@ -2290,7 +2377,9 @@ describe('successful migrations', () => { describe('Metrics Inventory Threshold rule', () => { test('Migrates incorrect action group spelling', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.0.0' + ]; const actions = [ { @@ -2317,7 +2406,9 @@ describe('successful migrations', () => { }); test('Works with the correct action group spelling', () => { - const migration800 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.0.0']; + const migration800 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '8.0.0' + ]; const actions = [ { @@ -2346,6 +2437,72 @@ describe('successful migrations', () => { }); }); +describe('search source migration', () => { + it('should apply migration within es query alert rule', () => { + const esQueryRuleSavedObject = { + attributes: { + params: { + searchConfiguration: { + some: 'prop', + migrated: false, + }, + }, + }, + } as SavedObjectUnsanitizedDoc; + + const versionToTest = '9.1.3'; + const migrations = getMigrations( + encryptedSavedObjectsSetup, + { + [versionToTest]: (state) => ({ ...state, migrated: true }), + }, + isPreconfigured + ); + + expect( + migrations[versionToTest](esQueryRuleSavedObject, {} as SavedObjectMigrationContext) + ).toEqual({ + attributes: { + params: { + searchConfiguration: { + some: 'prop', + migrated: true, + }, + }, + }, + }); + }); + + it('should not apply migration within es query alert rule when searchConfiguration not an object', () => { + const esQueryRuleSavedObject = { + attributes: { + params: { + searchConfiguration: 5, + }, + }, + } as SavedObjectUnsanitizedDoc; + + const versionToTest = '9.1.4'; + const migrations = getMigrations( + encryptedSavedObjectsSetup, + { + [versionToTest]: (state) => ({ ...state, migrated: true }), + }, + isPreconfigured + ); + + expect( + migrations[versionToTest](esQueryRuleSavedObject, {} as SavedObjectMigrationContext) + ).toEqual({ + attributes: { + params: { + searchConfiguration: 5, + }, + }, + }); + }); +}); + describe('handles errors during migrations', () => { beforeEach(() => { jest.resetAllMocks(); @@ -2355,7 +2512,7 @@ describe('handles errors during migrations', () => { }); describe('7.10.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.10.0']; + const migration710 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.10.0']; const alert = getMockData({ consumer: 'alerting', }); @@ -2380,7 +2537,7 @@ describe('handles errors during migrations', () => { describe('7.11.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.0']; + const migration711 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)['7.11.0']; const alert = getMockData({ consumer: 'alerting', }); @@ -2405,7 +2562,9 @@ describe('handles errors during migrations', () => { describe('7.11.2 throws if migration fails', () => { test('should show the proper exception', () => { - const migration7112 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.11.2']; + const migration7112 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.11.2' + ]; const alert = getMockData({ consumer: 'alerting', }); @@ -2430,7 +2589,9 @@ describe('handles errors during migrations', () => { describe('7.13.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration7130 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.13.0']; + const migration7130 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.13.0' + ]; const alert = getMockData({ consumer: 'alerting', }); @@ -2455,7 +2616,9 @@ describe('handles errors during migrations', () => { describe('7.16.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0']; + const migration7160 = getMigrations(encryptedSavedObjectsSetup, {}, isPreconfigured)[ + '7.16.0' + ]; const rule = getMockData(); expect(() => { migration7160(rule, migrationContext); @@ -2475,6 +2638,53 @@ describe('handles errors during migrations', () => { ); }); }); + + describe('8.3.0 throws if migration fails', () => { + test('should show the proper exception on search source migration', () => { + encryptedSavedObjectsSetup.createMigration.mockImplementation(({ migration }) => migration); + const mockRule = getMockData(); + const rule = { + ...mockRule, + attributes: { + ...mockRule.attributes, + params: { + searchConfiguration: { + some: 'prop', + migrated: false, + }, + }, + }, + }; + + const versionToTest = '8.3.0'; + const migration830 = getMigrations( + encryptedSavedObjectsSetup, + { + [versionToTest]: () => { + throw new Error(`Can't migrate search source!`); + }, + }, + isPreconfigured + )[versionToTest]; + + expect(() => { + migration830(rule, migrationContext); + }).toThrowError(`Can't migrate search source!`); + expect(migrationContext.log.error).toHaveBeenCalledWith( + `encryptedSavedObject ${versionToTest} migration failed for alert ${rule.id} with error: Can't migrate search source!`, + { + migrations: { + alertDocument: { + ...rule, + attributes: { + ...rule.attributes, + }, + }, + }, + } + ); + }); + }); }); function getUpdatedAt(): string { diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 69d88e196dcf..b3f8d873d8ef 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -7,6 +7,7 @@ import { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; import { isString } from 'lodash/fp'; +import { gte } from 'semver'; import { LogMeta, SavedObjectMigrationMap, @@ -19,12 +20,16 @@ import { } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import type { IsMigrationNeededPredicate } from '@kbn/encrypted-saved-objects-plugin/server'; -import { RawRule, RawRuleAction, RawRuleExecutionStatus } from '../types'; +import { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; +import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; +import { isSerializedSearchSource, SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations'; +import { RawRule, RawRuleAction, RawRuleExecutionStatus } from '../types'; import { getMappedParams } from '../rules_client/lib/mapped_params_utils'; const SIEM_APP_ID = 'securitySolution'; const SIEM_SERVER_APP_ID = 'siem'; +const MINIMUM_SS_MIGRATION_VERSION = '8.3.0'; export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0'; export const FILEBEAT_7X_INDICATOR_PATH = 'threatintel.indicator'; @@ -59,6 +64,9 @@ export const isAnyActionSupportIncidents = (doc: SavedObjectUnsanitizedDoc): boolean => doc.attributes.alertTypeId === 'siem.signals'; +export const isEsQueryRuleType = (doc: SavedObjectUnsanitizedDoc) => + doc.attributes.alertTypeId === '.es-query'; + export const isDetectionEngineAADRuleType = (doc: SavedObjectUnsanitizedDoc): boolean => (Object.values(ruleTypeMappings) as string[]).includes(doc.attributes.alertTypeId); @@ -75,6 +83,7 @@ export const isSecuritySolutionLegacyNotification = ( export function getMigrations( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + searchSourceMigrations: MigrateFunctionsObject, isPreconfigured: (connectorId: string) => boolean ): SavedObjectMigrationMap { const migrationWhenRBACWasIntroduced = createEsoMigration( @@ -155,22 +164,25 @@ export function getMigrations( const migrationRules830 = createEsoMigration( encryptedSavedObjects, (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(removeInternalTags) + pipeMigrations(addSearchType, removeInternalTags) ); - 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'), - '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), - '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), - '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), - }; + return mergeSavedObjectMigrationMaps( + { + '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'), + '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), + '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), + '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), + }, + getSearchSourceMigrations(encryptedSavedObjects, searchSourceMigrations) + ); } function executeMigrationWithErrorHandling( @@ -697,6 +709,23 @@ function addSecuritySolutionAADRuleTypes( : doc; } +function addSearchType(doc: SavedObjectUnsanitizedDoc) { + const searchType = doc.attributes.params.searchType; + + return isEsQueryRuleType(doc) && !searchType + ? { + ...doc, + attributes: { + ...doc.attributes, + params: { + ...doc.attributes.params, + searchType: 'esQuery', + }, + }, + } + : doc; +} + function addSecuritySolutionAADRuleTypeTags( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { @@ -902,3 +931,56 @@ function pipeMigrations(...migrations: AlertMigration[]): AlertMigration { return (doc: SavedObjectUnsanitizedDoc) => migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc); } + +function mapSearchSourceMigrationFunc( + migrateSerializedSearchSourceFields: MigrateFunction +): MigrateFunction { + return (doc) => { + const _doc = doc as { attributes: RawRule }; + + const serializedSearchSource = _doc.attributes.params.searchConfiguration; + + if (isSerializedSearchSource(serializedSearchSource)) { + return { + ..._doc, + attributes: { + ..._doc.attributes, + params: { + ..._doc.attributes.params, + searchConfiguration: migrateSerializedSearchSourceFields(serializedSearchSource), + }, + }, + }; + } + return _doc; + }; +} + +/** + * This creates a migration map that applies search source migrations to legacy es query rules. + * It doesn't modify existing migrations. The following migrations will occur at minimum version of 8.3+. + */ +function getSearchSourceMigrations( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + searchSourceMigrations: MigrateFunctionsObject +) { + const filteredMigrations: SavedObjectMigrationMap = {}; + for (const versionKey in searchSourceMigrations) { + if (gte(versionKey, MINIMUM_SS_MIGRATION_VERSION)) { + const migrateSearchSource = mapSearchSourceMigrationFunc( + searchSourceMigrations[versionKey] + ) as unknown as AlertMigration; + + filteredMigrations[versionKey] = executeMigrationWithErrorHandling( + createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => + isEsQueryRuleType(doc), + pipeMigrations(migrateSearchSource) + ), + versionKey + ); + } + } + return filteredMigrations; +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 4622e8408150..999b0a5f4c4f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -419,6 +419,18 @@ export default function createGetTests({ getService }: FtrProviderContext) { }); }); + it('8.2.0 migrates existing esQuery alerts to contain searchType param', async () => { + const response = await es.get<{ alert: RawRule }>( + { + index: '.kibana', + id: 'alert:776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8', + }, + { meta: true } + ); + expect(response.statusCode).to.equal(200); + expect(response.body._source?.alert?.params.searchType).to.eql('esQuery'); + }); + it('8.3.0 removes internal tags in Security Solution rule', async () => { const response = await es.get<{ alert: RawRule }>( { diff --git a/x-pack/test/functional/es_archives/alerts/data.json b/x-pack/test/functional/es_archives/alerts/data.json index 1c096d9df993..3ce8cddcb284 100644 --- a/x-pack/test/functional/es_archives/alerts/data.json +++ b/x-pack/test/functional/es_archives/alerts/data.json @@ -891,6 +891,57 @@ } } +{ + "type": "doc", + "value": { + "id": "alert:776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8", + "index": ".kibana_1", + "source": { + "alert": { + "name": "123", + "alertTypeId": ".es-query", + "consumer": "alerts", + "params": { + "esQuery": "{\n \"query\":{\n \"match_all\" : {}\n }\n}", + "size": 100, + "timeWindowSize": 5, + "timeWindowUnit": "m", + "threshold": [ + 1000 + ], + "thresholdComparator": ">", + "index": [ + "kibana_sample_data_ecommerce" + ], + "timeField": "order_date" + }, + "schedule": { + "interval": "1m" + }, + "enabled": true, + "actions": [ + ], + "throttle": null, + "apiKeyOwner": null, + "createdBy" : "elastic", + "updatedBy" : "elastic", + "createdAt": "2022-03-26T16:04:50.698Z", + "muteAll": false, + "mutedInstanceIds": [], + "scheduledTaskId": "776cb5c0-ad1e-11ec-ab9e-5f5932f4fad8", + "tags": [] + }, + "type": "alert", + "updated_at": "2022-03-26T16:05:55.957Z", + "migrationVersion": { + "alert": "8.0.1" + }, + "references": [ + ] + } + } +} + { "type":"doc", "value":{ @@ -989,4 +1040,4 @@ ] } } -} \ No newline at end of file +}