mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution] Migrates siem-detection-engine-rule-actions ruleAlertId and actions to saved object references array (#113577)
## Summary Fixes https://github.com/elastic/kibana/issues/113278 * Migrates the legacy `siem-detection-engine-rule-actions` `ruleAlertId` and `actions` to saved object references arrays * Adds an e2e test for `siem-detection-engine-rule-actions` * Updates the types to work with the migrations and the new and old data structures. * Decouples and removes reliance on alerting within the types since we do not want development of alerting to get in the way of legacy things and have migration changes by accident. * Updates the REST interface and code to produce post migration data structures. Removes some types and code where w can since those parts are no longer needed/used. * Adds `actionRef` to the mapping Before migration you should see data structures like this if you query: ```json GET .kibana/_search { "query": { "term": { "type": { "value": "siem-detection-engine-rule-actions" } } } } ``` ```json { "siem-detection-engine-rule-actions": { "ruleAlertId": "fb1046a0-0452-11ec-9b15-d13d79d162f3", <-- ruleAlertId which we want in the references array and removed "actions": [ { "action_type_id": ".slack", "id": "f6e64c00-0452-11ec-9b15-d13d79d162f3", <-- id which we want in the references array and removed "params": { "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" }, "group": "default" } ], "ruleThrottle": "7d", "alertThrottle": "7d" }, "type": "siem-detection-engine-rule-actions", "references": [], <-- Array is empty which instead needs the id's of alerts and actions "migrationVersion": { "siem-detection-engine-rule-actions": "7.11.2" }, "coreMigrationVersion": "7.14.0", "updated_at": "2021-09-15T22:18:48.369Z" } ``` After migration you should see data structures like this: ```json { "siem-detection-engine-rule-actions": { "actions": [ { "action_type_id": ".slack", "actionRef" : "action_0", <-- We use the name and "actionRef" to be consistent with kibana alerting "params": { "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" }, "group": "default" } ], "ruleThrottle": "7d", "alertThrottle": "7d" }, "type": "siem-detection-engine-rule-actions", "references" : [ { "name" : "alert_0", <-- Name is "alert_0" "id" : "fb1046a0-0452-11ec-9b15-d13d79d162f3", <-- Alert id is now here "type" : "alert" <-- Type should be "alert" }, { "name" : "action_0", <-- Name is "action_0" and should be the same as kibana alerting names theirs for consistencty "id" : "f6e64c00-0452-11ec-9b15-d13d79d162f3", <-- Id of the action is now here. "type" : "action" <-- Type should be "action" } ], "migrationVersion": { "siem-detection-engine-rule-actions": "7.16.0" }, "coreMigrationVersion": "8.0.0", "updated_at": "2021-09-15T22:18:48.369Z" } ``` Manual testing --- There are e2e tests but for any manual testing or verification you can do the following: If you have a 7.14.0 system and can migrate it forward that is the most straight forward way to ensure this does migrate correctly and forward. You should see that the legacy notification system still operates as expected. If you are a developer off of master and want to test different scenarios then this section is for below as it is more involved and harder to do but goes into more depth: * Create a rule and activate it normally within security_solution * Do not add actions to the rule at this point as we are exercising the older legacy system. However, you want at least one action configured such as a slack notification. * Within dev tools do a query for all your actions and grab one of the `_id` of them without their prefix: ```json # See all your actions GET .kibana/_search { "query": { "term": { "type": "action" } } } ``` Mine was `"_id" : "action:879e8ff0-1be1-11ec-a722-83da1c22a481"`, so I will be copying the ID of `879e8ff0-1be1-11ec-a722-83da1c22a481` Go to the file `detection_engine/scripts/legacy_notifications/one_action.json` and add this id to the file. Something like this: ```json { "name": "Legacy notification with one action", "interval": "1m", <--- You can use whatever you want. Real values are "1h", "1d", "1w". I use "1m" for testing purposes. "actions": [ { "id": "879e8ff0-1be1-11ec-a722-83da1c22a481", <--- My action id "group": "default", "params": { "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" }, "actionTypeId": ".slack" <--- I am a slack action id type. } ] } ``` Query for an alert you want to add manually add back a legacy notification to it. Such as: ```json # See all your siem.signals alert types and choose one GET .kibana/_search { "query": { "term": { "alert.alertTypeId": "siem.signals" } } } ``` Grab the `_id` without the alert prefix. For mine this was `933ca720-1be1-11ec-a722-83da1c22a481` Within the directory of detection_engine/scripts execute the script: ```json ./post_legacy_notification.sh 933ca720-1be1-11ec-a722-83da1c22a481 { "ok": "acknowledged" } ``` which is going to do a few things. See the file `detection_engine/routes/rules/legacy_create_legacy_notification.ts` for the definition of the route and what it does in full, but we should notice that we have now: Created a legacy side car action object of type `siem-detection-engine-rule-actions` you can see in dev tools: ```json # See the actions "side car" which are part of the legacy notification system. GET .kibana/_search { "query": { "term": { "type": { "value": "siem-detection-engine-rule-actions" } } } } ``` Take note that this actually creates the rule migrated since this PR updated the code to produce new side cars. So we have to use some scripting to change the actions to utilize the old format. However, before continuing you should verify that this does fire correctly and that the new format is working as expected. After that replace the structure with the older structure like so below and downgrade the migration version so that we can restart Kibana and ensure that this does migrate correctly forward: ```json # Get your id of your rules side car above and then use this script to downgrade the data structure POST .kibana/_update/siem-detection-engine-rule-actions:210f4c90-2233-11ec-98c6-ed2574588902 { "script" : { "source": """ ctx._source.migrationVersion['siem-detection-engine-rule-actions'] = "7.15.0"; ctx._source['siem-detection-engine-rule-actions'].actions[0].id = ctx._source.references[1].id; ctx._source['siem-detection-engine-rule-actions'].actions[0].remove('actionRef'); ctx._source['siem-detection-engine-rule-actions'].ruleAlertId = ctx._source.references[0].id; ctx._source.references.remove(0); ctx._source.references.remove(0); """, "lang": "painless" } } ``` Restart Kibana and now it should be migrated correctly and the system should fire the notifications as expected. You shouldn't see any errors in your console. In the scripts folder execute the `find_rules.sh` and expect to see actions like so in the rule with the `id` still in the REST interface and we shouldn't see `actionRef` within the actions: ```json "actions": [{ "id": "42534430-2092-11ec-99a6-05d79563c01a", "group": "default", "params": { "message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts" }, "action_type_id": ".slack" }], ``` Take the rule id and query that as well using `./get_rule_by_id.sh` and verify that the action also looks the same and is present within the rule. You can also verify all of this within the UI's as well for rules to ensure the action is still present and as we expect it to be and work. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
3135075250
commit
3237a746ae
28 changed files with 5196 additions and 85 deletions
|
@ -37,7 +37,7 @@ describe('legacy_extract_rule_id', () => {
|
|||
).toEqual<FuncReturn>([]);
|
||||
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Security Solution notification (Legacy) system "ruleAlertId" is null or undefined when it never should be. ,This indicates potentially that saved object migrations did not run correctly. Returning empty reference'
|
||||
'Security Solution notification (Legacy) system "ruleAlertId" is null or undefined when it never should be. This indicates potentially that saved object migrations did not run correctly. Returning empty reference.'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
*/
|
||||
|
||||
import { Logger, SavedObjectReference } from 'src/core/server';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetRuleReference } from '../../rule_actions/legacy_utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesNotificationParams } from '../legacy_types';
|
||||
|
||||
|
@ -30,17 +33,11 @@ export const legacyExtractRuleId = ({
|
|||
logger.error(
|
||||
[
|
||||
'Security Solution notification (Legacy) system "ruleAlertId" is null or undefined when it never should be. ',
|
||||
'This indicates potentially that saved object migrations did not run correctly. Returning empty reference',
|
||||
].join()
|
||||
'This indicates potentially that saved object migrations did not run correctly. Returning empty reference.',
|
||||
].join('')
|
||||
);
|
||||
return [];
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
id: ruleAlertId,
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
];
|
||||
return [legacyGetRuleReference(ruleAlertId)];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
|
||||
import { requestContextMock, requestMock, serverMock } from '../__mocks__';
|
||||
|
@ -23,9 +24,11 @@ describe.each([
|
|||
])('find_rules - %s', (_, isRuleRegistryEnabled) => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
|
||||
beforeEach(async () => {
|
||||
server = serverMock.create();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
|
||||
|
@ -35,7 +38,7 @@ describe.each([
|
|||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus());
|
||||
|
||||
findRulesRoute(server.router, isRuleRegistryEnabled);
|
||||
findRulesRoute(server.router, logger, isRuleRegistryEnabled);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { findRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/find_rules_type_dependents';
|
||||
import {
|
||||
findRulesSchema,
|
||||
|
@ -17,11 +18,13 @@ import { findRules } from '../../rules/find_rules';
|
|||
import { buildSiemResponse } from '../utils';
|
||||
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
|
||||
import { transformFindAlerts } from './utils';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetBulkRuleActionsSavedObject } from '../../rule_actions/legacy_get_bulk_rule_actions_saved_object';
|
||||
|
||||
export const findRulesRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger,
|
||||
isRuleRegistryEnabled: boolean
|
||||
) => {
|
||||
router.get(
|
||||
|
@ -71,7 +74,7 @@ export const findRulesRoute = (
|
|||
logsCount: 1,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
}),
|
||||
legacyGetBulkRuleActionsSavedObject({ alertIds, savedObjectsClient }),
|
||||
legacyGetBulkRuleActionsSavedObject({ alertIds, savedObjectsClient, logger }),
|
||||
]);
|
||||
const transformed = transformFindAlerts(rules, ruleStatuses, ruleActions);
|
||||
if (transformed == null) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { Logger } from 'src/core/server';
|
||||
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyUpdateOrCreateRuleActionsSavedObject } from '../../rule_actions/legacy_update_or_create_rule_actions_saved_object';
|
||||
|
@ -25,7 +27,10 @@ import { legacyCreateNotifications } from '../../notifications/legacy_create_not
|
|||
* @deprecated Once we no longer have legacy notifications and "side car actions" this can be removed.
|
||||
* @param router The router
|
||||
*/
|
||||
export const legacyCreateLegacyNotificationRoute = (router: SecuritySolutionPluginRouter): void => {
|
||||
export const legacyCreateLegacyNotificationRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger
|
||||
): void => {
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/api/detection/legacy/notifications',
|
||||
|
@ -95,6 +100,7 @@ export const legacyCreateLegacyNotificationRoute = (router: SecuritySolutionPlug
|
|||
savedObjectsClient,
|
||||
actions,
|
||||
throttle: interval,
|
||||
logger,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'unknown';
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
import { readRulesRoute } from './read_rules_route';
|
||||
import {
|
||||
|
@ -22,16 +24,18 @@ describe.each([
|
|||
])('read_rules - %s', (_, isRuleRegistryEnabled) => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
server = serverMock.create();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue([]);
|
||||
|
||||
readRulesRoute(server.router, isRuleRegistryEnabled);
|
||||
readRulesRoute(server.router, logger, isRuleRegistryEnabled);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents';
|
||||
import {
|
||||
queryRulesSchema,
|
||||
|
@ -24,6 +25,7 @@ import { legacyGetRuleActionsSavedObject } from '../../rule_actions/legacy_get_r
|
|||
|
||||
export const readRulesRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger,
|
||||
isRuleRegistryEnabled: boolean
|
||||
) => {
|
||||
router.get(
|
||||
|
@ -66,6 +68,7 @@ export const readRulesRoute = (
|
|||
const legacyRuleActions = await legacyGetRuleActionsSavedObject({
|
||||
savedObjectsClient,
|
||||
ruleAlertId: rule.id,
|
||||
logger,
|
||||
});
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
|
|
|
@ -38,7 +38,8 @@ import {
|
|||
} from '../../schemas/rule_schemas.mock';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object';
|
||||
import { RuleAlertAction } from '../../../../../common/detection_engine/types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRuleAlertAction } from '../../rule_actions/legacy_types';
|
||||
|
||||
type PromiseFromStreams = ImportRulesSchemaDecoded | Error;
|
||||
|
||||
|
@ -306,7 +307,7 @@ describe.each([
|
|||
});
|
||||
|
||||
test('outputs 200 if the data is of type siem alert and has a legacy rule action', () => {
|
||||
const actions: RuleAlertAction[] = [
|
||||
const actions: LegacyRuleAlertAction[] = [
|
||||
{
|
||||
id: '456',
|
||||
params: {},
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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 { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyCreateRuleActionsSavedObject } from './legacy_create_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
|
||||
describe('legacy_create_rule_actions_saved_object', () => {
|
||||
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
|
||||
beforeEach(() => {
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
});
|
||||
|
||||
test('it creates a rule actions saved object with empty actions array', () => {
|
||||
legacyCreateRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
actions: [],
|
||||
throttle: '1d',
|
||||
});
|
||||
const [[, arg2, arg3]] = savedObjectsClient.create.mock.calls;
|
||||
expect(arg2).toEqual<LegacyIRuleActionsAttributesSavedObjectAttributes>({
|
||||
actions: [],
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
expect(arg3).toEqual({
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('it creates a rule actions saved object with 1 single action', () => {
|
||||
legacyCreateRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'default',
|
||||
actionTypeId: '.slack',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
throttle: '1d',
|
||||
});
|
||||
const [[, arg2, arg3]] = savedObjectsClient.create.mock.calls;
|
||||
expect(arg2).toEqual<LegacyIRuleActionsAttributesSavedObjectAttributes>({
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
action_type_id: '.slack',
|
||||
group: 'default',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
expect(arg3).toEqual({
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('it creates a rule actions saved object with 2 actions', () => {
|
||||
legacyCreateRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'default',
|
||||
actionTypeId: '.slack',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '555',
|
||||
group: 'default_2',
|
||||
actionTypeId: '.email',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com/2',
|
||||
},
|
||||
},
|
||||
],
|
||||
throttle: '1d',
|
||||
});
|
||||
const [[, arg2, arg3]] = savedObjectsClient.create.mock.calls;
|
||||
expect(arg2).toEqual<LegacyIRuleActionsAttributesSavedObjectAttributes>({
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
action_type_id: '.slack',
|
||||
group: 'default',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
actionRef: 'action_1',
|
||||
action_type_id: '.email',
|
||||
group: 'default_2',
|
||||
params: {
|
||||
kibana_siem_app_url: 'www.example.com/2',
|
||||
},
|
||||
},
|
||||
],
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
expect(arg3).toEqual({
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
id: '555',
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,17 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectReference } from 'kibana/server';
|
||||
import { AlertServices } from '../../../../../alerting/server';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetThrottleOptions, legacyGetRuleActionsFromSavedObject } from './legacy_utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object';
|
||||
import {
|
||||
legacyGetActionReference,
|
||||
legacyGetRuleReference,
|
||||
legacyGetThrottleOptions,
|
||||
legacyTransformActionToReference,
|
||||
} from './legacy_utils';
|
||||
import { AlertAction } from '../../../../../alerting/common';
|
||||
import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -28,23 +31,30 @@ interface LegacyCreateRuleActionsSavedObject {
|
|||
}
|
||||
|
||||
/**
|
||||
* NOTE: This should _only_ be seen to be used within the legacy route of "legacyCreateLegacyNotificationRoute" and not exposed and not
|
||||
* used anywhere else. If you see it being used anywhere else, that would be a bug.
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
* @see legacyCreateLegacyNotificationRoute
|
||||
*/
|
||||
export const legacyCreateRuleActionsSavedObject = async ({
|
||||
ruleAlertId,
|
||||
savedObjectsClient,
|
||||
actions = [],
|
||||
throttle,
|
||||
}: LegacyCreateRuleActionsSavedObject): Promise<LegacyRulesActionsSavedObject> => {
|
||||
const ruleActionsSavedObject =
|
||||
await savedObjectsClient.create<LegacyIRuleActionsAttributesSavedObjectAttributes>(
|
||||
legacyRuleActionsSavedObjectType,
|
||||
{
|
||||
ruleAlertId,
|
||||
actions: actions.map((action) => transformAlertToRuleAction(action)),
|
||||
...legacyGetThrottleOptions(throttle),
|
||||
}
|
||||
);
|
||||
|
||||
return legacyGetRuleActionsFromSavedObject(ruleActionsSavedObject);
|
||||
}: LegacyCreateRuleActionsSavedObject): Promise<void> => {
|
||||
const referenceWithAlertId: SavedObjectReference[] = [legacyGetRuleReference(ruleAlertId)];
|
||||
const actionReferences: SavedObjectReference[] = actions.map((action, index) =>
|
||||
legacyGetActionReference(action.id, index)
|
||||
);
|
||||
const references: SavedObjectReference[] = [...referenceWithAlertId, ...actionReferences];
|
||||
await savedObjectsClient.create<LegacyIRuleActionsAttributesSavedObjectAttributes>(
|
||||
legacyRuleActionsSavedObjectType,
|
||||
{
|
||||
actions: actions.map((alertAction, index) =>
|
||||
legacyTransformActionToReference(alertAction, index)
|
||||
),
|
||||
...legacyGetThrottleOptions(throttle),
|
||||
},
|
||||
{ references }
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,497 @@
|
|||
/*
|
||||
* 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 { SavedObjectsFindOptions, SavedObjectsFindResult } from 'kibana/server';
|
||||
|
||||
import { loggingSystemMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetBulkRuleActionsSavedObject } from './legacy_get_bulk_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
|
||||
describe('legacy_get_bulk_rule_actions_saved_object', () => {
|
||||
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
type FuncReturn = Record<string, LegacyRulesActionsSavedObject>;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = loggingSystemMock.createLogger();
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('calls "savedObjectsClient.find" with the expected "hasReferences"', () => {
|
||||
legacyGetBulkRuleActionsSavedObject({ alertIds: ['123'], savedObjectsClient, logger });
|
||||
const [[arg1]] = savedObjectsClient.find.mock.calls;
|
||||
expect(arg1).toEqual<SavedObjectsFindOptions>({
|
||||
hasReference: [{ id: '123', type: 'alert' }],
|
||||
perPage: 10000,
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns nothing transformed through the find if it does not return any matches against the alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({});
|
||||
});
|
||||
|
||||
test('returns 1 action transformed through the find if 1 was found for 1 single alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
'alert-123': {
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 1 action transformed through the find for 2 alerts with 1 action each', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
{
|
||||
score: 0,
|
||||
id: '456',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-456',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
'alert-123': {
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
'alert-456': {
|
||||
id: '456',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_2',
|
||||
group: 'group_2',
|
||||
id: 'action-456',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 2 actions transformed through the find if they were found for 1 single alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_1',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
'alert-123': {
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
action_type_id: 'action_type_2',
|
||||
group: 'group_2',
|
||||
id: 'action-456',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns only 1 action if for some unusual reason the actions reference is missing an item for 1 single alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
// Missing an "action_1" here. { name: 'action_1', id: 'action-456', type: 'action', },
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_1',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
'alert-123': {
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns only 1 action if for some unusual reason the action is missing from the attributes', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
// Missing the action of { group: 'group_2', params: {}, action_type_id: 'action_type_2', actionRef: 'action_1', },
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
'alert-123': {
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('returns nothing if the alert id is missing within the references array', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
// Missing the "alert_0" of { name: 'alert_0', id: 'alert-123', type: 'alert', },
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetBulkRuleActionsSavedObject({
|
||||
alertIds: ['123'],
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectsFindOptionsReference } from 'kibana/server';
|
||||
import { Logger } from 'src/core/server';
|
||||
|
||||
import { AlertServices } from '../../../../../alerting/server';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
|
@ -14,7 +17,6 @@ import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_type
|
|||
import { legacyGetRuleActionsFromSavedObject } from './legacy_utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object';
|
||||
import { buildChunkedOrFilter } from '../signals/utils';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -22,6 +24,7 @@ import { buildChunkedOrFilter } from '../signals/utils';
|
|||
interface LegacyGetBulkRuleActionsSavedObject {
|
||||
alertIds: string[];
|
||||
savedObjectsClient: AlertServices['savedObjectsClient'];
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,22 +33,35 @@ interface LegacyGetBulkRuleActionsSavedObject {
|
|||
export const legacyGetBulkRuleActionsSavedObject = async ({
|
||||
alertIds,
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
}: LegacyGetBulkRuleActionsSavedObject): Promise<Record<string, LegacyRulesActionsSavedObject>> => {
|
||||
const filter = buildChunkedOrFilter(
|
||||
`${legacyRuleActionsSavedObjectType}.attributes.ruleAlertId`,
|
||||
alertIds
|
||||
);
|
||||
const references = alertIds.map<SavedObjectsFindOptionsReference>((alertId) => ({
|
||||
id: alertId,
|
||||
type: 'alert',
|
||||
}));
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
saved_objects,
|
||||
} = await savedObjectsClient.find<LegacyIRuleActionsAttributesSavedObjectAttributes>({
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
perPage: 10000,
|
||||
filter,
|
||||
hasReference: references,
|
||||
});
|
||||
return saved_objects.reduce(
|
||||
(acc: { [key: string]: LegacyRulesActionsSavedObject }, savedObject) => {
|
||||
acc[savedObject.attributes.ruleAlertId] = legacyGetRuleActionsFromSavedObject(savedObject);
|
||||
const ruleAlertId = savedObject.references.find((reference) => {
|
||||
// Find the first rule alert and assume that is the one we want since we should only ever have 1.
|
||||
return reference.type === 'alert';
|
||||
});
|
||||
// We check to ensure we have found a "ruleAlertId" and hopefully we have.
|
||||
const ruleAlertIdKey = ruleAlertId != null ? ruleAlertId.id : undefined;
|
||||
if (ruleAlertIdKey != null) {
|
||||
acc[ruleAlertIdKey] = legacyGetRuleActionsFromSavedObject(savedObject, logger);
|
||||
} else {
|
||||
logger.error(
|
||||
`Security Solution notification (Legacy) Was expecting to find a reference of type "alert" within ${savedObject.references} but did not. Skipping this notification.`
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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 { SavedObjectsFindOptions, SavedObjectsFindResult } from 'kibana/server';
|
||||
import { loggingSystemMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
legacyGetRuleActionsSavedObject,
|
||||
LegacyRulesActionsSavedObject,
|
||||
} from './legacy_get_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
|
||||
describe('legacy_get_rule_actions_saved_object', () => {
|
||||
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
type FuncReturn = LegacyRulesActionsSavedObject | null;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = loggingSystemMock.createLogger();
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('calls "savedObjectsClient.find" with the expected "hasReferences"', () => {
|
||||
legacyGetRuleActionsSavedObject({ ruleAlertId: '123', savedObjectsClient, logger });
|
||||
const [[arg1]] = savedObjectsClient.find.mock.calls;
|
||||
expect(arg1).toEqual<SavedObjectsFindOptions>({
|
||||
hasReference: { id: '123', type: 'alert' },
|
||||
perPage: 1,
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns null if it does not return any matches against the alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>(null);
|
||||
});
|
||||
|
||||
test('returns 1 action transformed through the find if 1 was found for 1 single alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
alertThrottle: '1d',
|
||||
id: '123',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 2 actions transformed through the find if they were found for 1 single alert id', async () => {
|
||||
const savedObjects: Array<
|
||||
SavedObjectsFindResult<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
> = [
|
||||
{
|
||||
score: 0,
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_1',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
},
|
||||
];
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 0,
|
||||
per_page: 0,
|
||||
page: 1,
|
||||
saved_objects: savedObjects,
|
||||
});
|
||||
|
||||
const returnValue = await legacyGetRuleActionsSavedObject({
|
||||
ruleAlertId: '123',
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
expect(returnValue).toEqual<FuncReturn>({
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
action_type_id: 'action_type_2',
|
||||
group: 'group_2',
|
||||
id: 'action-456',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
alertThrottle: '1d',
|
||||
id: '123',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,12 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
||||
import { SavedObjectsFindOptionsReference } from 'kibana/server';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { AlertServices } from '../../../../../alerting/server';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
import {
|
||||
LegacyIRuleActionsAttributesSavedObjectAttributes,
|
||||
LegacyRuleAlertAction,
|
||||
} from './legacy_types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetRuleActionsFromSavedObject } from './legacy_utils';
|
||||
|
||||
|
@ -20,6 +25,7 @@ import { legacyGetRuleActionsFromSavedObject } from './legacy_utils';
|
|||
interface LegacyGetRuleActionsSavedObject {
|
||||
ruleAlertId: string;
|
||||
savedObjectsClient: AlertServices['savedObjectsClient'];
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,7 +33,7 @@ interface LegacyGetRuleActionsSavedObject {
|
|||
*/
|
||||
export interface LegacyRulesActionsSavedObject {
|
||||
id: string;
|
||||
actions: RuleAlertAction[];
|
||||
actions: LegacyRuleAlertAction[];
|
||||
alertThrottle: string | null;
|
||||
ruleThrottle: string;
|
||||
}
|
||||
|
@ -38,20 +44,24 @@ export interface LegacyRulesActionsSavedObject {
|
|||
export const legacyGetRuleActionsSavedObject = async ({
|
||||
ruleAlertId,
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
}: LegacyGetRuleActionsSavedObject): Promise<LegacyRulesActionsSavedObject | null> => {
|
||||
const reference: SavedObjectsFindOptionsReference = {
|
||||
id: ruleAlertId,
|
||||
type: 'alert',
|
||||
};
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
saved_objects,
|
||||
} = await savedObjectsClient.find<LegacyIRuleActionsAttributesSavedObjectAttributes>({
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
perPage: 1,
|
||||
search: `${ruleAlertId}`,
|
||||
searchFields: ['ruleAlertId'],
|
||||
hasReference: reference,
|
||||
});
|
||||
|
||||
if (!saved_objects[0]) {
|
||||
return null;
|
||||
} else {
|
||||
return legacyGetRuleActionsFromSavedObject(saved_objects[0]);
|
||||
return legacyGetRuleActionsFromSavedObject(saved_objects[0], logger);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,355 @@
|
|||
/*
|
||||
* 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 { SavedObjectReference, SavedObjectUnsanitizedDoc } from 'kibana/server';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyMigrateRuleAlertId, legacyMigrateAlertId } from './legacy_migrations';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
|
||||
describe('legacy_migrations', () => {
|
||||
describe('legacyMigrateRuleAlertId', () => {
|
||||
type PartialForTests = Partial<
|
||||
SavedObjectUnsanitizedDoc<Partial<LegacyIRuleActionsAttributesSavedObjectAttributes>>
|
||||
>;
|
||||
|
||||
test('it migrates both a "ruleAlertId" and a actions array with 1 element into the references array', () => {
|
||||
const doc = {
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as unknown as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('it migrates both a "ruleAlertId" and a actions array with 2 elements into the references array', () => {
|
||||
const doc = {
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
{
|
||||
id: '780',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '9999',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as unknown as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
{
|
||||
actionRef: 'action_1',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '9999',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
id: '780',
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns existing references when migrating both a "ruleAlertId" and a actions array into the references array', () => {
|
||||
const doc = {
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '890',
|
||||
name: 'foreign_0',
|
||||
type: 'foreign',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as unknown as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '890',
|
||||
name: 'foreign_0',
|
||||
type: 'foreign',
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('it is idempotent and does not migrate twice if "ruleAlertId" and the actions array are already migrated', () => {
|
||||
const doc = {
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as unknown as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
actionRef: 'action_0',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('does not migrate if "ruleAlertId" is not a string', () => {
|
||||
const doc: PartialForTests = {
|
||||
attributes: {
|
||||
ruleAlertId: 123, // invalid as this is a number
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as unknown as PartialForTests;
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
ruleAlertId: 123,
|
||||
actions: [
|
||||
{
|
||||
id: '456',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: '789',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('does not migrate if "actions" is not an array', () => {
|
||||
const doc: PartialForTests = {
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: 'string', // invalid as this is not an array
|
||||
},
|
||||
} as unknown as PartialForTests;
|
||||
expect(
|
||||
legacyMigrateRuleAlertId(
|
||||
doc as SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
)
|
||||
).toEqual({
|
||||
attributes: {
|
||||
ruleAlertId: '123',
|
||||
actions: 'string',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('migrateAlertId', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyMigrateAlertId>;
|
||||
|
||||
test('it migrates a "ruleAlertId" when the existing references is an empty array', () => {
|
||||
expect(
|
||||
legacyMigrateAlertId({ ruleAlertId: '123', existingReferences: [] })
|
||||
).toEqual<FuncReturn>([
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('it does not return existing references when it migrates a "ruleAlertId"', () => {
|
||||
const existingReferences: SavedObjectReference[] = [
|
||||
{
|
||||
id: '456',
|
||||
name: 'foreign_0',
|
||||
type: 'foreign',
|
||||
},
|
||||
];
|
||||
expect(legacyMigrateAlertId({ ruleAlertId: '123', existingReferences })).toEqual<FuncReturn>([
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('it does not migrate twice if "ruleAlertId" is already migrated by returning an empty array', () => {
|
||||
const existingReferences: SavedObjectReference[] = [
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
];
|
||||
expect(legacyMigrateAlertId({ ruleAlertId: '123', existingReferences })).toEqual<FuncReturn>(
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test('it does not return existing references when it migrates a "ruleAlertId" and does not migrate twice if "ruleAlertId" is already migrated by returning an empty array', () => {
|
||||
const existingReferences: SavedObjectReference[] = [
|
||||
{
|
||||
id: '456',
|
||||
name: 'foreign_0',
|
||||
type: 'foreign',
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
},
|
||||
];
|
||||
expect(legacyMigrateAlertId({ ruleAlertId: '123', existingReferences })).toEqual<FuncReturn>(
|
||||
[]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,14 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
||||
import { isString } from 'lodash/fp';
|
||||
import {
|
||||
SavedObjectUnsanitizedDoc,
|
||||
SavedObjectSanitizedDoc,
|
||||
SavedObjectAttributes,
|
||||
SavedObjectReference,
|
||||
} from '../../../../../../../src/core/server';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
import {
|
||||
LegacyIRuleActionsAttributesSavedObjectAttributes,
|
||||
LegacyRuleAlertAction,
|
||||
LegacyRuleAlertSavedObjectAction,
|
||||
} from './legacy_types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
legacyGetActionReference,
|
||||
legacyGetRuleReference,
|
||||
legacyTransformLegacyRuleAlertActionToReference,
|
||||
} from './legacy_utils';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -89,7 +101,7 @@ export const legacyRuleActionsSavedObjectMigration = {
|
|||
},
|
||||
},
|
||||
},
|
||||
] as RuleAlertAction[];
|
||||
] as LegacyRuleAlertSavedObjectAction[];
|
||||
} else if (action.action_type_id === '.jira') {
|
||||
const { title, comments, description, issueType, priority, labels, parent, summary } =
|
||||
action.params.subActionParams as {
|
||||
|
@ -121,7 +133,7 @@ export const legacyRuleActionsSavedObjectMigration = {
|
|||
},
|
||||
},
|
||||
},
|
||||
] as RuleAlertAction[];
|
||||
] as LegacyRuleAlertSavedObjectAction[];
|
||||
} else if (action.action_type_id === '.resilient') {
|
||||
const { title, comments, description, incidentTypes, severityCode, name } = action.params
|
||||
.subActionParams as {
|
||||
|
@ -149,12 +161,12 @@ export const legacyRuleActionsSavedObjectMigration = {
|
|||
},
|
||||
},
|
||||
},
|
||||
] as RuleAlertAction[];
|
||||
] as LegacyRuleAlertSavedObjectAction[];
|
||||
}
|
||||
}
|
||||
|
||||
return [...acc, action];
|
||||
}, [] as RuleAlertAction[]);
|
||||
}, [] as LegacyRuleAlertSavedObjectAction[]);
|
||||
|
||||
return {
|
||||
...doc,
|
||||
|
@ -165,4 +177,102 @@ export const legacyRuleActionsSavedObjectMigration = {
|
|||
references: doc.references || [],
|
||||
};
|
||||
},
|
||||
'7.16.0': (
|
||||
doc: SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
): SavedObjectSanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes> => {
|
||||
return legacyMigrateRuleAlertId(doc);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This migrates rule_id's and actions within the legacy siem.notification to saved object references on an upgrade.
|
||||
* We only migrate rule_id if we find these conditions:
|
||||
* - ruleAlertId is a string and not null, undefined, or malformed data.
|
||||
* - The existing references do not already have a ruleAlertId found within it.
|
||||
* We only migrate the actions if we find these conditions:
|
||||
* - The actions exists and is an array.
|
||||
* Some of these issues could crop up during either user manual errors of modifying things, earlier migration
|
||||
* issues, etc... so we are safer to check them as possibilities
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
* @param doc The document that might have ruleId's to migrate into the references
|
||||
* @returns The document migrated with saved object references
|
||||
*/
|
||||
export const legacyMigrateRuleAlertId = (
|
||||
doc: SavedObjectUnsanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
): SavedObjectSanitizedDoc<LegacyIRuleActionsAttributesSavedObjectAttributes> => {
|
||||
const {
|
||||
attributes: { actions },
|
||||
references,
|
||||
} = doc;
|
||||
// remove the ruleAlertId from the doc
|
||||
const { ruleAlertId, ...attributesWithoutRuleAlertId } = doc.attributes;
|
||||
const existingReferences = references ?? [];
|
||||
if (!isString(ruleAlertId) || !Array.isArray(actions)) {
|
||||
// early return if we are not a string or if we are not a security solution notification saved object.
|
||||
return { ...doc, references: existingReferences };
|
||||
} else {
|
||||
const alertReferences = legacyMigrateAlertId({
|
||||
ruleAlertId,
|
||||
existingReferences,
|
||||
});
|
||||
|
||||
// we use flat map here to be "idempotent" and skip it if it has already been migrated for any particular reason
|
||||
const actionsReferences = actions.flatMap<SavedObjectReference>((action, index) => {
|
||||
if (
|
||||
existingReferences.find((reference) => {
|
||||
return (
|
||||
// we as cast it to the pre-7.16 version to get the old id from it
|
||||
(action as unknown as LegacyRuleAlertAction).id === reference.id &&
|
||||
reference.type === 'action'
|
||||
);
|
||||
})
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
// we as cast it to the pre-7.16 version to get the old id from it
|
||||
legacyGetActionReference((action as unknown as LegacyRuleAlertAction).id, index),
|
||||
];
|
||||
});
|
||||
|
||||
const actionsWithRef = actions.map((action, index) =>
|
||||
// we as cast it to the pre-7.16 version to pass it to get the actions with ref.
|
||||
legacyTransformLegacyRuleAlertActionToReference(
|
||||
action as unknown as LegacyRuleAlertAction,
|
||||
index
|
||||
)
|
||||
);
|
||||
return {
|
||||
...doc,
|
||||
attributes: {
|
||||
...attributesWithoutRuleAlertId.attributes,
|
||||
actions: actionsWithRef,
|
||||
},
|
||||
references: [...existingReferences, ...alertReferences, ...actionsReferences],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a helper to migrate "ruleAlertId"
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
* @param existingReferences The existing saved object references
|
||||
* @param ruleAlertId The ruleAlertId to migrate
|
||||
* @returns The savedObjectReferences migrated
|
||||
*/
|
||||
export const legacyMigrateAlertId = ({
|
||||
existingReferences,
|
||||
ruleAlertId,
|
||||
}: {
|
||||
existingReferences: SavedObjectReference[];
|
||||
ruleAlertId: string;
|
||||
}): SavedObjectReference[] => {
|
||||
const existingReferenceFound = existingReferences.find((reference) => {
|
||||
return reference.id === ruleAlertId && reference.type === 'alert';
|
||||
});
|
||||
if (existingReferenceFound) {
|
||||
return [];
|
||||
} else {
|
||||
return [legacyGetRuleReference(ruleAlertId)];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -30,10 +30,14 @@ const legacyRuleActionsSavedObjectMappings: SavedObjectsType['mappings'] = {
|
|||
},
|
||||
actions: {
|
||||
properties: {
|
||||
actionRef: {
|
||||
type: 'keyword',
|
||||
},
|
||||
group: {
|
||||
type: 'keyword',
|
||||
},
|
||||
id: {
|
||||
// "actions.id" is no longer used since the saved object references and "actionRef" was introduced. It is still here for legacy reasons such as migrations.
|
||||
type: 'keyword',
|
||||
},
|
||||
action_type_id: {
|
||||
|
|
|
@ -6,7 +6,30 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectAttributes } from 'kibana/server';
|
||||
import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
||||
import { AlertActionParams } from '../../../../../alerting/common';
|
||||
|
||||
/**
|
||||
* This was the pre-7.16 version of LegacyRuleAlertAction and how it was stored on disk pre-7.16.
|
||||
* Post 7.16 this is how it is serialized from the saved object from disk since we are using saved object references.
|
||||
* @deprecated Remove this once the legacy notification/side car is gone
|
||||
*/
|
||||
export interface LegacyRuleAlertAction {
|
||||
group: string;
|
||||
id: string;
|
||||
params: AlertActionParams;
|
||||
action_type_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is how it is stored on disk in its "raw format" for 7.16+
|
||||
* @deprecated Remove this once the legacy notification/side car is gone
|
||||
*/
|
||||
export interface LegacyRuleAlertSavedObjectAction {
|
||||
group: string;
|
||||
params: AlertActionParams;
|
||||
action_type_id: string;
|
||||
actionRef: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* We keep this around to migrate and update data for the old deprecated rule actions saved object mapping but we
|
||||
|
@ -16,8 +39,7 @@ import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
|||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface LegacyIRuleActionsAttributes extends Record<string, any> {
|
||||
ruleAlertId: string;
|
||||
actions: RuleAlertAction[];
|
||||
actions: LegacyRuleAlertSavedObjectAction[];
|
||||
ruleThrottle: string;
|
||||
alertThrottle: string | null;
|
||||
}
|
||||
|
@ -37,7 +59,7 @@ export interface LegacyIRuleActionsAttributesSavedObjectAttributes
|
|||
*/
|
||||
export interface LegacyRuleActions {
|
||||
id: string;
|
||||
actions: RuleAlertAction[];
|
||||
actions: LegacyRuleAlertAction[];
|
||||
ruleThrottle: string;
|
||||
alertThrottle: string | null;
|
||||
}
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger } from 'src/core/server';
|
||||
import { AlertAction } from '../../../../../alerting/common';
|
||||
import { AlertServices } from '../../../../../alerting/server';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetRuleActionsSavedObject } from './legacy_get_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyCreateRuleActionsSavedObject } from './legacy_create_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyUpdateRuleActionsSavedObject } from './legacy_update_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRuleActions } from './legacy_types';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -24,20 +24,26 @@ interface LegacyUpdateOrCreateRuleActionsSavedObject {
|
|||
savedObjectsClient: AlertServices['savedObjectsClient'];
|
||||
actions: AlertAction[] | undefined;
|
||||
throttle: string | null | undefined;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This should _only_ be seen to be used within the legacy route of "legacyCreateLegacyNotificationRoute" and not exposed and not
|
||||
* used anywhere else. If you see it being used anywhere else, that would be a bug.
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
* @see legacyCreateLegacyNotificationRoute
|
||||
*/
|
||||
export const legacyUpdateOrCreateRuleActionsSavedObject = async ({
|
||||
savedObjectsClient,
|
||||
ruleAlertId,
|
||||
actions,
|
||||
throttle,
|
||||
}: LegacyUpdateOrCreateRuleActionsSavedObject): Promise<LegacyRuleActions> => {
|
||||
logger,
|
||||
}: LegacyUpdateOrCreateRuleActionsSavedObject): Promise<void> => {
|
||||
const ruleActions = await legacyGetRuleActionsSavedObject({
|
||||
ruleAlertId,
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
});
|
||||
|
||||
if (ruleActions != null) {
|
||||
|
|
|
@ -5,17 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectReference } from 'kibana/server';
|
||||
import { AlertServices } from '../../../../../alerting/server';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyGetThrottleOptions } from './legacy_utils';
|
||||
import {
|
||||
legacyGetActionReference,
|
||||
legacyGetRuleReference,
|
||||
legacyGetThrottleOptions,
|
||||
legacyTransformActionToReference,
|
||||
legacyTransformLegacyRuleAlertActionToReference,
|
||||
} from './legacy_utils';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
import { AlertAction } from '../../../../../alerting/common';
|
||||
import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -29,6 +35,8 @@ interface LegacyUpdateRuleActionsSavedObject {
|
|||
}
|
||||
|
||||
/**
|
||||
* NOTE: This should _only_ be seen to be used within the legacy route of "legacyCreateLegacyNotificationRoute" and not exposed and not
|
||||
* used anywhere else. If you see it being used anywhere else, that would be a bug.
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyUpdateRuleActionsSavedObject = async ({
|
||||
|
@ -37,7 +45,14 @@ export const legacyUpdateRuleActionsSavedObject = async ({
|
|||
actions,
|
||||
throttle,
|
||||
ruleActions,
|
||||
}: LegacyUpdateRuleActionsSavedObject): Promise<LegacyRulesActionsSavedObject> => {
|
||||
}: LegacyUpdateRuleActionsSavedObject): Promise<void> => {
|
||||
const referenceWithAlertId = [legacyGetRuleReference(ruleAlertId)];
|
||||
const actionReferences =
|
||||
actions != null
|
||||
? actions.map((action, index) => legacyGetActionReference(action.id, index))
|
||||
: ruleActions.actions.map((action, index) => legacyGetActionReference(action.id, index));
|
||||
|
||||
const references: SavedObjectReference[] = [...referenceWithAlertId, ...actionReferences];
|
||||
const throttleOptions = throttle
|
||||
? legacyGetThrottleOptions(throttle)
|
||||
: {
|
||||
|
@ -45,25 +60,20 @@ export const legacyUpdateRuleActionsSavedObject = async ({
|
|||
alertThrottle: ruleActions.alertThrottle,
|
||||
};
|
||||
|
||||
const options = {
|
||||
const attributes: LegacyIRuleActionsAttributesSavedObjectAttributes = {
|
||||
actions:
|
||||
actions != null
|
||||
? actions.map((action) => transformAlertToRuleAction(action))
|
||||
: ruleActions.actions,
|
||||
? actions.map((alertAction, index) => legacyTransformActionToReference(alertAction, index))
|
||||
: ruleActions.actions.map((alertAction, index) =>
|
||||
legacyTransformLegacyRuleAlertActionToReference(alertAction, index)
|
||||
),
|
||||
...throttleOptions,
|
||||
};
|
||||
|
||||
await savedObjectsClient.update<LegacyIRuleActionsAttributesSavedObjectAttributes>(
|
||||
legacyRuleActionsSavedObjectType,
|
||||
ruleActions.id,
|
||||
{
|
||||
ruleAlertId,
|
||||
...options,
|
||||
}
|
||||
attributes,
|
||||
{ references }
|
||||
);
|
||||
|
||||
return {
|
||||
id: ruleActions.id,
|
||||
...options,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* 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 { SavedObjectsUpdateResponse } from 'kibana/server';
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
|
||||
import { AlertAction } from '../../../../../alerting/common';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { legacyRuleActionsSavedObjectType } from './legacy_saved_object_mappings';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
LegacyIRuleActionsAttributesSavedObjectAttributes,
|
||||
LegacyRuleAlertAction,
|
||||
} from './legacy_types';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
legacyGetActionReference,
|
||||
legacyGetRuleActionsFromSavedObject,
|
||||
legacyGetRuleReference,
|
||||
legacyGetThrottleOptions,
|
||||
legacyTransformActionToReference,
|
||||
legacyTransformLegacyRuleAlertActionToReference,
|
||||
} from './legacy_utils';
|
||||
|
||||
describe('legacy_utils', () => {
|
||||
describe('legacyGetRuleActionsFromSavedObject', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyGetRuleActionsFromSavedObject>;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = loggingSystemMock.createLogger();
|
||||
});
|
||||
|
||||
test('returns no_actions and an alert throttle of null if nothing is in the references or in the attributes', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [],
|
||||
attributes: {},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
actions: [],
|
||||
alertThrottle: null,
|
||||
id: '123',
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
|
||||
test('returns "no_throttle" if the rule throttle is not set', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [],
|
||||
},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
actions: [],
|
||||
alertThrottle: null,
|
||||
id: '123',
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 1 action transformed through the find if 1 was found for 1 single alert id', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
alertThrottle: '1d',
|
||||
id: '123',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 2 actions transformed through the find if 1 was found for 1 single alert id', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'action-123',
|
||||
type: 'action',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_1',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_1',
|
||||
group: 'group_1',
|
||||
id: 'action-123',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
action_type_id: 'action_type_2',
|
||||
group: 'group_2',
|
||||
id: 'action-456',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('returns 1 action transformed through the find if 1 was found for 1 single alert id a but a 2nd was not', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_1',
|
||||
id: 'action-456',
|
||||
type: 'action',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
{
|
||||
group: 'group_2',
|
||||
params: {},
|
||||
action_type_id: 'action_type_2',
|
||||
actionRef: 'action_1',
|
||||
},
|
||||
],
|
||||
ruleThrottle: '1d',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
id: '123',
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: 'action_type_2',
|
||||
group: 'group_2',
|
||||
id: 'action-456',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('returns empty actions array and "no_actions" if it cannot be found in the references', async () => {
|
||||
const savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes> =
|
||||
{
|
||||
id: '123',
|
||||
type: legacyRuleActionsSavedObjectType,
|
||||
references: [
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'alert-123',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
attributes: {
|
||||
actions: [
|
||||
{
|
||||
group: 'group_1',
|
||||
params: {},
|
||||
action_type_id: 'action_type_1',
|
||||
actionRef: 'action_0',
|
||||
},
|
||||
],
|
||||
ruleThrottle: 'no_actions',
|
||||
alertThrottle: '1d',
|
||||
},
|
||||
};
|
||||
expect(legacyGetRuleActionsFromSavedObject(savedObject, logger)).toEqual<FuncReturn>({
|
||||
actions: [],
|
||||
alertThrottle: '1d',
|
||||
id: '123',
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacyGetThrottleOptions', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyGetThrottleOptions>;
|
||||
|
||||
test('it returns "no_actions" and "alertThrottle" set to "null" if given no throttle', () => {
|
||||
expect(legacyGetThrottleOptions()).toEqual<FuncReturn>({
|
||||
alertThrottle: null,
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns "no_actions" and "alertThrottle" set to "null" if given a null throttle', () => {
|
||||
expect(legacyGetThrottleOptions(null)).toEqual<FuncReturn>({
|
||||
alertThrottle: null,
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns "1d" if given a "1d" throttle', () => {
|
||||
expect(legacyGetThrottleOptions('1d')).toEqual<FuncReturn>({
|
||||
alertThrottle: '1d',
|
||||
ruleThrottle: '1d',
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns null and "no_actions" if given a "no_actions"', () => {
|
||||
expect(legacyGetThrottleOptions('no_actions')).toEqual<FuncReturn>({
|
||||
alertThrottle: null,
|
||||
ruleThrottle: 'no_actions',
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns null and "rule" if given a "rule"', () => {
|
||||
expect(legacyGetThrottleOptions('rule')).toEqual<FuncReturn>({
|
||||
alertThrottle: null,
|
||||
ruleThrottle: 'rule',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacyGetRuleReference', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyGetRuleReference>;
|
||||
|
||||
test('it returns the id transformed', () => {
|
||||
expect(legacyGetRuleReference('123')).toEqual<FuncReturn>({
|
||||
id: '123',
|
||||
name: 'alert_0',
|
||||
type: 'alert',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacyGetActionReference', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyGetActionReference>;
|
||||
|
||||
test('it returns the id and index transformed with the index at 0', () => {
|
||||
expect(legacyGetActionReference('123', 0)).toEqual<FuncReturn>({
|
||||
id: '123',
|
||||
name: 'action_0',
|
||||
type: 'action',
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns the id and index transformed with the index at 1', () => {
|
||||
expect(legacyGetActionReference('123', 1)).toEqual<FuncReturn>({
|
||||
id: '123',
|
||||
name: 'action_1',
|
||||
type: 'action',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacyTransformActionToReference', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyTransformActionToReference>;
|
||||
const alertAction: AlertAction = {
|
||||
id: '123',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
actionTypeId: '567',
|
||||
};
|
||||
|
||||
test('it returns the id and index transformed with the index at 0', () => {
|
||||
expect(legacyTransformActionToReference(alertAction, 0)).toEqual<FuncReturn>({
|
||||
actionRef: 'action_0',
|
||||
action_type_id: '567',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns the id and index transformed with the index at 1', () => {
|
||||
expect(legacyTransformActionToReference(alertAction, 1)).toEqual<FuncReturn>({
|
||||
actionRef: 'action_1',
|
||||
action_type_id: '567',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacyTransformLegacyRuleAlertActionToReference', () => {
|
||||
type FuncReturn = ReturnType<typeof legacyTransformLegacyRuleAlertActionToReference>;
|
||||
const alertAction: LegacyRuleAlertAction = {
|
||||
id: '123',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
action_type_id: '567',
|
||||
};
|
||||
|
||||
test('it returns the id and index transformed with the index at 0', () => {
|
||||
expect(legacyTransformLegacyRuleAlertActionToReference(alertAction, 0)).toEqual<FuncReturn>({
|
||||
actionRef: 'action_0',
|
||||
action_type_id: '567',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns the id and index transformed with the index at 1', () => {
|
||||
expect(legacyTransformLegacyRuleAlertActionToReference(alertAction, 1)).toEqual<FuncReturn>({
|
||||
actionRef: 'action_1',
|
||||
action_type_id: '567',
|
||||
group: 'group_1',
|
||||
params: {
|
||||
test: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,9 +6,16 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsUpdateResponse } from 'kibana/server';
|
||||
import { RuleAlertAction } from '../../../../common/detection_engine/types';
|
||||
import { Logger } from 'src/core/server';
|
||||
|
||||
import { AlertAction } from '../../../../../alerting/common';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy_types';
|
||||
import {
|
||||
LegacyIRuleActionsAttributesSavedObjectAttributes,
|
||||
LegacyRuleAlertAction,
|
||||
LegacyRuleAlertSavedObjectAction,
|
||||
} from './legacy_types';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -27,15 +34,105 @@ export const legacyGetThrottleOptions = (
|
|||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyGetRuleActionsFromSavedObject = (
|
||||
savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes>
|
||||
savedObject: SavedObjectsUpdateResponse<LegacyIRuleActionsAttributesSavedObjectAttributes>,
|
||||
logger: Logger
|
||||
): {
|
||||
id: string;
|
||||
actions: RuleAlertAction[];
|
||||
actions: LegacyRuleAlertAction[];
|
||||
alertThrottle: string | null;
|
||||
ruleThrottle: string;
|
||||
} => ({
|
||||
id: savedObject.id,
|
||||
actions: savedObject.attributes.actions || [],
|
||||
alertThrottle: savedObject.attributes.alertThrottle || null,
|
||||
ruleThrottle: savedObject.attributes.ruleThrottle || 'no_actions',
|
||||
} => {
|
||||
const existingActions = savedObject.attributes.actions ?? [];
|
||||
// We have to serialize the action from the saved object references
|
||||
const actionsWithIdReplacedFromReference = existingActions.flatMap<LegacyRuleAlertAction>(
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
({ group, params, action_type_id, actionRef }) => {
|
||||
const found = savedObject.references?.find(
|
||||
(reference) => actionRef === reference.name && reference.type === 'action'
|
||||
);
|
||||
if (found) {
|
||||
return [
|
||||
{
|
||||
id: found.id,
|
||||
group,
|
||||
params,
|
||||
action_type_id,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// We cannot find it so we return no actions. This line should not be reached.
|
||||
logger.error(
|
||||
[
|
||||
'Security Solution notification (Legacy) Expected to find an action within the action reference of:',
|
||||
`${actionRef} inside of the references of ${savedObject.references} but did not. Skipping this action.`,
|
||||
].join('')
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
);
|
||||
return {
|
||||
id: savedObject.id,
|
||||
actions: actionsWithIdReplacedFromReference,
|
||||
alertThrottle: savedObject.attributes.alertThrottle || null,
|
||||
ruleThrottle:
|
||||
savedObject.attributes.ruleThrottle == null || actionsWithIdReplacedFromReference.length === 0
|
||||
? 'no_actions'
|
||||
: savedObject.attributes.ruleThrottle,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an id this returns a legacy rule reference.
|
||||
* @param id The id of the alert
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyGetRuleReference = (id: string) => ({
|
||||
id,
|
||||
type: 'alert',
|
||||
name: 'alert_0',
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an id this returns a legacy action reference.
|
||||
* @param id The id of the action
|
||||
* @param index The index of the action
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyGetActionReference = (id: string, index: number) => ({
|
||||
id,
|
||||
type: 'action',
|
||||
name: `action_${index}`,
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an alertAction this returns a transformed legacy action as a reference.
|
||||
* @param alertAction The alertAction
|
||||
* @param index The index of the action
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyTransformActionToReference = (
|
||||
alertAction: AlertAction,
|
||||
index: number
|
||||
): LegacyRuleAlertSavedObjectAction => ({
|
||||
actionRef: `action_${index}`,
|
||||
group: alertAction.group,
|
||||
params: alertAction.params,
|
||||
action_type_id: alertAction.actionTypeId,
|
||||
});
|
||||
|
||||
/**
|
||||
* Given an alertAction this returns a transformed legacy action as a reference.
|
||||
* @param alertAction The alertAction
|
||||
* @param index The index of the action
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
*/
|
||||
export const legacyTransformLegacyRuleAlertActionToReference = (
|
||||
alertAction: LegacyRuleAlertAction,
|
||||
index: number
|
||||
): LegacyRuleAlertSavedObjectAction => ({
|
||||
actionRef: `action_${index}`,
|
||||
group: alertAction.group,
|
||||
params: alertAction.params,
|
||||
action_type_id: alertAction.action_type_id,
|
||||
});
|
||||
|
|
|
@ -285,6 +285,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
plugins.security,
|
||||
plugins.ml,
|
||||
ruleDataService,
|
||||
this.logger,
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
registerEndpointRoutes(router, endpointContext);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger } from 'src/core/server';
|
||||
import { RuleDataPluginService } from '../../../rule_registry/server';
|
||||
|
||||
import { SecuritySolutionPluginRouter } from '../types';
|
||||
|
@ -67,19 +68,20 @@ export const initRoutes = (
|
|||
security: SetupPlugins['security'],
|
||||
ml: SetupPlugins['ml'],
|
||||
ruleDataService: RuleDataPluginService,
|
||||
logger: Logger,
|
||||
isRuleRegistryEnabled: boolean
|
||||
) => {
|
||||
// Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules
|
||||
// All REST rule creation, deletion, updating, etc......
|
||||
createRulesRoute(router, ml, isRuleRegistryEnabled);
|
||||
readRulesRoute(router, isRuleRegistryEnabled);
|
||||
readRulesRoute(router, logger, isRuleRegistryEnabled);
|
||||
updateRulesRoute(router, ml, isRuleRegistryEnabled);
|
||||
patchRulesRoute(router, ml, isRuleRegistryEnabled);
|
||||
deleteRulesRoute(router, isRuleRegistryEnabled);
|
||||
findRulesRoute(router, isRuleRegistryEnabled);
|
||||
findRulesRoute(router, logger, isRuleRegistryEnabled);
|
||||
|
||||
// Once we no longer have the legacy notifications system/"side car actions" this should be removed.
|
||||
legacyCreateLegacyNotificationRoute(router);
|
||||
legacyCreateLegacyNotificationRoute(router, logger);
|
||||
|
||||
// TODO: pass isRuleRegistryEnabled to all relevant routes
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./runtime'));
|
||||
loadTestFile(require.resolve('./throttle'));
|
||||
loadTestFile(require.resolve('./ignore_fields'));
|
||||
loadTestFile(require.resolve('./migrations'));
|
||||
});
|
||||
|
||||
// That split here enable us on using a different ciGroup to run the tests
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const es = getService('es');
|
||||
|
||||
describe('migrations', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/security_solution/migrations');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/migrations');
|
||||
});
|
||||
|
||||
describe('7.16.0', () => {
|
||||
it('migrates legacy siem-detection-engine-rule-actions to use saved object references', async () => {
|
||||
const response = await es.get<{
|
||||
'siem-detection-engine-rule-actions': {
|
||||
ruleAlertId: string;
|
||||
actions: [{ id: string; actionRef: string }];
|
||||
};
|
||||
references: [{}];
|
||||
}>({
|
||||
index: '.kibana',
|
||||
id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3',
|
||||
});
|
||||
expect(response.statusCode).to.eql(200);
|
||||
|
||||
// references exist and are expected values
|
||||
expect(response.body._source?.references).to.eql([
|
||||
{
|
||||
name: 'alert_0',
|
||||
id: 'fb1046a0-0452-11ec-9b15-d13d79d162f3',
|
||||
type: 'alert',
|
||||
},
|
||||
{
|
||||
name: 'action_0',
|
||||
id: 'f6e64c00-0452-11ec-9b15-d13d79d162f3',
|
||||
type: 'action',
|
||||
},
|
||||
]);
|
||||
|
||||
// actionRef exists and is the expected value
|
||||
expect(
|
||||
response.body._source?.['siem-detection-engine-rule-actions'].actions[0].actionRef
|
||||
).to.eql('action_0');
|
||||
|
||||
// ruleAlertId no longer exist
|
||||
expect(response.body._source?.['siem-detection-engine-rule-actions'].ruleAlertId).to.eql(
|
||||
undefined
|
||||
);
|
||||
|
||||
// actions.id no longer exist
|
||||
expect(response.body._source?.['siem-detection-engine-rule-actions'].actions[0].id).to.eql(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3",
|
||||
"index": ".kibana_1",
|
||||
"source": {
|
||||
"siem-detection-engine-rule-actions": {
|
||||
"ruleAlertId": "fb1046a0-0452-11ec-9b15-d13d79d162f3",
|
||||
"actions": [
|
||||
{
|
||||
"action_type_id": ".slack",
|
||||
"id": "f6e64c00-0452-11ec-9b15-d13d79d162f3",
|
||||
"params": {
|
||||
"message": "Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts"
|
||||
},
|
||||
"group": "default"
|
||||
}
|
||||
],
|
||||
"ruleThrottle": "7d",
|
||||
"alertThrottle": "7d"
|
||||
},
|
||||
"type": "siem-detection-engine-rule-actions",
|
||||
"references": [],
|
||||
"migrationVersion": {
|
||||
"siem-detection-engine-rule-actions": "7.11.2"
|
||||
},
|
||||
"coreMigrationVersion": "7.14.0",
|
||||
"updated_at": "2021-09-15T22:18:48.369Z"
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue