kibana/x-pack/plugins/actions/server/saved_objects/migrations.ts
ymao1 81a0267c7b
Gracefully handle decryption errors during ESO migrations (#105968)
* Updating unit tests

* Fixing types

* Updating readme and adding warning message

* Updating README

* PR fixes

* collapsing args to create migration fn

* Adding functional tests

* Adding comment to functional test

* Adding stripOrDecryptAttributesSync

* Using stripOrDecryptAttributesSync

* Fixing unit tests

* PR fixes

* PR fixes

* Moving validation of apikey existence in alerting task runner

* Cleanup

* Reverting changes to alerting task runner

* PR fixes

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2021-07-28 13:04:20 -04:00

167 lines
5.1 KiB
TypeScript

/*
* 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 {
LogMeta,
SavedObjectMigrationMap,
SavedObjectUnsanitizedDoc,
SavedObjectMigrationFn,
SavedObjectMigrationContext,
} from '../../../../../src/core/server';
import { RawAction } from '../types';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server';
interface ActionsLogMeta extends LogMeta {
migrations: { actionDocument: SavedObjectUnsanitizedDoc<RawAction> };
}
type ActionMigration = (
doc: SavedObjectUnsanitizedDoc<RawAction>
) => SavedObjectUnsanitizedDoc<RawAction>;
function createEsoMigration(
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup,
isMigrationNeededPredicate: IsMigrationNeededPredicate<RawAction, RawAction>,
migrationFunc: ActionMigration
) {
return encryptedSavedObjects.createMigration<RawAction, RawAction>({
isMigrationNeededPredicate,
migration: migrationFunc,
shouldMigrateIfDecryptionFails: true, // shouldMigrateIfDecryptionFails flag that applies the migration to undecrypted document if decryption fails
});
}
export function getMigrations(
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
): SavedObjectMigrationMap {
const migrationActionsTen = createEsoMigration(
encryptedSavedObjects,
(doc): doc is SavedObjectUnsanitizedDoc<RawAction> =>
doc.attributes.config?.hasOwnProperty('casesConfiguration') ||
doc.attributes.actionTypeId === '.email',
pipeMigrations(renameCasesConfigurationObject, addHasAuthConfigurationObject)
);
const migrationActionsEleven = createEsoMigration(
encryptedSavedObjects,
(doc): doc is SavedObjectUnsanitizedDoc<RawAction> =>
doc.attributes.config?.hasOwnProperty('isCaseOwned') ||
doc.attributes.config?.hasOwnProperty('incidentConfiguration') ||
doc.attributes.actionTypeId === '.webhook',
pipeMigrations(removeCasesFieldMappings, addHasAuthConfigurationObject)
);
const migrationActionsFourteen = createEsoMigration(
encryptedSavedObjects,
(doc): doc is SavedObjectUnsanitizedDoc<RawAction> => true,
pipeMigrations(addisMissingSecretsField)
);
return {
'7.10.0': executeMigrationWithErrorHandling(migrationActionsTen, '7.10.0'),
'7.11.0': executeMigrationWithErrorHandling(migrationActionsEleven, '7.11.0'),
'7.14.0': executeMigrationWithErrorHandling(migrationActionsFourteen, '7.14.0'),
};
}
function executeMigrationWithErrorHandling(
migrationFunc: SavedObjectMigrationFn<RawAction, RawAction>,
version: string
) {
return (doc: SavedObjectUnsanitizedDoc<RawAction>, context: SavedObjectMigrationContext) => {
try {
return migrationFunc(doc, context);
} catch (ex) {
context.log.error<ActionsLogMeta>(
`encryptedSavedObject ${version} migration failed for action ${doc.id} with error: ${ex.message}`,
{
migrations: {
actionDocument: doc,
},
}
);
throw ex;
}
};
}
function renameCasesConfigurationObject(
doc: SavedObjectUnsanitizedDoc<RawAction>
): SavedObjectUnsanitizedDoc<RawAction> {
if (!doc.attributes.config?.casesConfiguration) {
return doc;
}
const { casesConfiguration, ...restConfiguration } = doc.attributes.config;
return {
...doc,
attributes: {
...doc.attributes,
config: {
...restConfiguration,
incidentConfiguration: casesConfiguration,
},
},
};
}
function removeCasesFieldMappings(
doc: SavedObjectUnsanitizedDoc<RawAction>
): SavedObjectUnsanitizedDoc<RawAction> {
if (
!doc.attributes.config?.hasOwnProperty('isCaseOwned') &&
!doc.attributes.config?.hasOwnProperty('incidentConfiguration')
) {
return doc;
}
const { incidentConfiguration, isCaseOwned, ...restConfiguration } = doc.attributes.config;
return {
...doc,
attributes: {
...doc.attributes,
config: restConfiguration,
},
};
}
const addHasAuthConfigurationObject = (
doc: SavedObjectUnsanitizedDoc<RawAction>
): SavedObjectUnsanitizedDoc<RawAction> => {
if (doc.attributes.actionTypeId !== '.email' && doc.attributes.actionTypeId !== '.webhook') {
return doc;
}
const hasAuth = !!doc.attributes.secrets?.user || !!doc.attributes.secrets?.password;
return {
...doc,
attributes: {
...doc.attributes,
config: {
...doc.attributes.config,
hasAuth,
},
},
};
};
const addisMissingSecretsField = (
doc: SavedObjectUnsanitizedDoc<RawAction>
): SavedObjectUnsanitizedDoc<RawAction> => {
return {
...doc,
attributes: {
...doc.attributes,
isMissingSecrets: false,
},
};
};
function pipeMigrations(...migrations: ActionMigration[]): ActionMigration {
return (doc: SavedObjectUnsanitizedDoc<RawAction>) =>
migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
}