From ba9743f96a61f79e0bcae3a683d4f01de73e3d51 Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Wed, 18 Oct 2023 15:02:53 +0200 Subject: [PATCH 01/12] move add and update actions --- .buildkite/ftr_configs.yml | 4 +- .../config/serverless/config.base.ts | 5 - .../package.json | 6 +- .../default_license/actions/add_actions.ts | 110 +++++++++ .../actions/configs/ess.config.ts | 22 ++ .../actions/configs/serverless.config.ts | 15 ++ .../default_license/actions/index.ts | 14 ++ .../default_license/actions/update_actions.ts | 210 ++++++++++++++++++ .../utils/actions/create_new_action.ts | 34 +++ .../utils/actions/index.ts | 1 + .../rules/get_rule_with_web_hook_action.ts | 34 +++ ...simple_rule_output_with_web_hook_action.ts | 29 +++ .../detections_response/utils/rules/index.ts | 4 + .../utils/rules/rule_to_update_schema.ts | 37 +++ .../utils/rules/update_rule.ts | 41 ++++ 15 files changed, 559 insertions(+), 7 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_web_hook_action.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_with_web_hook_action.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_update_schema.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index f026499502e0d..7abd52a1ea153 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -457,7 +457,9 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts - + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts + diff --git a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts index 105701ec61e08..6790c39c85115 100644 --- a/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/serverless/config.base.ts @@ -5,8 +5,6 @@ * 2.0. */ import { FtrConfigProviderContext } from '@kbn/test'; -// import { ES_RESOURCES } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/serverless'; - export interface CreateTestConfigOptions { testFiles: string[]; junit: { reportName: string }; @@ -24,9 +22,6 @@ export function createTestConfig(options: CreateTestConfigOptions) { ...svlSharedConfig.get('kbnTestServer'), serverArgs: [...svlSharedConfig.get('kbnTestServer.serverArgs'), '--serverless=security'], }, - // esServerlessOptions: { - // resources: Object.values(ES_RESOURCES), - // }, testFiles: options.testFiles, junit: options.junit, diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index ed8252f86c2da..514aca887e332 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -24,6 +24,10 @@ "rule_creation:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts", "rule_creation:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", "rule_creation:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts", - "rule_creation:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts --grep @ess" + "rule_creation:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts --grep @ess", + "actions:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/actions/configs/serverless.config.ts", + "actions:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/actions/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "actions:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/actions/configs/ess.config.ts", + "actions:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/actions/configs/ess.config.ts --grep @ess" } } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts new file mode 100644 index 0000000000000..b7ef4949a7f11 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts @@ -0,0 +1,110 @@ +/* + * 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 { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { + createAlertsIndex, + deleteAllRules, + removeServerGeneratedProperties, + getWebHookAction, + getRuleWithWebHookAction, + getSimpleRuleOutputWithWebHookAction, + waitForRuleSuccess, + createRule, + deleteAllAlerts, + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const es = getService('es'); + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + describe('@serverless @ess add_actions', () => { + describe('adding actions', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should be able to create a new webhook action and attach it to a rule', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); + const bodyToCompare = removeServerGeneratedProperties(rule); + const expected = getSimpleRuleOutputWithWebHookAction( + `${bodyToCompare?.actions?.[0].id}`, + `${bodyToCompare?.actions?.[0].uuid}` + ); + const expectedRuleWithUserUpdated = updateUsername(expected, ELASTICSEARCH_USERNAME); + expect(bodyToCompare).to.eql(expectedRuleWithUserUpdated); + }); + + it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const rule = await createRule( + supertest, + log, + getRuleWithWebHookAction(hookAction.id, true) + ); + await waitForRuleSuccess({ supertest, log, id: rule.id }); + }); + + it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule with the action attached and a meta field + const ruleWithAction: RuleCreateProps = { + ...getRuleWithWebHookAction(hookAction.id, true), + meta: {}, + }; + + const rule = await createRule(supertest, log, ruleWithAction); + await waitForRuleSuccess({ supertest, log, id: rule.id }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts new file mode 100644 index 0000000000000..cec8d1cca41b5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/ess.config.ts @@ -0,0 +1,22 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../config/ess/config.base.trial') + ); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine ESS/Actions API Integration Tests', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts new file mode 100644 index 0000000000000..66edc0eef7f30 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/configs/serverless.config.ts @@ -0,0 +1,15 @@ +/* + * 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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Detection Engine Serverless/Actions API Integration Tests', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts new file mode 100644 index 0000000000000..1268e6193b531 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Exceptions API', function () { + loadTestFile(require.resolve('./add_actions')); + loadTestFile(require.resolve('./update_actions')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts new file mode 100644 index 0000000000000..292304a9cd637 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts @@ -0,0 +1,210 @@ +/* + * 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 { omit } from 'lodash'; + +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + removeServerGeneratedProperties, + getRuleWithWebHookAction, + getSimpleRuleOutputWithWebHookAction, + waitForRuleSuccess, + createRule, + getSimpleRule, + updateRule, + installMockPrebuiltRules, + getRule, + createNewAction, + findImmutableRuleById, + getPrebuiltRulesAndTimelinesStatus, + getSimpleRuleOutput, + ruleToUpdateSchema, + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const es = getService('es'); + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + + const getImmutableRule = async () => { + await installMockPrebuiltRules(supertest, es); + return getRule(supertest, log, ELASTIC_SECURITY_RULE_ID); + }; + + describe('@serverless @ess update_actions', () => { + describe('updating actions', () => { + before(async () => { + await esArchiver.load(path); + }); + + after(async () => { + await esArchiver.unload(path); + }); + + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should be able to create a new webhook action and update a rule with the webhook action', async () => { + const hookAction = await createNewAction(supertest, log); + const rule = getSimpleRule(); + await createRule(supertest, log, rule); + const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + const bodyToCompare = removeServerGeneratedProperties(updatedRule); + + const expectedRule = { + ...getSimpleRuleOutputWithWebHookAction( + `${bodyToCompare.actions?.[0].id}`, + `${bodyToCompare.actions?.[0].uuid}` + ), + revision: 1, // revision bump is required since this is an updated rule and this is part of the testing that we do bump the revision number on update + }; + const expectedRuleWithUserUpdated = updateUsername(expectedRule, ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRuleWithUserUpdated); + }); + + it('should be able to add a new webhook action and then remove the action from the rule again', async () => { + const hookAction = await createNewAction(supertest, log); + const rule = getSimpleRule(); + await createRule(supertest, log, rule); + const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); + await updateRule(supertest, log, ruleToUpdate); + const ruleAfterActionRemoved = await updateRule(supertest, log, rule); + const bodyToCompare = removeServerGeneratedProperties(ruleAfterActionRemoved); + const expected = { + ...getSimpleRuleOutput(), + revision: 2, // revision bump is required since this is an updated rule and this is part of the testing that we do bump the revision number on update + }; + const expectedRuleWithUserUpdated = updateUsername(expected, ELASTICSEARCH_USERNAME); + expect(bodyToCompare).to.eql(expectedRuleWithUserUpdated); + }); + + it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { + const hookAction = await createNewAction(supertest, log); + const rule = getSimpleRule(); + await createRule(supertest, log, rule); + const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, true, rule); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); + }); + + it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { + const hookAction = await createNewAction(supertest, log); + const rule = getSimpleRule(); + await createRule(supertest, log, rule); + const ruleToUpdate: RuleCreateProps = { + ...getRuleWithWebHookAction(hookAction.id, true, rule), + meta: {}, // create a rule with the action attached and a meta field + }; + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); + }); + + it('should not change properties of immutable rule when applying actions to it', async () => { + // actions and throttle to be removed from assertion (it asserted in a separate test case) + const actionsProps = ['actions', 'throttle']; + + const immutableRule = await getImmutableRule(); + const hookAction = await createNewAction(supertest, log); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + const expected = omit(removeServerGeneratedProperties(updatedRule), actionsProps); + + const immutableRuleToAssert = { + ...omit(removeServerGeneratedProperties(immutableRule), actionsProps), + revision: 1, // Unlike `version` which is static for immutable rules, `revision` will increment when an action/exception is added + }; + + expect(immutableRuleToAssert).to.eql(expected); + expect(expected.immutable).to.be(true); // It should stay immutable true when returning + }); + + it('should be able to create a new webhook action and attach it to an immutable rule', async () => { + const immutableRule = await getImmutableRule(); + const hookAction = await createNewAction(supertest, log); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); + const updatedRule = await updateRule(supertest, log, ruleToUpdate); + const bodyToCompare = removeServerGeneratedProperties(updatedRule); + + const expected = getSimpleRuleOutputWithWebHookAction( + `${bodyToCompare.actions?.[0].id}`, + `${bodyToCompare.actions?.[0].uuid}` + ); + + const expectedRuleWithUserUpdated = updateUsername(expected, ELASTICSEARCH_USERNAME); + expect(bodyToCompare.actions).to.eql(expectedRuleWithUserUpdated.actions); + expect(bodyToCompare.throttle).to.eql(expectedRuleWithUserUpdated.throttle); + }); + + it('should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { + const immutableRule = await getImmutableRule(); + const hookAction = await createNewAction(supertest, log); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); + await updateRule(supertest, log, ruleToUpdate); + + const status = await getPrebuiltRulesAndTimelinesStatus(supertest); + expect(status.rules_not_installed).to.eql(0); + }); + + it('should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { + const immutableRule = await getImmutableRule(); + const hookAction = await createNewAction(supertest, log); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); + await updateRule(supertest, log, ruleToUpdate); + const body = await findImmutableRuleById(supertest, log, ELASTIC_SECURITY_RULE_ID); + + expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad. + const bodyToCompare = removeServerGeneratedProperties(body.data[0]); + const expected = getSimpleRuleOutputWithWebHookAction( + `${bodyToCompare.actions?.[0].id}`, + `${bodyToCompare.actions?.[0].uuid}` + ); + const expectedRuleWithUserUpdated = updateUsername(expected, ELASTICSEARCH_USERNAME); + expect(bodyToCompare.actions).to.eql(expectedRuleWithUserUpdated.actions); + expect(bodyToCompare.immutable).to.be(true); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts new file mode 100644 index 0000000000000..d5093d8d5d39b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts @@ -0,0 +1,34 @@ +/* + * 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 type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; + +import { getWebHookAction } from './get_web_hook_action'; + +/** + * Helper to cut down on the noise in some of the tests. This + * creates a new action and expects a 200 and does not do any retries. + * @param supertest The supertest deps + */ +export const createNewAction = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog +) => { + const response = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when creating a new action. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts index 438d983a69e05..d9b65ba596dd6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/index.ts @@ -7,3 +7,4 @@ export * from './get_slack_action'; export * from './get_web_hook_action'; export * from './remove_uuid_from_actions'; +export * from './create_new_action'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_web_hook_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_web_hook_action.ts new file mode 100644 index 0000000000000..6437df274098d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rule_with_web_hook_action.ts @@ -0,0 +1,34 @@ +/* + * 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 type { + RuleCreateProps, + RuleUpdateProps, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getSimpleRule } from './get_simple_rule'; + +export const getRuleWithWebHookAction = ( + id: string, + enabled = false, + rule?: RuleCreateProps +): RuleCreateProps | RuleUpdateProps => { + const finalRule = rule != null ? { ...rule, enabled } : getSimpleRule('rule-1', enabled); + return { + ...finalRule, + throttle: 'rule', + actions: [ + { + group: 'default', + id, + params: { + body: '{}', + }, + action_type_id: '.webhook', + }, + ], + }; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_with_web_hook_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_with_web_hook_action.ts new file mode 100644 index 0000000000000..7ecee679e50b3 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_output_with_web_hook_action.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { NOTIFICATION_DEFAULT_FREQUENCY } from '@kbn/security-solution-plugin/common/constants'; +import { getSimpleRuleOutput } from './get_simple_rule_output'; +import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; + +export const getSimpleRuleOutputWithWebHookAction = ( + actionId: string, + uuid: string +): RuleWithoutServerGeneratedProperties => ({ + ...getSimpleRuleOutput(), + actions: [ + { + action_type_id: '.webhook', + group: 'default', + id: actionId, + params: { + body: '{}', + }, + uuid, + frequency: NOTIFICATION_DEFAULT_FREQUENCY, + }, + ], +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts index ba91dea27743e..0170faa8ceeda 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/index.ts @@ -26,5 +26,9 @@ export * from './find_immutable_rule_by_id'; export * from './create_rule_with_exception_entries'; export * from './downgrade_immutable_rule'; export * from './get_eql_rule_for_alert_testing'; +export * from './get_rule_with_web_hook_action'; +export * from './get_simple_rule_output_with_web_hook_action'; +export * from './rule_to_update_schema'; +export * from './update_rule'; export * from './prebuilt_rules'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_update_schema.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_update_schema.ts new file mode 100644 index 0000000000000..f6669a1325eb1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_update_schema.ts @@ -0,0 +1,37 @@ +/* + * 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 type { + RuleResponse, + RuleUpdateProps, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { omit, pickBy } from 'lodash'; + +const propertiesToRemove = [ + 'id', + 'immutable', + 'updated_at', + 'updated_by', + 'created_at', + 'created_by', + 'related_integrations', + 'required_fields', + 'revision', + 'setup', + 'execution_summary', +]; + +/** + * transforms RuleResponse rule to RuleUpdateProps + * returned result can be used in rule update API calls + */ +export const ruleToUpdateSchema = (rule: RuleResponse): RuleUpdateProps => { + const removedProperties = omit(rule, propertiesToRemove); + + // We're only removing undefined values, so this cast correctly narrows the type + return pickBy(removedProperties, (value) => value !== undefined) as RuleUpdateProps; +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts new file mode 100644 index 0000000000000..53c1beb272764 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/update_rule.ts @@ -0,0 +1,41 @@ +/* + * 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 type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + RuleUpdateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * Helper to cut down on the noise in some of the tests. This checks for + * an expected 200 still and does not do any retries. + * @param supertest The supertest deps + * @param rule The rule to create + */ +export const updateRule = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + updatedRule: RuleUpdateProps +): Promise => { + const response = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when updating a rule (updateRule). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; +}; From 5de22f4b9622921476ae9b6f31e8e56de9f4dc9a Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Wed, 18 Oct 2023 15:12:05 +0200 Subject: [PATCH 02/12] remove old files from group1 --- .../security_and_spaces/group1/add_actions.ts | 103 --------- .../security_and_spaces/group1/index.ts | 2 - .../group1/update_actions.ts | 199 ------------------ 3 files changed, 304 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts deleted file mode 100644 index 2bfd3c6102e61..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllRules, - removeServerGeneratedProperties, - getWebHookAction, - getRuleWithWebHookAction, - getSimpleRuleOutputWithWebHookAction, - waitForRuleSuccess, - createRule, - deleteAllAlerts, -} from '../../utils'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const log = getService('log'); - const es = getService('es'); - - describe('add_actions', () => { - describe('adding actions', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - }); - - it('should be able to create a new webhook action and attach it to a rule', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id)); - const bodyToCompare = removeServerGeneratedProperties(rule); - expect(bodyToCompare).to.eql( - getSimpleRuleOutputWithWebHookAction( - `${bodyToCompare?.actions?.[0].id}`, - `${bodyToCompare?.actions?.[0].uuid}` - ) - ); - }); - - it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const rule = await createRule( - supertest, - log, - getRuleWithWebHookAction(hookAction.id, true) - ); - await waitForRuleSuccess({ supertest, log, id: rule.id }); - }); - - it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - // create a rule with the action attached and a meta field - const ruleWithAction: RuleCreateProps = { - ...getRuleWithWebHookAction(hookAction.id, true), - meta: {}, - }; - - const rule = await createRule(supertest, log, ruleWithAction); - await waitForRuleSuccess({ supertest, log, id: rule.id }); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index 2969429494b28..2f24e4202b7b0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -15,8 +15,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { // existence being near 0. loadTestFile(require.resolve('./aliases')); - loadTestFile(require.resolve('./add_actions')); - loadTestFile(require.resolve('./update_actions')); loadTestFile(require.resolve('./check_privileges')); loadTestFile(require.resolve('./create_index')); loadTestFile(require.resolve('./preview_rules')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts deleted file mode 100644 index 8390fc10ad7fc..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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 { omit } from 'lodash'; - -import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { ELASTIC_SECURITY_RULE_ID } from '@kbn/security-solution-plugin/common'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, - removeServerGeneratedProperties, - getRuleWithWebHookAction, - getSimpleRuleOutputWithWebHookAction, - waitForRuleSuccess, - createRule, - getSimpleRule, - updateRule, - installMockPrebuiltRules, - getRule, - createNewAction, - findImmutableRuleById, - getPrebuiltRulesAndTimelinesStatus, - getSimpleRuleOutput, - ruleToUpdateSchema, -} from '../../utils'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const es = getService('es'); - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const log = getService('log'); - - const getImmutableRule = async () => { - await installMockPrebuiltRules(supertest, es); - return getRule(supertest, log, ELASTIC_SECURITY_RULE_ID); - }; - - describe('update_actions', () => { - describe('updating actions', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - }); - - it('should be able to create a new webhook action and update a rule with the webhook action', async () => { - const hookAction = await createNewAction(supertest, log); - const rule = getSimpleRule(); - await createRule(supertest, log, rule); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); - const updatedRule = await updateRule(supertest, log, ruleToUpdate); - const bodyToCompare = removeServerGeneratedProperties(updatedRule); - - const expected = { - ...getSimpleRuleOutputWithWebHookAction( - `${bodyToCompare.actions?.[0].id}`, - `${bodyToCompare.actions?.[0].uuid}` - ), - revision: 1, // revision bump is required since this is an updated rule and this is part of the testing that we do bump the revision number on update - }; - expect(bodyToCompare).to.eql(expected); - }); - - it('should be able to add a new webhook action and then remove the action from the rule again', async () => { - const hookAction = await createNewAction(supertest, log); - const rule = getSimpleRule(); - await createRule(supertest, log, rule); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, rule); - await updateRule(supertest, log, ruleToUpdate); - const ruleAfterActionRemoved = await updateRule(supertest, log, rule); - const bodyToCompare = removeServerGeneratedProperties(ruleAfterActionRemoved); - const expected = { - ...getSimpleRuleOutput(), - revision: 2, // revision bump is required since this is an updated rule and this is part of the testing that we do bump the revision number on update - }; - expect(bodyToCompare).to.eql(expected); - }); - - it('should be able to create a new webhook action and attach it to a rule without a meta field and run it correctly', async () => { - const hookAction = await createNewAction(supertest, log); - const rule = getSimpleRule(); - await createRule(supertest, log, rule); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, true, rule); - const updatedRule = await updateRule(supertest, log, ruleToUpdate); - await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); - }); - - it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { - const hookAction = await createNewAction(supertest, log); - const rule = getSimpleRule(); - await createRule(supertest, log, rule); - const ruleToUpdate: RuleCreateProps = { - ...getRuleWithWebHookAction(hookAction.id, true, rule), - meta: {}, // create a rule with the action attached and a meta field - }; - const updatedRule = await updateRule(supertest, log, ruleToUpdate); - await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); - }); - - it('should not change properties of immutable rule when applying actions to it', async () => { - // actions and throttle to be removed from assertion (it asserted in a separate test case) - const actionsProps = ['actions', 'throttle']; - - const immutableRule = await getImmutableRule(); - const hookAction = await createNewAction(supertest, log); - const ruleToUpdate = getRuleWithWebHookAction( - hookAction.id, - immutableRule.enabled, - ruleToUpdateSchema(immutableRule) - ); - const updatedRule = await updateRule(supertest, log, ruleToUpdate); - const expected = omit(removeServerGeneratedProperties(updatedRule), actionsProps); - - const immutableRuleToAssert = { - ...omit(removeServerGeneratedProperties(immutableRule), actionsProps), - revision: 1, // Unlike `version` which is static for immutable rules, `revision` will increment when an action/exception is added - }; - - expect(immutableRuleToAssert).to.eql(expected); - expect(expected.immutable).to.be(true); // It should stay immutable true when returning - }); - - it('should be able to create a new webhook action and attach it to an immutable rule', async () => { - const immutableRule = await getImmutableRule(); - const hookAction = await createNewAction(supertest, log); - const ruleToUpdate = getRuleWithWebHookAction( - hookAction.id, - immutableRule.enabled, - ruleToUpdateSchema(immutableRule) - ); - const updatedRule = await updateRule(supertest, log, ruleToUpdate); - const bodyToCompare = removeServerGeneratedProperties(updatedRule); - - const expected = getSimpleRuleOutputWithWebHookAction( - `${bodyToCompare.actions?.[0].id}`, - `${bodyToCompare.actions?.[0].uuid}` - ); - - expect(bodyToCompare.actions).to.eql(expected.actions); - expect(bodyToCompare.throttle).to.eql(expected.throttle); - }); - - it('should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { - const immutableRule = await getImmutableRule(); - const hookAction = await createNewAction(supertest, log); - const ruleToUpdate = getRuleWithWebHookAction( - hookAction.id, - immutableRule.enabled, - ruleToUpdateSchema(immutableRule) - ); - await updateRule(supertest, log, ruleToUpdate); - - const status = await getPrebuiltRulesAndTimelinesStatus(supertest); - expect(status.rules_not_installed).to.eql(0); - }); - - it('should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { - const immutableRule = await getImmutableRule(); - const hookAction = await createNewAction(supertest, log); - const ruleToUpdate = getRuleWithWebHookAction( - hookAction.id, - immutableRule.enabled, - ruleToUpdateSchema(immutableRule) - ); - await updateRule(supertest, log, ruleToUpdate); - const body = await findImmutableRuleById(supertest, log, ELASTIC_SECURITY_RULE_ID); - - expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad. - const bodyToCompare = removeServerGeneratedProperties(body.data[0]); - const expected = getSimpleRuleOutputWithWebHookAction( - `${bodyToCompare.actions?.[0].id}`, - `${bodyToCompare.actions?.[0].uuid}` - ); - - expect(bodyToCompare.actions).to.eql(expected.actions); - expect(bodyToCompare.immutable).to.be(true); - }); - }); - }); -}; From cf9b28fe76f2ae38595ba326c5c84d731e93ab4b Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:56:11 +0100 Subject: [PATCH 03/12] Update add_actions.ts --- .../default_license/actions/add_actions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts index b7ef4949a7f11..af833bccd851e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from '@kbn/expect/expect'; import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { @@ -59,6 +59,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: hookAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .send(getWebHookAction()) .expect(200); @@ -77,6 +78,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: hookAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .send(getWebHookAction()) .expect(200); @@ -93,6 +95,7 @@ export default ({ getService }: FtrProviderContext) => { const { body: hookAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .send(getWebHookAction()) .expect(200); From b1c208769556aa9398d9515b739bcdbd28c98329 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:56:43 +0100 Subject: [PATCH 04/12] Update create_new_action.ts --- .../detections_response/utils/actions/create_new_action.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts index d5093d8d5d39b..7cbe8e858a043 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/create_new_action.ts @@ -22,6 +22,7 @@ export const createNewAction = async ( const response = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') .send(getWebHookAction()); if (response.status !== 200) { log.error( From c0f47da6925f5f422e9e80ce6df11d507e699f98 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:40:10 +0000 Subject: [PATCH 05/12] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../detections_response/default_license/actions/add_actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts index af833bccd851e..257378f7442b9 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/add_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect/expect'; +import expect from '@kbn/expect'; import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { From 915ed9a4972f18d342f0c7f24c47c267ad2def6d Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Mon, 23 Oct 2023 10:00:31 +0200 Subject: [PATCH 06/12] update apis in index.ts --- .../detections_response/default_license/actions/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts index 1268e6193b531..3b94209742e29 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('Exceptions API', function () { + describe('Actions API', function () { loadTestFile(require.resolve('./add_actions')); loadTestFile(require.resolve('./update_actions')); }); From a67bbdb5ac7b2524719ccb189d05fe706713263c Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Mon, 23 Oct 2023 11:15:48 +0200 Subject: [PATCH 07/12] move legacy actions migrations --- .../security_and_spaces/group10/index.ts | 1 - .../detections_response/default_license/actions/index.ts | 1 + .../default_license/actions}/legacy_actions_migrations.ts | 7 +++---- 3 files changed, 4 insertions(+), 5 deletions(-) rename x-pack/test/{detection_engine_api_integration/security_and_spaces/group10 => security_solution_api_integration/test_suites/detections_response/default_license/actions}/legacy_actions_migrations.ts (98%) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts index 8844107744854..673f343429ff1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts @@ -17,7 +17,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./get_rule_execution_results')); loadTestFile(require.resolve('./import_rules')); loadTestFile(require.resolve('./import_export_rules')); - loadTestFile(require.resolve('./legacy_actions_migrations')); loadTestFile(require.resolve('./read_rules')); loadTestFile(require.resolve('./resolve_read_rules')); loadTestFile(require.resolve('./update_rules')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts index 3b94209742e29..883c1bc753735 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts @@ -10,5 +10,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Actions API', function () { loadTestFile(require.resolve('./add_actions')); loadTestFile(require.resolve('./update_actions')); + loadTestFile(require.resolve('./legacy_actions_migrations')); }); } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts similarity index 98% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts index 236bf6991c9d6..fad704d5e65fa 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/legacy_actions_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts @@ -8,17 +8,16 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { getLegacyActionSOById, getLegacyActionNotificationSOById, getRuleSOById, -} from '../../utils'; +} from '../../../../../detection_engine_api_integration/utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; /** * @deprecated Once the legacy notification system is removed, remove this test too. */ -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); @@ -31,7 +30,7 @@ export default ({ getService }: FtrProviderContext) => { // For new routes that do any updates on a rule, please ensure that you are including the legacy // action migration code. We are monitoring legacy action telemetry to clean up once we see their // existence being near 0. - describe('migrate_legacy_actions', () => { + describe('@serverless @ess migrate_legacy_actions', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/legacy_actions'); }); From db199a81e9e66aead070dd945901e560196f4fde Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Mon, 23 Oct 2023 12:29:27 +0200 Subject: [PATCH 08/12] add all actions migrations under migrations and mark 7.16 as brokeninserverless --- .../security_and_spaces/group10/index.ts | 1 - .../security_and_spaces/group10/migrations.ts | 102 ---- .../default_license/actions/index.ts | 2 +- .../actions/legacy_actions_migrations.ts | 353 -------------- .../default_license/actions/migrations.ts | 451 ++++++++++++++++++ 5 files changed, 452 insertions(+), 457 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts delete mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts index 673f343429ff1..7822d11698c95 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts @@ -35,7 +35,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./runtime')); loadTestFile(require.resolve('./throttle')); loadTestFile(require.resolve('./ignore_fields')); - loadTestFile(require.resolve('./migrations')); loadTestFile(require.resolve('./risk_engine/init_and_status_apis')); loadTestFile(require.resolve('./risk_engine/risk_score_preview')); loadTestFile(require.resolve('./risk_engine/risk_score_calculation')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts deleted file mode 100644 index d43ecaff6823e..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/migrations.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -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: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', - }, - { - meta: true, - } - ); - 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 - ); - }); - - it('migrates legacy siem-detection-engine-rule-actions and retains "ruleThrottle" and "alertThrottle" as the same attributes as before', async () => { - const response = await es.get<{ - 'siem-detection-engine-rule-actions': { - ruleThrottle: string; - alertThrottle: string; - }; - }>( - { - index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', - }, - { - meta: true, - } - ); - expect(response.statusCode).to.eql(200); - - // "alertThrottle" and "ruleThrottle" should still exist - expect(response.body._source?.['siem-detection-engine-rule-actions'].alertThrottle).to.eql( - '7d' - ); - expect(response.body._source?.['siem-detection-engine-rule-actions'].ruleThrottle).to.eql( - '7d' - ); - }); - }); - }); -}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts index 883c1bc753735..5c26d445eb158 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/index.ts @@ -10,6 +10,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('Actions API', function () { loadTestFile(require.resolve('./add_actions')); loadTestFile(require.resolve('./update_actions')); - loadTestFile(require.resolve('./legacy_actions_migrations')); + loadTestFile(require.resolve('./migrations')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts deleted file mode 100644 index fad704d5e65fa..0000000000000 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/legacy_actions_migrations.ts +++ /dev/null @@ -1,353 +0,0 @@ -/* - * 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 { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - getLegacyActionSOById, - getLegacyActionNotificationSOById, - getRuleSOById, -} from '../../../../../detection_engine_api_integration/utils'; -import { FtrProviderContext } from '../../../../ftr_provider_context'; - -/** - * @deprecated Once the legacy notification system is removed, remove this test too. - */ -export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); - const es = getService('es'); - const esArchiver = getService('esArchiver'); - - // This test suite is not meant to test a specific route, but to test the legacy action migration - // code that lives in multiple routes. This code is also tested in each of the routes it lives in - // but not in as much detail and relying on mocks. This test loads an es_archive containing rules - // created in 7.15 with legacy actions. - // For new routes that do any updates on a rule, please ensure that you are including the legacy - // action migration code. We are monitoring legacy action telemetry to clean up once we see their - // existence being near 0. - describe('@serverless @ess migrate_legacy_actions', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/security_solution/legacy_actions'); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/legacy_actions' - ); - }); - - it('migrates legacy actions for rule with no actions', async () => { - const soId = '9095ee90-b075-11ec-bb3f-1f063f8e06cf'; - const ruleId = '2297be91-894c-4831-830f-b424a0ec84f0'; - const legacySidecarId = '926668d0-b075-11ec-bb3f-1f063f8e06cf'; - - // check for legacy sidecar action - const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionSO.hits.hits.length).to.eql(1); - - // check for legacy notification SO - // should not have been created for a rule with no actions - const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSO.hits.hits.length).to.eql(0); - - // patch enable the rule - // any route that edits the rule should trigger the migration - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleId, enabled: false }) - .expect(200); - - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, soId); - - // Sidecar should be removed - const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); - - expect(ruleSO?.alert.actions).to.eql([]); - expect(ruleSO?.alert.throttle).to.eql(null); - expect(ruleSO?.alert.notifyWhen).to.eql(null); - }); - - it('migrates legacy actions for rule with action run on every run', async () => { - const soId = 'dc6595f0-b075-11ec-bb3f-1f063f8e06cf'; - const ruleId = '72a0d429-363b-4f70-905e-c6019a224d40'; - const legacySidecarId = 'dde13970-b075-11ec-bb3f-1f063f8e06cf'; - - // check for legacy sidecar action - const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionSO.hits.hits.length).to.eql(1); - - // check for legacy notification SO - // should not have been created for a rule that runs on every rule run - const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSO.hits.hits.length).to.eql(0); - - // patch enable the rule - // any route that edits the rule should trigger the migration - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleId, enabled: false }) - .expect(200); - - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, soId); - - // Sidecar should be removed - const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); - - expect(ruleSO?.alert.actions).to.eql([ - { - actionRef: 'action_0', - actionTypeId: '.email', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['test@test.com'], - }, - uuid: ruleSO?.alert.actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - { - actionRef: 'action_1', - actionTypeId: '.email', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['test@test.com'], - }, - uuid: ruleSO?.alert.actions[1].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ]); - expect(ruleSO?.alert.throttle).to.eql(null); - expect(ruleSO?.alert.notifyWhen).to.eql(null); - expect(ruleSO?.references).to.eql([ - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - name: 'action_0', - type: 'action', - }, - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - name: 'action_1', - type: 'action', - }, - ]); - }); - - it('migrates legacy actions for rule with action run hourly', async () => { - const soId = '064e3160-b076-11ec-bb3f-1f063f8e06cf'; - const ruleId = '4c056b05-75ac-4209-be32-82100f771eb4'; - const legacySidecarId = '07aa8d10-b076-11ec-bb3f-1f063f8e06cf'; - - // check for legacy sidecar action - const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionSO.hits.hits.length).to.eql(1); - - // check for legacy notification SO - const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSO.hits.hits.length).to.eql(1); - - // patch enable the rule - // any route that edits the rule should trigger the migration - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleId, enabled: false }) - .expect(200); - - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, soId); - - // Sidecar should be removed - const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); - - // Legacy notification should be removed - const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); - - expect(ruleSO?.alert.actions).to.eql([ - { - actionTypeId: '.email', - params: { - subject: 'Rule email', - to: ['test@test.com'], - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - actionRef: 'action_0', - group: 'default', - uuid: ruleSO?.alert.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - { - actionTypeId: '.slack', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - actionRef: 'action_1', - group: 'default', - uuid: ruleSO?.alert.actions[1].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - expect(ruleSO?.alert.throttle).to.eql(undefined); - expect(ruleSO?.alert.notifyWhen).to.eql(null); - expect(ruleSO?.references).to.eql([ - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - name: 'action_0', - type: 'action', - }, - { - id: '207fa0e0-c04e-11ec-8a52-4fb92379525a', - name: 'action_1', - type: 'action', - }, - ]); - }); - - it('migrates legacy actions for rule with action run daily', async () => { - const soId = '27639570-b076-11ec-bb3f-1f063f8e06cf'; - const ruleId = '8e2c8550-f13f-4e21-be0c-92148d71a5f1'; - const legacySidecarId = '291ae260-b076-11ec-bb3f-1f063f8e06cf'; - - // check for legacy sidecar action - const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionSO.hits.hits.length).to.eql(1); - - // check for legacy notification SO - const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSO.hits.hits.length).to.eql(1); - - // patch enable the rule - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleId, enabled: false }) - .expect(200); - - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, soId); - - // Sidecar should be removed - const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); - - // Legacy notification should be removed - const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); - - expect(ruleSO?.alert.actions).to.eql([ - { - actionRef: 'action_0', - actionTypeId: '.email', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['test@test.com'], - }, - uuid: ruleSO?.alert.actions[0].uuid, - frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' }, - }, - ]); - expect(ruleSO?.alert.throttle).to.eql(undefined); - expect(ruleSO?.alert.notifyWhen).to.eql(null); - expect(ruleSO?.references).to.eql([ - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - name: 'action_0', - type: 'action', - }, - ]); - }); - - it('migrates legacy actions for rule with action run weekly', async () => { - const soId = '61ec7a40-b076-11ec-bb3f-1f063f8e06cf'; - const ruleId = '05fbdd2a-e802-420b-bdc3-95ae0acca454'; - const legacySidecarId = '63aa2fd0-b076-11ec-bb3f-1f063f8e06cf'; - - // check for legacy sidecar action - const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionSO.hits.hits.length).to.eql(1); - - // check for legacy notification SO - const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSO.hits.hits.length).to.eql(1); - - // patch enable the rule - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleId, enabled: false }) - .expect(200); - - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, soId); - - // Sidecar should be removed - const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); - expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); - - // Legacy notification should be removed - const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById(es, soId); - expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); - - expect(ruleSO?.alert.actions).to.eql([ - { - actionRef: 'action_0', - actionTypeId: '.email', - group: 'default', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - subject: 'Test Actions', - to: ['test@test.com'], - }, - uuid: ruleSO?.alert.actions[0].uuid, - frequency: { summary: true, throttle: '7d', notifyWhen: 'onThrottleInterval' }, - }, - ]); - expect(ruleSO?.alert.throttle).to.eql(undefined); - expect(ruleSO?.alert.notifyWhen).to.eql(null); - expect(ruleSO?.references).to.eql([ - { - id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', - name: 'action_0', - type: 'action', - }, - ]); - }); - }); -}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts new file mode 100644 index 0000000000000..e7cf4331417e1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts @@ -0,0 +1,451 @@ +/* + * 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 { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + getLegacyActionSOById, + getLegacyActionNotificationSOById, + getRuleSOById, +} from '../../../../../detection_engine_api_integration/utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +/** + * @deprecated Once the legacy notification system is removed, remove this test too. + */ +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('@serverless @ess actions migrations', () => { + // This test suite is not meant to test a specific route, but to test the legacy action migration + // code that lives in multiple routes. This code is also tested in each of the routes it lives in + // but not in as much detail and relying on mocks. This test loads an es_archive containing rules + // created in 7.15 with legacy actions. + // For new routes that do any updates on a rule, please ensure that you are including the legacy + // action migration code. We are monitoring legacy action telemetry to clean up once we see their + // existence being near 0. + describe('legacy actions', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/legacy_actions' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/legacy_actions' + ); + }); + + it('migrates legacy actions for rule with no actions', async () => { + const soId = '9095ee90-b075-11ec-bb3f-1f063f8e06cf'; + const ruleId = '2297be91-894c-4831-830f-b424a0ec84f0'; + const legacySidecarId = '926668d0-b075-11ec-bb3f-1f063f8e06cf'; + + // check for legacy sidecar action + const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionSO.hits.hits.length).to.eql(1); + + // check for legacy notification SO + // should not have been created for a rule with no actions + const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); + expect(legacyNotificationSO.hits.hits.length).to.eql(0); + + // patch enable the rule + // any route that edits the rule should trigger the migration + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleId, enabled: false }) + .expect(200); + + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, soId); + + // Sidecar should be removed + const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); + + expect(ruleSO?.alert.actions).to.eql([]); + expect(ruleSO?.alert.throttle).to.eql(null); + expect(ruleSO?.alert.notifyWhen).to.eql(null); + }); + + it('migrates legacy actions for rule with action run on every run', async () => { + const soId = 'dc6595f0-b075-11ec-bb3f-1f063f8e06cf'; + const ruleId = '72a0d429-363b-4f70-905e-c6019a224d40'; + const legacySidecarId = 'dde13970-b075-11ec-bb3f-1f063f8e06cf'; + + // check for legacy sidecar action + const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionSO.hits.hits.length).to.eql(1); + + // check for legacy notification SO + // should not have been created for a rule that runs on every rule run + const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); + expect(legacyNotificationSO.hits.hits.length).to.eql(0); + + // patch enable the rule + // any route that edits the rule should trigger the migration + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleId, enabled: false }) + .expect(200); + + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, soId); + + // Sidecar should be removed + const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); + + expect(ruleSO?.alert.actions).to.eql([ + { + actionRef: 'action_0', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: ruleSO?.alert.actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + { + actionRef: 'action_1', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: ruleSO?.alert.actions[1].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ]); + expect(ruleSO?.alert.throttle).to.eql(null); + expect(ruleSO?.alert.notifyWhen).to.eql(null); + expect(ruleSO?.references).to.eql([ + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_0', + type: 'action', + }, + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_1', + type: 'action', + }, + ]); + }); + + it('migrates legacy actions for rule with action run hourly', async () => { + const soId = '064e3160-b076-11ec-bb3f-1f063f8e06cf'; + const ruleId = '4c056b05-75ac-4209-be32-82100f771eb4'; + const legacySidecarId = '07aa8d10-b076-11ec-bb3f-1f063f8e06cf'; + + // check for legacy sidecar action + const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionSO.hits.hits.length).to.eql(1); + + // check for legacy notification SO + const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); + expect(legacyNotificationSO.hits.hits.length).to.eql(1); + + // patch enable the rule + // any route that edits the rule should trigger the migration + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleId, enabled: false }) + .expect(200); + + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, soId); + + // Sidecar should be removed + const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); + + // Legacy notification should be removed + const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById( + es, + soId + ); + expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); + + expect(ruleSO?.alert.actions).to.eql([ + { + actionTypeId: '.email', + params: { + subject: 'Rule email', + to: ['test@test.com'], + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionRef: 'action_0', + group: 'default', + uuid: ruleSO?.alert.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + { + actionTypeId: '.slack', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + actionRef: 'action_1', + group: 'default', + uuid: ruleSO?.alert.actions[1].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + expect(ruleSO?.alert.throttle).to.eql(undefined); + expect(ruleSO?.alert.notifyWhen).to.eql(null); + expect(ruleSO?.references).to.eql([ + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_0', + type: 'action', + }, + { + id: '207fa0e0-c04e-11ec-8a52-4fb92379525a', + name: 'action_1', + type: 'action', + }, + ]); + }); + + it('migrates legacy actions for rule with action run daily', async () => { + const soId = '27639570-b076-11ec-bb3f-1f063f8e06cf'; + const ruleId = '8e2c8550-f13f-4e21-be0c-92148d71a5f1'; + const legacySidecarId = '291ae260-b076-11ec-bb3f-1f063f8e06cf'; + + // check for legacy sidecar action + const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionSO.hits.hits.length).to.eql(1); + + // check for legacy notification SO + const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); + expect(legacyNotificationSO.hits.hits.length).to.eql(1); + + // patch enable the rule + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleId, enabled: false }) + .expect(200); + + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, soId); + + // Sidecar should be removed + const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); + + // Legacy notification should be removed + const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById( + es, + soId + ); + expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); + + expect(ruleSO?.alert.actions).to.eql([ + { + actionRef: 'action_0', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: ruleSO?.alert.actions[0].uuid, + frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' }, + }, + ]); + expect(ruleSO?.alert.throttle).to.eql(undefined); + expect(ruleSO?.alert.notifyWhen).to.eql(null); + expect(ruleSO?.references).to.eql([ + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_0', + type: 'action', + }, + ]); + }); + + it('migrates legacy actions for rule with action run weekly', async () => { + const soId = '61ec7a40-b076-11ec-bb3f-1f063f8e06cf'; + const ruleId = '05fbdd2a-e802-420b-bdc3-95ae0acca454'; + const legacySidecarId = '63aa2fd0-b076-11ec-bb3f-1f063f8e06cf'; + + // check for legacy sidecar action + const sidecarActionSO = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionSO.hits.hits.length).to.eql(1); + + // check for legacy notification SO + const legacyNotificationSO = await getLegacyActionNotificationSOById(es, soId); + expect(legacyNotificationSO.hits.hits.length).to.eql(1); + + // patch enable the rule + await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ rule_id: ruleId, enabled: false }) + .expect(200); + + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, soId); + + // Sidecar should be removed + const sidecarActionsSOAfterMigration = await getLegacyActionSOById(es, legacySidecarId); + expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0); + + // Legacy notification should be removed + const legacyNotificationSOAfterMigration = await getLegacyActionNotificationSOById( + es, + soId + ); + expect(legacyNotificationSOAfterMigration.hits.hits.length).to.eql(0); + + expect(ruleSO?.alert.actions).to.eql([ + { + actionRef: 'action_0', + actionTypeId: '.email', + group: 'default', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + subject: 'Test Actions', + to: ['test@test.com'], + }, + uuid: ruleSO?.alert.actions[0].uuid, + frequency: { summary: true, throttle: '7d', notifyWhen: 'onThrottleInterval' }, + }, + ]); + expect(ruleSO?.alert.throttle).to.eql(undefined); + expect(ruleSO?.alert.notifyWhen).to.eql(null); + expect(ruleSO?.references).to.eql([ + { + id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf', + name: 'action_0', + type: 'action', + }, + ]); + }); + }); + + describe('@brokenInServerless 7.16.0', () => { + 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'); + }); + + 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: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', + }, + { + meta: true, + } + ); + 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 + ); + }); + + it('migrates legacy siem-detection-engine-rule-actions and retains "ruleThrottle" and "alertThrottle" as the same attributes as before', async () => { + const response = await es.get<{ + 'siem-detection-engine-rule-actions': { + ruleThrottle: string; + alertThrottle: string; + }; + }>( + { + index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + id: 'siem-detection-engine-rule-actions:fce024a0-0452-11ec-9b15-d13d79d162f3', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + + // "alertThrottle" and "ruleThrottle" should still exist + expect(response.body._source?.['siem-detection-engine-rule-actions'].alertThrottle).to.eql( + '7d' + ); + expect(response.body._source?.['siem-detection-engine-rule-actions'].ruleThrottle).to.eql( + '7d' + ); + }); + }); + }); +}; From 9be7abb1e42d7344468b90614d21091fc8e74e3a Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Mon, 23 Oct 2023 15:26:45 +0200 Subject: [PATCH 09/12] remove brokenInServerless --- .../detections_response/default_license/actions/migrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts index e7cf4331417e1..9ae19c03553e4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts @@ -364,7 +364,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('@brokenInServerless 7.16.0', () => { + describe('7.16.0', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/migrations'); }); From b22dbcdb31db8e078e4fa8e6ff4f5716e9e86b4e Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Tue, 24 Oct 2023 13:02:28 +0200 Subject: [PATCH 10/12] only run legacy actions migrations in ESS --- .../detections_response/default_license/actions/migrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts index 9ae19c03553e4..7bbb8281b31be 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const esArchiver = getService('esArchiver'); - describe('@serverless @ess actions migrations', () => { + describe('@ess actions migrations', () => { // This test suite is not meant to test a specific route, but to test the legacy action migration // code that lives in multiple routes. This code is also tested in each of the routes it lives in // but not in as much detail and relying on mocks. This test loads an es_archive containing rules From 46463dbb1dccc1f9bbaf6d1a6a477fff0a0edbb2 Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Wed, 25 Oct 2023 12:00:11 +0200 Subject: [PATCH 11/12] add qa commad for actions --- x-pack/test/security_solution_api_integration/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 6eb3c499e8207..a33ca0dacdd23 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -32,6 +32,7 @@ "rule_creation:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts --grep @ess", "actions:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/actions/configs/serverless.config.ts", "actions:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/actions/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "actions:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/actions/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "actions:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/actions/configs/ess.config.ts", "actions:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/actions/configs/ess.config.ts --grep @ess" } From 81c11e146673d3565b87d3d158f43494b9271406 Mon Sep 17 00:00:00 2001 From: wafaanasr Date: Mon, 30 Oct 2023 11:47:54 +0100 Subject: [PATCH 12/12] unify commands and skip the failing tests in qa --- .../security_solution_api_integration/scripts/index.js | 6 +++--- .../default_license/actions/migrations.ts | 2 +- .../default_license/actions/update_actions.ts | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/scripts/index.js b/x-pack/test/security_solution_api_integration/scripts/index.js index 635c135e2c8b1..af12482f9f293 100644 --- a/x-pack/test/security_solution_api_integration/scripts/index.js +++ b/x-pack/test/security_solution_api_integration/scripts/index.js @@ -31,15 +31,15 @@ let grepArgs = []; if (type !== 'server') { switch (environment) { case 'serverlessEnv': - grepArgs = ['--grep', '@serverless', '--grep', '@brokenInServerless', '--invert']; + grepArgs = ['--grep', '/^(?!.*@brokenInServerless).*@serverless.*/']; break; case 'essEnv': - grepArgs = ['--grep', '@ess']; + grepArgs = ['--grep', '/^(?!.*@brokenInEss).*@ess.*/']; break; case 'qaEnv': - grepArgs = ['--grep', '@serverless', '--grep', '@brokenInServerless|@skipInQA', '--invert']; + grepArgs = ['--grep', '/^(?!.*@brokenInServerless|.*@skipInQA).*@serverless.*/']; break; default: diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts index 7bbb8281b31be..ce5c87d2c3fb4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/migrations.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); const esArchiver = getService('esArchiver'); - describe('@ess actions migrations', () => { + describe('@ess @skipInQA actions migrations', () => { // This test suite is not meant to test a specific route, but to test the legacy action migration // code that lives in multiple routes. This code is also tested in each of the routes it lives in // but not in as much detail and relying on mocks. This test loads an es_archive containing rules diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts index 292304a9cd637..f56d949eadd38 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions/update_actions.ts @@ -114,7 +114,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); }); - it('should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { + it('@skipInQA should be able to create a new webhook action and attach it to a rule with a meta field and run it correctly', async () => { const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); await createRule(supertest, log, rule); @@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccess({ supertest, log, id: updatedRule.id }); }); - it('should not change properties of immutable rule when applying actions to it', async () => { + it('@skipInQA should not change properties of immutable rule when applying actions to it', async () => { // actions and throttle to be removed from assertion (it asserted in a separate test case) const actionsProps = ['actions', 'throttle']; @@ -149,7 +149,7 @@ export default ({ getService }: FtrProviderContext) => { expect(expected.immutable).to.be(true); // It should stay immutable true when returning }); - it('should be able to create a new webhook action and attach it to an immutable rule', async () => { + it('@skipInQA should be able to create a new webhook action and attach it to an immutable rule', async () => { const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); const ruleToUpdate = getRuleWithWebHookAction( @@ -170,7 +170,7 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare.throttle).to.eql(expectedRuleWithUserUpdated.throttle); }); - it('should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { + it('@skipInQA should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); const ruleToUpdate = getRuleWithWebHookAction( @@ -184,7 +184,7 @@ export default ({ getService }: FtrProviderContext) => { expect(status.rules_not_installed).to.eql(0); }); - it('should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { + it('@skipInQA should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); const ruleToUpdate = getRuleWithWebHookAction(