Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Draft for socialization at this point] Migration side car actions 2 #112043

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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.
*/

/**
* We keep this around to migrate and update data for the old deprecated rule actions saved object mapping but we
* do not use it anymore within the code base. Once we feel comfortable that users are upgrade far enough and this is no longer
* needed then it will be safe to remove this saved object and all its migrations.
* @deprecated Remove this once we no longer need legacy migrations for rule actions (8.0.0)
*/
export const ruleActionsSavedObjectType = 'siem-detection-engine-rule-actions';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { Logger } from '../../../../../../../../src/core/server';
import { EncryptedSavedObjectsClient } from '../../../../../../encrypted_saved_objects/server';
import { decryptAPIKey } from './decrypt_api_key';

export interface ApiKeyAsAlertAttributesOptions {
id: string;
logger: Logger;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
}

export type ApiKeyAsAlertAttributesReturn =
| {
apiKey: string;
apiKeyOwner: string;
}
| undefined;

export const apiKeyAsAlertAttributes = async ({
id,
encryptedSavedObjectsClient,
logger,
}: ApiKeyAsAlertAttributesOptions): Promise<ApiKeyAsAlertAttributesReturn> => {
const decrypted = await decryptAPIKey({
logger,
id,
encryptedSavedObjectsClient,
});
if (decrypted == null) {
return undefined;
} else {
return {
apiKeyOwner: decrypted.apiKeyOwner,
apiKey: Buffer.from(`${decrypted.keyId}:${decrypted.apiKey}`).toString('base64'),
};
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 { Logger } from '../../../../../../../../src/core/server';
import { Alerting } from '../deletion_migration/types';
import { EncryptedSavedObjectsClient } from '../../../../../../encrypted_saved_objects/server';

export interface DecryptApiKeyOptions {
id: string;
logger: Logger;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
}

export type DecryptApiKeyReturn =
| {
keyId: string;
apiKey: string;
apiKeyOwner: string;
}
| undefined;

export const decryptAPIKey = async ({
id,
encryptedSavedObjectsClient,
logger,
}: DecryptApiKeyOptions): Promise<DecryptApiKeyReturn> => {
const decrypted = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<Alerting>(
'alert',
id
);
const apiKeyDecrypted = decrypted.attributes.apiKey;
const apiKeyOwner = decrypted.attributes.apiKeyOwner;
if (apiKeyDecrypted == null || apiKeyOwner == null) {
logger.error(
[
'API Key could not be decrypted.',
'Could not update legacy actions to newer actions.',
'Please check the existing "security_solution" actions to ensure their action intervals are set correctly as migrations have failed',
].join(' ')
);
return undefined;
} else {
const stringAPIKey = Buffer.from(apiKeyDecrypted, 'base64').toString('utf-8');
const split = stringAPIKey.split(':');
if (split.length !== 2) {
logger.error(
[
`API Key could not be decrypted because the expected length after decryption was ${stringAPIKey.length} instead of 2.`,
'Please check the existing "security_solution" actions to ensure their action intervals are set correctly as migrations have failed',
].join(' ')
);
return undefined;
} else {
const [keyId, apiKey] = split;
return {
keyId,
apiKey,
apiKeyOwner,
};
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 { SavedObject } from '../../../../../../../../src/core/server';
import { Alerting } from './types';

export const getActionsCountInReferences = (
references: SavedObject<Alerting>['references']
): number => {
return references.reduce((accum, reference) => {
if (reference.type === 'action') {
return accum + 1;
} else {
return accum;
}
}, 0);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { SavedObjectsFindResult, Logger } from '../../../../../../../../src/core/server';
import { SideCarAction } from './types';

export interface GetThrottleOptions {
actionSideCar: SavedObjectsFindResult<SideCarAction>;
logger: Logger;
}

export const getThrottle = ({ actionSideCar, logger }: GetThrottleOptions): string => {
if (actionSideCar.attributes.alertThrottle != null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check "rule" first intead of "alert" first since terminology changed and it's more than likely rule that is being stored as a more first class than "alert".

return actionSideCar.attributes.alertThrottle;
} else if (actionSideCar.attributes.ruleThrottle != null) {
return actionSideCar.attributes.ruleThrottle;
} else {
logger.error(
'Error finding a legacy "alertThrottle" or "ruleThrottle" to determine interval to migrate the actions to. Using a fall back of "1h" interval"'
);
return '1h';
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { SavedObjectsResolveResponse } from 'kibana/server';
import { JoinOptions } from '../../../startup_migrations/types';
import { Alerting, SideCarAction } from './types';

export const join = ({
logger,
savedObject: actionSideCar,
savedObjectsClient,
}: JoinOptions<SideCarAction>): Array<Promise<SavedObjectsResolveResponse<unknown>>> => {
const { ruleAlertId, actions } = actionSideCar.attributes;
logger.debug(
`Getting legacy action side car promises which has a rulAlertId: ${ruleAlertId} and actions of length:, ${actions.length}`
);
const resolvedAlert = savedObjectsClient.resolve<Alerting>('alert', ruleAlertId);
const resolvedActions = actions.map((action) => {
logger.debug(`Getting join promises of action.id: ${action.id}`);
return savedObjectsClient.resolve<unknown>('action', action.id);
});
const returnPromises = [resolvedAlert, ...resolvedActions];
logger.debug(`Returning migration join promises of length: ${returnPromises.length}`);
return [resolvedAlert, ...resolvedActions];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { Actions, AlertingActions } from './types';

export interface TransformActionsOptions {
actions: Actions[];
actionsCountInReferences: number;
}

export const transformActions = ({
actions,
actionsCountInReferences,
}: TransformActionsOptions): AlertingActions[] => {
return actions.map<AlertingActions>((action, index) => {
const { action_type_id: actionTypeId, id, ...rest } = action;
return { ...rest, actionTypeId, actionRef: `action_${actionsCountInReferences + index}` };
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.
*/

// We import this restricted area so we can get access to the SO "Raw" types from alerting
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import type { RawAlertAction, RawAlert } from '../../../../../../alerting/server/types';

/**
* This is the legacy Actions type.
* This type should _never_ be used outside of this one use case.
* @deprecated Remove this once we have no more migrations for "legacy actions" left.
*/
export interface Actions {
id: string;
params: {
message: string;
};
action_type_id: string;
group: string;
}

/**
* This is the legacy Actions side car type.
* NOTE: I intentionally do _NOT_ use other types from other parts of the system.
* This type should _never_ be used outside of this one use case.
* @deprecated Remove this once we no longer need this migration and this code is all deleted
*/
export interface SideCarAction {
ruleAlertId: string;
actions: Actions[];
ruleThrottle: string;
alertThrottle: string;
}

/**
* We carefully pull in this one restricted path and the RawAlertAction to use within this part of our migration code.
* @deprecated Remove this once we no longer need this migration code.
*/
export type AlertingActions = RawAlertAction;

/**
* We carefully pull in this one restricted path and the RawAlertAction to use within this part of our migration code.
* @deprecated Remove this once we no longer need this migration code.
*/
export type Alerting = RawAlert;
Loading