diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 788bbc2df8d4e..d6ca99493ec5d 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -232,8 +232,6 @@ enabled: - x-pack/test/cloud_security_posture_api/config.ts - x-pack/test/dataset_quality_api_integration/basic/config.ts - x-pack/test/detection_engine_api_integration/basic/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts - - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts - x-pack/test/disable_ems/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/examples/config.ts @@ -499,5 +497,17 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/exception_lists_items/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/default_license/lists_items/configs/ess.config.ts - - + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts diff --git a/.buildkite/pipelines/security_solution/api_integration.yml b/.buildkite/pipelines/security_solution/api_integration.yml index 0fbc23bcab68a..21f5b34a6fe21 100644 --- a/.buildkite/pipelines/security_solution/api_integration.yml +++ b/.buildkite/pipelines/security_solution/api_integration.yml @@ -15,7 +15,7 @@ steps: key: exception_operators_date_numeric_types:qa:serverless agents: queue: n2-4-spot - timeout_in_minutes: 120 + timeout_in_minutes: 120 retry: automatic: - exit_status: '*' @@ -31,7 +31,7 @@ steps: automatic: - exit_status: '*' limit: 2 - + - label: Running exception_operators_ips:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_ips:qa:serverless key: exception_operators_ips:qa:serverless @@ -53,7 +53,6 @@ steps: automatic: - exit_status: '1' limit: 2 - - label: Running exception_operators_text:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh exception_operators_text:qa:serverless @@ -76,7 +75,7 @@ steps: automatic: - exit_status: '1' limit: 2 - + - label: Running actions:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh actions:qa:serverless key: actions:qa:serverless @@ -97,7 +96,7 @@ steps: retry: automatic: - exit_status: '1' - limit: 2 + limit: 2 - label: Running prebuilt_rules_management:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_management:qa:serverless @@ -119,7 +118,7 @@ steps: retry: automatic: - exit_status: '1' - limit: 2 + limit: 2 - label: Running prebuilt_rules_large_prebuilt_rules_package:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_large_prebuilt_rules_package:qa:serverless @@ -130,7 +129,7 @@ steps: retry: automatic: - exit_status: '1' - limit: 2 + limit: 2 - label: Running prebuilt_rules_update_prebuilt_rules_package:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh prebuilt_rules_update_prebuilt_rules_package:qa:serverless @@ -152,8 +151,8 @@ steps: retry: automatic: - exit_status: '1' - limit: 2 - + limit: 2 + - label: Running user_roles:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh user_roles:qa:serverless key: user_roles:qa:serverless @@ -163,7 +162,7 @@ steps: retry: automatic: - exit_status: '1' - limit: 2 + limit: 2 - label: Running telemetry:qa:serverless command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh telemetry:qa:serverless @@ -175,4 +174,80 @@ steps: automatic: - exit_status: '1' limit: 2 - \ No newline at end of file + + - label: Running rule_delete:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_delete:qa:serverless + key: rule_delete:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_update:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_update:qa:serverless + key: rule_update:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_patch:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_patch:qa:serverless + key: rule_patch:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_import_export:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_import_export:qa:serverless + key: rule_import_export:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_management:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_management:qa:serverless + key: rule_management:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_bulk_actions:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_bulk_actions:qa:serverless + key: rule_bulk_actions:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 + + - label: Running rule_read:qa:serverless + command: .buildkite/scripts/pipelines/security_solution_quality_gate/api-integration-tests.sh rule_read:qa:serverless + key: rule_read:qa:serverless + agents: + queue: n2-4-spot + timeout_in_minutes: 120 + retry: + automatic: + - exit_status: '1' + limit: 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 86d597b88c20c..dda328bfa1bef 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1344,6 +1344,14 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules @elastic/security-detection-rule-management /x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/rule_management @elastic/security-detection-rule-management /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/prebuilt_rules @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete @elastic/security-detection-rule-management +x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read @elastic/security-detection-rule-management +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/health_truncate_text @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/links_to_docs @elastic/security-detection-rule-management @@ -1401,14 +1409,24 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/server/lib/sourcerer @elastic/security-detection-engine /x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine @elastic/security-detection-engine + /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions @elastic/security-detection-engine -/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation @elastic/security-detection-engine +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_new_terms @elastic/security-detection-engine +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules @elastic/security-detection-engine +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/preview_rules @elastic/security-detection-engine /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/actions @elastic/security-detection-engine /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts @elastic/security-detection-engine /x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/user_roles @elastic/security-detection-engine /x-pack/test/security_solution_api_integration/test_suites/detections_response/basic_essentials_license/detection_engine @elastic/security-detection-engine /x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users @elastic/security-detection-engine /x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists @elastic/security-detection-engine +x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts @elastic/security-detection-engine +x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts @elastic/security-detection-engine +x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts @elastic/security-detection-engine +x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts @elastic/security-detection-engine +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts @elastic/security-detection-engine +/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_ess.ts @elastic/security-detection-engine + ## Security Threat Intelligence - Under Security Platform /x-pack/plugins/security_solution/public/common/components/threat_match @elastic/security-detection-engine diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts index e894cea05325c..b40033b5eb17e 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts @@ -19,8 +19,8 @@ import { deleteAllRules, getSimpleRule, installPrebuiltRulesAndTimelines, + createNonSecurityRule, } from '../../utils'; -import { createNonSecurityRule } from '../../utils/create_non_security_rule'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/README.md deleted file mode 100644 index ca10827803c65..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# What are all these groups? - -These tests take a while so they have been broken up into groups with their own `config.ts` and `index.ts` file, causing each of these groups to be independent bundles of tests which can be run on some worker in CI without taking an incredible amount of time. - -Want to change the groups to something more logical? Have fun! Just make sure that each group executes on CI in less than 10 minutes or so. We don't currently have any mechanism for validating this right now, you just need to look at the times in the log output on CI, but we'll be working on tooling for making this information more accessible soon. - -- Kibana Operations - -# Rule Exception List Tests - -These tests are currently in group7-9. - -These are tests for rule exception lists where we test each data type - -- date -- double -- float -- integer -- ip -- keyword -- long -- text - -Against the operator types of: - -- "is" -- "is not" -- "is one of" -- "is not one of" -- "exists" -- "does not exist" -- "is in list" -- "is not in list" 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 deleted file mode 100644 index d3e83305ad11e..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ /dev/null @@ -1,24 +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 { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 1', function () { - // !!NOTE: 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. - - loadTestFile(require.resolve('./create_rules_bulk')); - loadTestFile(require.resolve('./delete_rules')); - loadTestFile(require.resolve('./delete_rules_bulk')); - loadTestFile(require.resolve('./export_rules')); - loadTestFile(require.resolve('./find_rules')); - loadTestFile(require.resolve('./get_rule_management_filters')); - }); -}; 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 deleted file mode 100644 index 70b50d7f5aef3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts +++ /dev/null @@ -1,29 +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 { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ loadTestFile }: FtrProviderContext): void => { - describe('detection engine api security and spaces enabled - Group 10', function () { - // !!NOTE: 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. - - loadTestFile(require.resolve('./get_rule_execution_results')); - loadTestFile(require.resolve('./import_rules')); - loadTestFile(require.resolve('./import_export_rules')); - loadTestFile(require.resolve('./read_rules')); - loadTestFile(require.resolve('./resolve_read_rules')); - loadTestFile(require.resolve('./update_rules')); - loadTestFile(require.resolve('./update_rules_bulk')); - loadTestFile(require.resolve('./patch_rules_bulk')); - loadTestFile(require.resolve('./perform_bulk_action')); - loadTestFile(require.resolve('./perform_bulk_action_dry_run')); - loadTestFile(require.resolve('./patch_rules')); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts deleted file mode 100644 index f3fb8671906c6..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/import_rules.ts +++ /dev/null @@ -1,1311 +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 { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - toNdJsonString, - getImportExceptionsListItemSchemaMock, - getImportExceptionsListSchemaMock, -} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; -import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createSignalsIndex, - deleteAllRules, - deleteAllAlerts, - getSimpleRule, - getSimpleRuleAsNdjson, - getSimpleRuleOutput, - getWebHookAction, - removeServerGeneratedProperties, - ruleToNdjson, - deleteAllExceptions, -} from '../../utils'; -import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; - -const getImportRuleBuffer = (connectorId: string) => { - const rule1 = { - id: '53aad690-544e-11ec-a349-11361cc441c4', - updated_at: '2021-12-03T15:33:13.271Z', - updated_by: 'elastic', - created_at: '2021-12-03T15:33:13.271Z', - created_by: 'elastic', - name: '7.16 test with action', - tags: [], - interval: '5m', - enabled: true, - description: 'test', - risk_score: 21, - severity: 'low', - license: '', - output_index: '', - meta: { from: '1m', kibana_siem_app_url: 'http://0.0.0.0:5601/s/7/app/security' }, - author: [], - false_positives: [], - from: 'now-360s', - rule_id: 'aa525d7c-8948-439f-b32d-27e00c750246', - max_signals: 100, - risk_score_mapping: [], - severity_mapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptions_list: [], - immutable: false, - type: 'query', - language: 'kuery', - index: [ - 'apm-*-transaction*', - 'traces-apm*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'logs-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - query: '*:*', - filters: [], - throttle: '1h', - actions: [ - { - group: 'default', - id: connectorId, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - action_type_id: '.slack', - }, - ], - }; - const rule1String = JSON.stringify(rule1); - const buffer = Buffer.from(`${rule1String}\n`); - return buffer; -}; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext): void => { - const supertest = getService('supertest'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); - - describe('import_rules', () => { - describe('importing rules with different roles', () => { - before(async () => { - await createUserAndRole(getService, ROLES.hunter_no_actions); - await createUserAndRole(getService, ROLES.hunter); - }); - after(async () => { - await deleteUserAndRole(getService, ROLES.hunter_no_actions); - await deleteUserAndRole(getService, ROLES.hunter); - }); - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - }); - it('should successfully import rules without actions when user has no actions privileges', async () => { - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter_no_actions, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - it('should successfully import rules with actions when user has "read" actions privileges', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - it('should not import rules with actions when a user has no actions privileges', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter_no_actions, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') - .expect(200); - expect(body).to.eql({ - success: false, - success_count: 0, - rules_count: 1, - errors: [ - { - error: { - message: - 'You may not have actions privileges required to import rules with actions: Unable to bulk_create action', - status_code: 403, - }, - rule_id: '(unknown id)', - }, - { - error: { - message: `1 connector is missing. Connector id missing is: ${hookAction.id}`, - status_code: 404, - }, - rule_id: 'rule-1', - }, - ], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - }); - describe('importing rules with an index', () => { - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - }); - - it('should set the response content types to be expected', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); - }); - - it('should reject with an error if the file type is not that of a ndjson', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.txt') - .expect(400); - - expect(body).to.eql({ - status_code: 400, - message: 'Invalid file extension .txt', - }); - }); - - it('should report that it imported a simple rule successfully', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should be able to read an imported rule back out correctly', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql({ - ...getSimpleRuleOutput('rule-1', false), - output_index: '', - }); - }); - - it('should be able to import two rules', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 2, - rules_count: 2, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should report a conflict if there is an attempt to import two rules with the same rule_id', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [ - { - error: { - message: 'More than one rule with rule-id: "rule-1" found', - status_code: 400, - }, - rule_id: 'rule-1', - }, - ], - success: false, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should NOT report a conflict if there is an attempt to import two rules with the same rule_id and overwrite is set to true', async () => { - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should report a conflict if there is an attempt to import a rule with a rule_id that already exists', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [ - { - error: { - message: 'rule_id: "rule-1" already exists', - status_code: 409, - }, - rule_id: 'rule-1', - }, - ], - success: false, - success_count: 0, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should NOT report a conflict if there is an attempt to import a rule with a rule_id that already exists and overwrite is set to true', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should overwrite an existing rule if overwrite is set to true', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - const simpleRule = getSimpleRule('rule-1'); - simpleRule.name = 'some other name'; - const ndjson = ruleToNdjson(simpleRule); - - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ndjson, 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - const ruleOutput = { - ...getSimpleRuleOutput('rule-1'), - output_index: '', - }; - ruleOutput.name = 'some other name'; - ruleOutput.version = 2; - expect(bodyToCompare).to.eql(ruleOutput); - }); - - it('should report a conflict if there is an attempt to import a rule with a rule_id that already exists, but still have some successes with other rules', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [ - { - error: { - message: 'rule_id: "rule-1" already exists', - status_code: 409, - }, - rule_id: 'rule-1', - }, - ], - success: false, - success_count: 2, - rules_count: 3, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should report a mix of conflicts and a mix of successes', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .expect(200); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [ - { - error: { - message: 'rule_id: "rule-1" already exists', - status_code: 409, - }, - rule_id: 'rule-1', - }, - { - error: { - message: 'rule_id: "rule-2" already exists', - status_code: 409, - }, - rule_id: 'rule-2', - }, - ], - success: false, - success_count: 1, - rules_count: 3, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should be able to correctly read back a mixed import of different rules even if some cause conflicts', async () => { - const simpleRuleOutput = (ruleName: string) => ({ - ...getSimpleRuleOutput(ruleName), - output_index: '', - }); - - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2']), 'rules.ndjson') - .expect(200); - - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1', 'rule-2', 'rule-3']), 'rules.ndjson') - .expect(200); - - const { body: bodyOfRule1 } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const { body: bodyOfRule2 } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-2`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const { body: bodyOfRule3 } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-3`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const bodyToCompareOfRule1 = removeServerGeneratedProperties(bodyOfRule1); - const bodyToCompareOfRule2 = removeServerGeneratedProperties(bodyOfRule2); - const bodyToCompareOfRule3 = removeServerGeneratedProperties(bodyOfRule3); - - expect([bodyToCompareOfRule1, bodyToCompareOfRule2, bodyToCompareOfRule3]).to.eql([ - simpleRuleOutput('rule-1'), - simpleRuleOutput('rule-2'), - simpleRuleOutput('rule-3'), - ]); - }); - - it('should give single connector error back if we have a single connector error message', async () => { - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: '123', - action_type_id: '456', - params: {}, - }, - ], - }; - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - success: false, - success_count: 0, - rules_count: 1, - errors: [ - { - rule_id: 'rule-1', - error: { - status_code: 404, - message: '1 connector is missing. Connector id missing is: 123', - }, - }, - ], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [ - { - rule_id: 'rule-1', - error: { - status_code: 404, - message: '1 connector is missing. Connector id missing is: 123', - }, - }, - ], - action_connectors_warnings: [], - }); - }); - - it('should be able to import a rule with an action connector that exists', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') - .expect(200); - expect(body).to.eql({ - success: true, - success_count: 1, - rules_count: 1, - errors: [], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should be able to import 2 rules with action connectors that exist', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - - const rule2: ReturnType = { - ...getSimpleRule('rule-2'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const rule1String = JSON.stringify(rule1); - const rule2String = JSON.stringify(rule2); - const buffer = Buffer.from(`${rule1String}\n${rule2String}\n`); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - success: true, - success_count: 2, - rules_count: 2, - errors: [], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should be able to import 1 rule with an action connector that exists and get 1 other error back for a second rule that does not have the connector', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - - const rule2: ReturnType = { - ...getSimpleRule('rule-2'), - actions: [ - { - group: 'default', - id: '123', // <-- This does not exist - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const rule1String = JSON.stringify(rule1); - const rule2String = JSON.stringify(rule2); - const buffer = Buffer.from(`${rule1String}\n${rule2String}\n`); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - success: false, - success_count: 1, - rules_count: 2, - errors: [ - { - rule_id: 'rule-2', - error: { - status_code: 404, - message: '1 connector is missing. Connector id missing is: 123', - }, - }, - ], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - describe('migrate pre-8.0 action connector ids', () => { - const defaultSpaceActionConnectorId = '61b17790-544e-11ec-a349-11361cc441c4'; - const space714ActionConnectorId = '51b17790-544e-11ec-a349-11361cc441c4'; - beforeEach(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' - ); - }); - afterEach(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' - ); - }); - - it('importing a non-default-space 7.16 rule with a connector made in the non-default space should result in a 200', async () => { - const spaceId = '714-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.eql(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into the default space should result in a 404', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - // When objects become share-capable we will either add / update this test - it('importing a non-default-space 7.16 rule with a connector made in the non-default space into a different non-default space should result in a 404', async () => { - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(space714ActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${space714ActionConnectorId}` - ); - }); - - it('importing a default-space 7.16 rule with a connector made in the default space into the default space should result in a 200', async () => { - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(true); - expect(body.success_count).to.eql(1); - expect(body.errors.length).to.eql(0); - }); - it('importing a default-space 7.16 rule with a connector made in the default space into a non-default space should result in a 404', async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/import_rule_connector' - ); - const spaceId = '4567-space'; - // connectorId is from the 7.x connector here - // x-pack/test/functional/es_archives/security_solution/import_rule_connector - // it - const buffer = getImportRuleBuffer(defaultSpaceActionConnectorId); - - const { body } = await supertest - .post(`/s/${spaceId}${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', buffer, 'rules.ndjson') - .expect(200); - expect(body.success).to.equal(false); - expect(body.errors[0].error.status_code).to.equal(404); - expect(body.errors[0].error.message).to.equal( - `1 connector is missing. Connector id missing is: ${defaultSpaceActionConnectorId}` - ); - }); - }); - - describe('importing with exceptions', () => { - beforeEach(async () => { - await deleteAllExceptions(supertest, log); - }); - - it('should be able to import a rule and an exception list', async () => { - const simpleRule = getSimpleRule('rule-1'); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - Buffer.from( - toNdJsonString([ - simpleRule, - getImportExceptionsListSchemaMock('test_list_id'), - getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'), - ]) - ), - 'rules.ndjson' - ) - .expect(200); - expect(body).to.eql({ - success: true, - success_count: 1, - rules_count: 1, - errors: [], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 1, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should only remove non existent exception list references from rule', async () => { - // create an exception list - const { body: exceptionBody } = await supertest - .post(EXCEPTION_LIST_URL) - .set('kbn-xsrf', 'true') - .send({ - ...getCreateExceptionListMinimalSchemaMock(), - list_id: 'i_exist', - namespace_type: 'single', - type: 'detection', - }) - .expect(200); - - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - exceptions_list: [ - { - id: exceptionBody.id, - list_id: 'i_exist', - type: 'detection', - namespace_type: 'single', - }, - { - id: 'i_dont_exist', - list_id: '123', - type: 'detection', - namespace_type: 'single', - }, - ], - }; - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ruleToNdjson(simpleRule), 'rules.ndjson') - .expect(200); - - const { body: ruleResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare.exceptions_list).to.eql([ - { - id: exceptionBody.id, - list_id: 'i_exist', - namespace_type: 'single', - type: 'detection', - }, - ]); - - expect(body).to.eql({ - success: false, - success_count: 1, - rules_count: 2, - errors: [ - { - rule_id: 'rule-1', - error: { - message: - 'Rule with rule_id: "rule-1" references a non existent exception list of list_id: "123". Reference has been removed.', - status_code: 400, - }, - }, - ], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should resolve exception references when importing into a clean slate', async () => { - // So importing a rule that references an exception list - // Keep in mind, no exception lists or rules exist yet - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - exceptions_list: [ - { - id: 'abc', - list_id: 'i_exist', - type: 'detection', - namespace_type: 'single', - }, - ], - }; - - // Importing the "simpleRule", along with the exception list - // it's referencing and the list's item - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - Buffer.from( - toNdJsonString([ - simpleRule, - { - ...getImportExceptionsListSchemaMock('i_exist'), - id: 'abc', - type: 'detection', - namespace_type: 'single', - }, - { - description: 'some description', - entries: [ - { - entries: [ - { - field: 'nested.field', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - field: 'some.parentField', - type: 'nested', - }, - { - field: 'some.not.nested.field', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - item_id: 'item_id_1', - list_id: 'i_exist', - name: 'Query with a rule id', - type: 'simple', - }, - ]) - ), - 'rules.ndjson' - ) - .expect(200); - - const { body: ruleResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - const referencedExceptionList = ruleResponse.exceptions_list[0]; - - // create an exception list - const { body: exceptionBody } = await supertest - .get( - `${EXCEPTION_LIST_URL}?list_id=${referencedExceptionList.list_id}&id=${referencedExceptionList.id}` - ) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(bodyToCompare.exceptions_list).to.eql([ - { - id: exceptionBody.id, - list_id: 'i_exist', - namespace_type: 'single', - type: 'detection', - }, - ]); - - expect(body).to.eql({ - success: true, - success_count: 1, - rules_count: 1, - errors: [], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 1, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should resolve exception references that include comments', async () => { - // So importing a rule that references an exception list - // Keep in mind, no exception lists or rules exist yet - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - exceptions_list: [ - { - id: 'abc', - list_id: 'i_exist', - type: 'detection', - namespace_type: 'single', - }, - ], - }; - - // Importing the "simpleRule", along with the exception list - // it's referencing and the list's item - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - Buffer.from( - toNdJsonString([ - simpleRule, - { - ...getImportExceptionsListSchemaMock('i_exist'), - id: 'abc', - type: 'detection', - namespace_type: 'single', - }, - { - comments: [ - { - comment: 'This is an exception to the rule', - created_at: '2022-02-04T02:27:40.938Z', - created_by: 'elastic', - id: '845fc456-91ff-4530-bcc1-5b7ebd2f75b5', - }, - { - comment: 'I decided to add a new comment', - }, - ], - description: 'some description', - entries: [ - { - entries: [ - { - field: 'nested.field', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - field: 'some.parentField', - type: 'nested', - }, - { - field: 'some.not.nested.field', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - item_id: 'item_id_1', - list_id: 'i_exist', - name: 'Query with a rule id', - type: 'simple', - }, - ]) - ), - 'rules.ndjson' - ) - .expect(200); - - const { body: ruleResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - const referencedExceptionList = ruleResponse.exceptions_list[0]; - - // create an exception list - const { body: exceptionBody } = await supertest - .get( - `${EXCEPTION_LIST_URL}?list_id=${referencedExceptionList.list_id}&id=${referencedExceptionList.id}` - ) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(bodyToCompare.exceptions_list).to.eql([ - { - id: exceptionBody.id, - list_id: 'i_exist', - namespace_type: 'single', - type: 'detection', - }, - ]); - - const { body: exceptionItemBody } = await supertest - .get(`${EXCEPTION_LIST_ITEM_URL}?item_id="item_id_1"`) - .set('kbn-xsrf', 'true') - .expect(200); - - expect(exceptionItemBody.comments).to.eql([ - { - comment: 'This is an exception to the rule', - created_at: `${exceptionItemBody.comments[0].created_at}`, - created_by: 'elastic', - id: `${exceptionItemBody.comments[0].id}`, - }, - { - comment: 'I decided to add a new comment', - created_at: `${exceptionItemBody.comments[1].created_at}`, - created_by: 'elastic', - id: `${exceptionItemBody.comments[1].id}`, - }, - ]); - - expect(body).to.eql({ - success: true, - success_count: 1, - rules_count: 1, - errors: [], - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 1, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_container_with_endpoint_entries.ts b/x-pack/test/detection_engine_api_integration/utils/create_container_with_endpoint_entries.ts deleted file mode 100644 index ca04e01c654e9..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_container_with_endpoint_entries.ts +++ /dev/null @@ -1,85 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { - CreateExceptionListItemSchema, - ListArray, - NonEmptyEntriesArray, - OsTypeArray, -} from '@kbn/securitysolution-io-ts-list-types'; - -import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; -import { createExceptionListItem } from './create_exception_list_item'; -import { waitFor } from './wait_for'; -import { createExceptionList } from './create_exception_list'; - -/** - * Convenience testing function where you can pass in just the endpoint entries and you will - * get a container created with the entries. - * @param supertest super test agent - * @param endpointEntries The endpoint entries to create the rule and exception list from - * @param osTypes The os types to optionally add or not to add to the container - */ -export const createContainerWithEndpointEntries = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - endpointEntries: Array<{ - entries: NonEmptyEntriesArray; - osTypes: OsTypeArray | undefined; - }> -): Promise => { - // If not given any endpoint entries, return without any - if (endpointEntries.length === 0) { - return []; - } - - // create the endpoint exception list container - // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { - description: 'endpoint description', - list_id: 'endpoint_list', - name: 'endpoint_list', - type: 'endpoint', - }); - - // Add the endpoint exception list container to the backend - await Promise.all( - endpointEntries.map((endpointEntry) => { - const exceptionListItem: CreateExceptionListItemSchema = { - description: 'endpoint description', - entries: endpointEntry.entries, - list_id: 'endpoint_list', - name: 'endpoint_list', - os_types: endpointEntry.osTypes, - type: 'simple', - }; - return createExceptionListItem(supertest, log, exceptionListItem); - }) - ); - - // To reduce the odds of in-determinism and/or bugs we ensure we have - // the same length of entries before continuing. - await waitFor( - async () => { - const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); - return body.data.length === endpointEntries.length; - }, - `within createContainerWithEndpointEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, - log - ); - - return [ - { - id, - list_id, - namespace_type, - type, - }, - ]; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_container_with_entries.ts b/x-pack/test/detection_engine_api_integration/utils/create_container_with_entries.ts deleted file mode 100644 index 169a9bcea239b..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_container_with_entries.ts +++ /dev/null @@ -1,76 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { ListArray, NonEmptyEntriesArray } from '@kbn/securitysolution-io-ts-list-types'; - -import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; -import { createExceptionList } from './create_exception_list'; -import { createExceptionListItem } from './create_exception_list_item'; -import { waitFor } from './wait_for'; - -/** - * Convenience testing function where you can pass in just the endpoint entries and you will - * get a container created with the entries. - * @param supertest super test agent - * @param entries The entries to create the rule and exception list from - * @param osTypes The os types to optionally add or not to add to the container - */ -export const createContainerWithEntries = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - entries: NonEmptyEntriesArray[] -): Promise => { - // If not given any endpoint entries, return without any - if (entries.length === 0) { - return []; - } - // Create the rule exception list container - // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, list_id, namespace_type, type } = await createExceptionList(supertest, log, { - description: 'some description', - list_id: 'some-list-id', - name: 'some name', - type: 'detection', - }); - - // Add the rule exception list container to the backend - await Promise.all( - entries.map((entry) => { - const exceptionListItem: CreateExceptionListItemSchema = { - description: 'some description', - list_id: 'some-list-id', - name: 'some name', - type: 'simple', - entries: entry, - }; - return createExceptionListItem(supertest, log, exceptionListItem); - }) - ); - - // To reduce the odds of in-determinism and/or bugs we ensure we have - // the same length of entries before continuing. - await waitFor( - async () => { - const { body } = await supertest.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`); - return body.data.length === entries.length; - }, - `within createContainerWithEntries ${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${list_id}`, - log - ); - - return [ - { - id, - list_id, - namespace_type, - type, - }, - ]; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_exception_list.ts b/x-pack/test/detection_engine_api_integration/utils/create_exception_list.ts deleted file mode 100644 index 24ebabb5243b2..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_exception_list.ts +++ /dev/null @@ -1,68 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { - CreateExceptionListSchema, - ExceptionListSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import { deleteExceptionList } from './delete_exception_list'; - -/** - * Helper to cut down on the noise in some of the tests. This checks for - * an expected 200 still and does not try to any retries. Creates exception lists - * @param supertest The supertest deps - * @param exceptionList The exception list to create - * @param log The tooling logger - */ -export const createExceptionList = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - exceptionList: CreateExceptionListSchema -): Promise => { - const response = await supertest - .post(EXCEPTION_LIST_URL) - .set('kbn-xsrf', 'true') - .send(exceptionList); - - if (response.status === 409) { - if (exceptionList.list_id != null) { - log.error( - `When creating an exception list found an unexpected conflict (409) creating an exception list (createExceptionList), will attempt a cleanup and one time re-try. This usually indicates a bad cleanup or race condition within the tests: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - await deleteExceptionList(supertest, log, exceptionList.list_id); - const secondResponseTry = await supertest - .post(EXCEPTION_LIST_URL) - .set('kbn-xsrf', 'true') - .send(exceptionList); - if (secondResponseTry.status !== 200) { - throw new Error( - `Unexpected non 200 ok when attempting to create an exception list (second try): ${JSON.stringify( - response.body - )}` - ); - } else { - return secondResponseTry.body; - } - } else { - throw new Error('When creating an exception list found an unexpected conflict (404)'); - } - } else if (response.status !== 200) { - throw new Error( - `Unexpected non 200 ok when attempting to create an exception list: ${JSON.stringify( - response.status - )}` - ); - } else { - return response.body; - } -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_exception_list_item.ts b/x-pack/test/detection_engine_api_integration/utils/create_exception_list_item.ts deleted file mode 100644 index fccbd3e243b17..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_exception_list_item.ts +++ /dev/null @@ -1,42 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { - CreateExceptionListItemSchema, - ExceptionListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; - -/** - * Helper to cut down on the noise in some of the tests. This checks for - * an expected 200 still and does not try to any retries. Creates exception lists - * @param supertest The supertest deps - * @param exceptionListItem The exception list item to create - * @param log The tooling logger - */ -export const createExceptionListItem = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - exceptionListItem: CreateExceptionListItemSchema -): Promise => { - const response = await supertest - .post(EXCEPTION_LIST_ITEM_URL) - .set('kbn-xsrf', 'true') - .send(exceptionListItem); - - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when creating an exception list item (createExceptionListItem). 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/detection_engine_api_integration/utils/create_legacy_rule_action.ts b/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts deleted file mode 100644 index 8e0cb59d5ee90..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_legacy_rule_action.ts +++ /dev/null @@ -1,35 +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 type SuperTest from 'supertest'; - -import { UPDATE_OR_CREATE_LEGACY_ACTIONS } from '@kbn/security-solution-plugin/common/constants'; - -export const createLegacyRuleAction = async ( - supertest: SuperTest.SuperTest, - alertId: string, - connectorId: string -): Promise => - supertest - .post(UPDATE_OR_CREATE_LEGACY_ACTIONS) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') - .query({ alert_id: alertId }) - .send({ - name: 'Legacy notification with one action', - interval: '1h', - actions: [ - { - id: connectorId, - group: 'default', - params: { - message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - actionTypeId: '.slack', - }, - ], - }); diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts deleted file mode 100644 index fac1efa9ad0eb..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts +++ /dev/null @@ -1,72 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; -import type { - RuleCreateProps, - RuleResponse, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { createContainerWithEntries } from './create_container_with_entries'; -import { createContainerWithEndpointEntries } from './create_container_with_endpoint_entries'; -import { createRule } from './create_rule'; - -/** - * Convenience testing function where you can pass in just the entries and you will - * get a rule created with the entries added to an exception list and exception list item - * all auto-created at once. - * @param supertest super test agent - * @param rule The rule to create and attach an exception list to - * @param entries The entries to create the rule and exception list from - * @param endpointEntries The endpoint entries to create the rule and exception list from - * @param osTypes The os types to optionally add or not to add to the container - */ -export const createRuleWithExceptionEntries = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - rule: RuleCreateProps, - entries: NonEmptyEntriesArray[], - endpointEntries?: Array<{ - entries: NonEmptyEntriesArray; - osTypes: OsTypeArray | undefined; - }> -): Promise => { - const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); - const maybeEndpointList = await createContainerWithEndpointEntries( - supertest, - log, - endpointEntries ?? [] - ); - - // create the rule but don't run it immediately as running it immediately can cause - // the rule to sometimes not filter correctly the first time with an exception list - // or other timing issues. Then afterwards wait for the rule to have succeeded before - // returning. - const ruleWithException: RuleCreateProps = { - ...rule, - enabled: false, - exceptions_list: [...maybeExceptionList, ...maybeEndpointList], - }; - const ruleResponse = await createRule(supertest, log, ruleWithException); - const response = await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ rule_id: ruleResponse.rule_id, enabled: true }); - - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when patching a rule with exception entries (createRuleWithExceptionEntries). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - } - return ruleResponse; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md b/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md deleted file mode 100644 index e737e7b133929..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/README.md +++ /dev/null @@ -1,606 +0,0 @@ -# Data Generator for functional tests - -Helper to generate and index documents for using in Kibana functional tests - -- [Data Generator for functional tests](#data-generator-for-functional-tests) - - [DataGenerator](#datagenerator) - - [Initialization](#initialization) - - [Prerequisites](#prerequisites) - - [dataGeneratorFactory](#datageneratorfactory) - - [methods](#methods) - - [**indexListOfDocuments**](#indexlistofdocuments) - - [**indexGeneratedDocuments**](#indexgenerateddocuments) - - [**indexEnhancedDocuments**](#indexenhanceddocuments) - - [Utils](#utils) - - [**generateDocuments**](#generatedocuments) - - [**enhanceDocument**](#enhancedocument) - - [**enhanceDocuments**](#enhancedocuments) - - [Usage](#usage) - - [create test query rule that queries indexed documents within a test](#create-test-query-rule-that-queries-indexed-documents-within-a-test) - -## DataGenerator - -### Initialization - - -#### Prerequisites -1. Create index mappings in `x-pack/test/functional/es_archives/security_solution` - - create folder for index `foo_bar` - - add mappings file `mappings.json` in it - -
- x-pack/test/functional/es_archives/security_solution/foo_bar/mappings.json - - ```JSON - { - "type": "index", - "value": { - "index": "foo_bar", - "mappings": { - "properties": { - "id": { - "type": "keyword" - }, - "@timestamp": { - "type": "date" - }, - "foo": { - "type": "keyword" - }, - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } - } - ``` -
-2. Add in `before` of the test file index initialization - - ```ts - const esArchiver = getService('esArchiver'); - - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/foo_bar' - ); - }); - - ``` - -3. Add in `after` of the test file index removal - - ```ts - const esArchiver = getService('esArchiver'); - - before(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/foo_bar' - ); - }); - - ``` - -#### dataGeneratorFactory - -`DataGeneratorParams` - -| Property | Description | Type | -| --------------- | ------------------------------------------------------ | ------ | -| es | ES client | `ESClient` | -| index | index where document will be added | `string` | -| log | log client | `LogClient`| - -1. import and initialize factory - - ```ts - import { dataGeneratorFactory } from '../../utils/data_generator'; - - const es = getService('es'); - const log = getService('log'); - - const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ - es, - index: 'foo_bar', - log, - }); - - ``` -2. Factory will return 2 methods which can be used to index documents into `foo_bar` - -where `getService` is method from `FtrProviderContext` - -### methods - -#### **indexListOfDocuments** - -| Property | Description | Type | -| --------------- | ------------------------------------------------------ | ------ | -| documents | list of documents to index | `Record` | - -Will index list of documents to `foo_bar` index as defined in `dataGeneratorFactory` params - -```ts - await indexListOfDocuments([{ foo: "bar" }, { id: "test-1" }]) - -``` - -#### **indexGeneratedDocuments** - -Will generate 10 documents in defined interval and index them in `foo_bar` index as defined in `dataGeneratorFactory` params -Method receives same parameters as [generateDocuments](#generateDocuments) util. - -```ts - await indexGeneratedDocuments({ - docsCount: 10, - interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], - seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) - }) - -``` - -#### **indexEnhancedDocuments** - -Will index list of enhanced documents to `foo_bar` index as defined in `dataGeneratorFactory` params -Method receives same parameters as [enhanceDocuments](#enhanceDocuments) util. - -```ts - await indexEnhancedDocuments({ - interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], - documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] - }) - -``` - -## Utils - -### **generateDocuments** - -Util `generateDocuments` can generate list of documents based on basic seed function - - Seed callback will receive sequential number of document of document, generated id, timestamp. - Can be used to generate custom document with large set of options depends on needs. See examples below. - - | Property | Description | Type | - | --------------- | ------------------------------------------------------ | ------ | - | docsCount | number of documents to generate | `number` | - | seed | function that receives sequential number of document, generated id, timestamp as arguments and can used it create a document | `(index: number, id: string, timestamp: string) => Document` | - | interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | - -Examples: - - 1. Generate 10 documents with random id, timestamp in interval between '2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z', and field `seq` that represents sequential number of document - - ```ts - - const documents = generateDocuments({ - docsCount: 10, - interval: ['2020-10-28T07:30:00.000Z', '2020-10-30T07:30:00.000Z'], - seed: (i, id, timestamp) => ({ id, '@timestamp': timestamp, seq: i }) - }) - ``` - -
-Generated docs - - ```JSON - [ - { - "id": "87d3d231-13c8-4d03-9ae4-d40781b3b2d1", - "@timestamp": "2020-10-30T04:00:55.790Z", - "seq": 0 - }, - { - "id": "90b99797-d0da-460d-86fd-eca40bedff39", - "@timestamp": "2020-10-28T08:43:01.117Z", - "seq": 1 - }, - { - "id": "809c05be-f401-4e31-86e1-55be8af4fac4", - "@timestamp": "2020-10-29T15:06:23.054Z", - "seq": 2 - }, - { - "id": "a2720f82-5401-4eab-b2eb-444a8425c937", - "@timestamp": "2020-10-29T23:19:47.790Z", - "seq": 3 - }, - { - "id": "e36e4418-4e89-4388-97df-97085b3fca92", - "@timestamp": "2020-10-29T09:14:00.966Z", - "seq": 4 - }, - { - "id": "4747adb3-0603-4651-8c0f-0c7df037f779", - "@timestamp": "2020-10-28T14:23:50.500Z", - "seq": 5 - }, - { - "id": "1fbfd873-b0ca-4cda-9c96-9a044622e712", - "@timestamp": "2020-10-28T10:00:20.995Z", - "seq": 6 - }, - { - "id": "9173cf93-1f9f-4f91-be5e-1e6888cb3aae", - "@timestamp": "2020-10-28T08:52:27.830Z", - "seq": 7 - }, - { - "id": "53245337-e383-4b28-9975-acbd79901b7c", - "@timestamp": "2020-10-29T08:58:02.385Z", - "seq": 8 - }, - { - "id": "0c700d33-df10-426e-8f71-677f437923ec", - "@timestamp": "2020-10-29T16:33:10.240Z", - "seq": 9 - } - ] - ``` - -
- - 2. Generate 3 identical documents `{foo: bar}` - - ```ts - - const documents = generateDocuments({ - docsCount: 3, - seed: () => ({ foo: 'bar' }) - }) - ``` - -
-Generated docs - - ```JSON - [ - { - "foo": "bar" - }, - { - "foo": "bar" - }, - { - "foo": "bar" - } - ] - ``` - -
- - 3. Generate 5 documents with custom ingested timestamp, with no interval. If interval not defined, timestamp will be current time - - ```ts - - const documents = generateDocuments({ - docsCount: 5, - seed: (i, id, timestamp) => ({ foo: 'bar', event: { ingested: timestamp } }) - }) - ``` - -
-Generated docs - - ```JSON - [ - { - "foo": "bar", - "event": { - "ingested": "2023-02-15T13:02:37.531Z" - } - }, - { - "foo": "bar", - "event": { - "ingested": "2023-02-15T13:02:37.531Z" - } - }, - { - "foo": "bar", - "event": { - "ingested": "2023-02-15T13:02:37.531Z" - } - }, - { - "foo": "bar", - "event": { - "ingested": "2023-02-15T13:02:37.531Z" - } - }, - { - "foo": "bar", - "event": { - "ingested": "2023-02-15T13:02:37.531Z" - } - } - ] - ``` - -
- - 4. Generate 4 documents with custom if based on sequential number id - - ```ts - - const documents = generateDocuments({ - docsCount: 4, - seed: (i) => ({ foo: 'bar', id: `id-${i}`}) - }) - ``` - -
-Generated docs - - ```JSON - [ - { - "foo": "bar", - "id": "id-0" - }, - { - "foo": "bar", - "id": "id-1" - }, - { - "foo": "bar", - "id": "id-2" - }, - { - "foo": "bar", - "id": "id-3" - } - ] - ``` - -
- - -### **enhanceDocument** - -Adds generated `uuidv4` id and current time as `@timestamp` to document if `id`, `timestamp` params are not specified - - -`EnhanceDocumentOptions` - -| Property | Description | Type | -| --------------- | ------------------------------------------------------ | ------ | -| id | id for document | `string` _(optional)_ | -| timestamp | timestamp for document | `string` _(optional)_ | -| document | document to enhance | `Record` | - -Examples: - -1. Enhance document with generated `uuidv4` id and current time as `@timestamp` - - ```ts - const document = enhanceDocument({ - document: { foo: 'bar' }, - }); - ``` -
- document - - ```JSON - { - "foo": "bar", - "id": "b501a64f-0dd4-4275-a38c-889be6a15a4d", - "@timestamp": "2023-02-15T17:21:21.429Z" - } - ``` - -
- -2. Enhance document with generated `uuidv4` id and predefined timestamp - - - ```ts - const document = enhanceDocument({ - timestamp: '1996-02-15T13:02:37.531Z', - document: { foo: 'bar' }, - }); - ``` -
- document - - ```JSON - { - "foo": "bar", - "id": "7b7460bf-e173-4744-af15-2c01ac52963b", - "@timestamp": "1996-02-15T13:02:37.531Z" - } - ``` - -
- -3. Enhance document with predefined id and and current time as `@timestamp` - - - ```ts - const document = enhanceDocument({ - id: 'test-id', - document: { foo: 'bar' }, - }); - ``` -
- document - - ```JSON - { - "foo": "bar", - "id": "test-id", - "@timestamp": "2023-02-15T17:21:21.429Z" - } - ``` -
- -### **enhanceDocuments** - - - -Adds generated `uuidv4` `id` property to list of documents if `id` parameter is not specified. -Adds `@timestamp` in defined interval to list of documents. If it's not specified, `@timestamp` will be added as current time - -| Property | Description | Type | -| --------------- | ------------------------------------------------------ | ------ | -| documents | documents to enhance | `Record[]` | -| id | id for documents | `string` _(optional)_ | -| interval | interval in which generate documents, defined by '@timestamp' field | `[string \| Date string \| Date]` _(optional)_ | - -Examples: - -1. Enhance documents with generated `uuidv4` id and current time as `@timestamp` - - ```ts - const documents = enhanceDocuments({ - documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] - }); - ``` -
- documents - - ```JSON - [ - { - "foo": "bar", - "id": "c55ddd6b-3cf2-4ebf-94d6-4eeeb4e5b655", - "@timestamp": "2023-02-16T16:43:13.573Z" - }, - { - "foo": "bar-1", - "id": "61b157b9-5f1f-4d99-a5bf-072069f5139d", - "@timestamp": "2023-02-16T16:43:13.573Z" - }, - { - "foo": "bar-2", - "id": "04929927-6d9e-4ccc-b083-250e3fe2d7a7", - "@timestamp": "2023-02-16T16:43:13.573Z" - } - ] - ``` - -
- -2. Enhance document with generated `uuidv4` id and timestamp in predefined interval - - ```ts - const documents = enhanceDocuments({ - interval: ['1996-02-15T13:02:37.531Z', '2000-02-15T13:02:37.531Z'], - documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] - }); - ``` -
- documents - - ```JSON - [ - { - "foo": "bar", - "id": "883a67cb-0a57-4711-bdf9-e8a394a52460", - "@timestamp": "1998-07-04T15:16:46.587Z" - }, - { - "foo": "bar-1", - "id": "70691d9e-1030-412f-8ae1-c6db50e90e91", - "@timestamp": "1998-05-15T07:00:52.339Z" - }, - { - "foo": "bar-2", - "id": "b2140328-5cc4-4532-947e-30b8fd830ed7", - "@timestamp": "1999-09-01T21:50:38.957Z" - } - ] - ``` - -
- -3. Enhance documents with predefined id and and current time as `@timestamp` - - ```ts - const documents = enhanceDocuments({ - id: 'test-id', - documents: [{ foo: 'bar' }, { foo: 'bar-1' }, { foo: 'bar-2' }] - }); - ``` -
- documents - - ```JSON - [ - { - "foo": "bar", - "id": "test-id", - "@timestamp": "2023-02-16T16:43:13.574Z" - }, - { - "foo": "bar-1", - "id": "test-id", - "@timestamp": "2023-02-16T16:43:13.574Z" - }, - { - "foo": "bar-2", - "id": "test-id", - "@timestamp": "2023-02-16T16:43:13.574Z" - } - ] - - ``` -
- -## Usage - -### create test query rule that queries indexed documents within a test - -When documents generated and indexed, there might be a need to create a test rule that targets only these documents. So, documents generated in the test, will be used only in context of this test. - -There are few possible ways to do this - -1. Create new index every time for a new test. Thus, newly indexed documents, will be the only documents present in test index. It might be costly operation, as it will require to create new index for each test, that re-initialize dataGeneratorFactory, or delete index after rule's run - -2. Use the same id or specific field in documents. - For example: - - ```ts - - const id = uuidv4(); - const firstTimestamp = new Date().toISOString(); - const firstDocument = { - id, - '@timestamp': firstTimestamp, - agent: { - name: 'agent-1', - }, - }; - await indexListOfDocuments([firstDocument, firstDocument]); - - const rule: QueryRuleCreateProps = { - ...getRuleForSignalTesting(['ecs_compliant']), - query: `id:${id}`, - }; - - - ``` - - All documents will have the same `id` and can be queried by following `id:${id}` - -3. Use utility method `getKQLQueryFromDocumentList` that will create query from all ids in generated documents - - ```ts - const { documents } = await indexGeneratedDocuments({ - docsCount: 4, - document: { foo: 'bar' }, - enhance: true, - }); - - const query = getKQLQueryFromDocumentList(documents); - const rule = { - ...getRuleForSignalTesting(['ecs_non_compliant']), - query, - }; - ``` - - util will generate the following query: `(id: "f6ca3ee1-407c-4685-a94b-11ef4ed5136b" or id: "2a7358b2-8cad-47ce-83b7-e4418c266f3e" or id: "9daec569-0ba1-4c46-a0c6-e340cee1c5fb" or id: "b03c2fdf-0ca1-447c-b8c6-2cc5a663ffe2")`, that will include all generated documents \ No newline at end of file diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts deleted file mode 100644 index 7842d105e40b0..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/data_generator_factory.ts +++ /dev/null @@ -1,75 +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 type { Client } from '@elastic/elasticsearch'; -import { ToolingLog } from '@kbn/tooling-log'; -import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { indexDocuments } from './index_documents'; -import { generateDocuments } from './generate_documents'; -import { enhanceDocuments, EnhanceDocumentsOptions } from './enhance_documents'; -import type { GenerateDocumentsParams } from './generate_documents'; -import type { Document } from './types'; - -interface DataGeneratorParams { - es: Client; - documents: Array>; - index: string; - log: ToolingLog; -} - -interface DataGeneratorResponse { - response: BulkResponse; - documents: Document[]; -} - -interface DataGenerator { - indexListOfDocuments: (docs: Document[]) => Promise; - indexGeneratedDocuments: (params: GenerateDocumentsParams) => Promise; - indexEnhancedDocuments: (params: EnhanceDocumentsOptions) => Promise; -} - -/** - * initialize {@link DataGenerator} - * @param param.es - ES client - * @param params.index - index where document will be added - * @param params.log - logClient - * @returns methods of {@link DataGenerator} - */ -export const dataGeneratorFactory = ({ - es, - index, - log, -}: Omit): DataGenerator => { - return { - indexListOfDocuments: async (documents: DataGeneratorParams['documents']) => { - const response = await indexDocuments({ es, index, documents, log }); - - return { - documents, - response, - }; - }, - indexGeneratedDocuments: async (params: GenerateDocumentsParams) => { - const documents = generateDocuments(params); - const response = await indexDocuments({ es, index, documents, log }); - - return { - documents, - response, - }; - }, - indexEnhancedDocuments: async (params: EnhanceDocumentsOptions) => { - const documents = enhanceDocuments(params); - const response = await indexDocuments({ es, index, documents, log }); - - return { - documents, - response, - }; - }, - }; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts deleted file mode 100644 index f2e244edb90b3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_document.ts +++ /dev/null @@ -1,29 +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 { v4 as uuidv4 } from 'uuid'; - -interface EnhanceDocumentOptions { - id?: string; - timestamp?: string; - document: Record; -} - -/** - * enhances document with generated id and timestamp - * @param {string} options.id - optional id, if not provided randomly generated - * @param {string} options.timestamp - optional timestamp of document, if not provided current time - * @param {Record} options.document - document that will be enhanced - */ -export const enhanceDocument = (options: EnhanceDocumentOptions) => { - const id = options?.id ?? uuidv4(); - const timestamp = options?.timestamp ?? new Date().toISOString(); - return { - ...options.document, - id, - '@timestamp': timestamp, - }; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.ts deleted file mode 100644 index 6d5bce70b5432..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/enhance_documents.ts +++ /dev/null @@ -1,32 +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 type { IndexingInterval, Document } from './types'; -import { getTimestamp } from './utils'; -import { enhanceDocument } from './enhance_document'; - -export interface EnhanceDocumentsOptions { - interval?: IndexingInterval; - documents: Document[]; - id?: string; -} - -/** - * enhances documents with generated id and timestamp within interval - * @param {string} options.id - optional id, if not provided randomly generated - * @param {string} options.interval - optional interval of document, if not provided set as a current time - * @param {Record[]} options.documents - documents that will be enhanced - */ -export const enhanceDocuments = ({ documents, interval, id }: EnhanceDocumentsOptions) => { - return documents.map((document) => - enhanceDocument({ - document, - id, - timestamp: getTimestamp(interval), - }) - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts deleted file mode 100644 index 8bb05d50bea89..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/generate_documents.ts +++ /dev/null @@ -1,39 +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 { v4 as uuidv4 } from 'uuid'; -import { getTimestamp } from './utils'; - -import type { Document, IndexingInterval } from './types'; - -type DocumentSeedFunc = (index: number, id: string, timestamp: string) => Document; - -export interface GenerateDocumentsParams { - interval?: IndexingInterval; - docsCount: number; - seed: DocumentSeedFunc; -} - -/** - * - * @param param.interval - interval in which generate documents, defined by '@timestamp' field - * @param param.docsCount - number of document to generate - * @param param.seed - seed function. Function that receives index of document, generated id, timestamp as arguments and can used it create a document - * @returns generated Documents - */ -export const generateDocuments = ({ docsCount, interval, seed }: GenerateDocumentsParams) => { - const documents = []; - - for (let i = 0; i < docsCount; i++) { - const id = uuidv4(); - const timestamp = getTimestamp(interval); - - documents.push(seed(i, id, timestamp)); - } - - return documents; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.ts deleted file mode 100644 index 3347c2120d4a4..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/get_kql_query_from_documents_list.ts +++ /dev/null @@ -1,41 +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 type { Document } from './types'; - -/** - * returns KQL query from a list documents that includes all documents by their ids. - * it can be used later to create test rules that will query only these documents - * ```ts - * const documents = [ - { - foo: 'bar', - id: 'f07df596-65ec-4ab1-b0b2-f3b69558ed26', - '@timestamp': '2020-10-29T07:10:51.989Z', - }, - { - foo: 'bar', - id: 'e07614f9-1dc5-4849-90c4-31362bbdf8d0', - '@timestamp': '2020-10-30T00:32:48.987Z', - }, - { - foo: 'test', - id: 'e03a5b12-77e6-4aa3-b0be-fbe5b0843f07', - '@timestamp': '2020-10-29T03:40:35.318Z', - }, - ]; - - const query = getKQLQueryFromDocumentList(documents); - - // query equals to - // (id: "f07df596-65ec-4ab1-b0b2-f3b69558ed26" or id: "e07614f9-1dc5-4849-90c4-31362bbdf8d0" or id: "e03a5b12-77e6-4aa3-b0be-fbe5b0843f07") - * ``` - */ -export const getKQLQueryFromDocumentList = (documents: Document[]) => { - const orClauses = documents.map(({ id }) => `id: "${id}"`).join(' or '); - - return `(${orClauses})`; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts deleted file mode 100644 index acc6307e62dcd..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/index.ts +++ /dev/null @@ -1,12 +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. - */ - -export * from './data_generator_factory'; -export * from './enhance_document'; -export * from './enhance_documents'; -export * from './generate_documents'; -export * from './get_kql_query_from_documents_list'; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts deleted file mode 100644 index 5408e11b25015..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/index_documents.ts +++ /dev/null @@ -1,39 +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 type { Client } from '@elastic/elasticsearch'; -import type { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ToolingLog } from '@kbn/tooling-log'; - -interface IndexDocumentsParams { - es: Client; - documents: Array>; - index: string; - log: ToolingLog; -} - -type IndexDocuments = (params: IndexDocumentsParams) => Promise; - -/** - * Indexes documents into provided index - */ -export const indexDocuments: IndexDocuments = async ({ es, documents, index, log }) => { - const operations = documents.flatMap((doc: object) => [{ index: { _index: index } }, doc]); - - const response = await es.bulk({ refresh: true, operations }); - - // throw error if document wasn't indexed, so test will be terminated earlier and no false positives can happen - response.items.some(({ index: responseIndex } = {}) => { - if (responseIndex?.error) { - log.error( - `Failed to index document in non_ecs_fields test suits: "${responseIndex.error?.reason}"` - ); - throw Error(responseIndex.error.message); - } - }); - return response; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/types.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/types.ts deleted file mode 100644 index bbf54be68cfee..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/types.ts +++ /dev/null @@ -1,10 +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. - */ - -export type IndexingInterval = [string | Date, string | Date]; - -export type Document = Record; diff --git a/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts b/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts deleted file mode 100644 index e828767a39650..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/data_generator/utils.ts +++ /dev/null @@ -1,16 +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 faker from 'faker'; -import type { IndexingInterval } from './types'; - -export const getTimestamp = (interval?: IndexingInterval) => { - if (interval) { - return faker.date.between(...interval).toISOString(); - } - - return new Date().toISOString(); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_event_log_execution_events.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_event_log_execution_events.ts deleted file mode 100644 index 6ff1e9fda30be..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_event_log_execution_events.ts +++ /dev/null @@ -1,39 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type { Client } from '@elastic/elasticsearch'; - -import { countDownES } from './count_down_es'; - -/** - * Remove all .kibana-event-log-* documents with an execution.uuid - * This will retry 50 times before giving up and hopefully still not interfere with other tests - * @param es The ElasticSearch handle - * @param log The tooling logger - */ -export const deleteAllEventLogExecutionEvents = async ( - es: Client, - log: ToolingLog -): Promise => { - return countDownES( - async () => { - return es.deleteByQuery( - { - index: '.kibana-event-log-*', - q: '_exists_:kibana.alert.rule.execution.uuid', - wait_for_completion: true, - refresh: true, - body: {}, - }, - { meta: true } - ); - }, - 'deleteAllEventLogExecutionEvents', - log - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_exceptions.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_exceptions.ts deleted file mode 100644 index aed98bc61561a..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_exceptions.ts +++ /dev/null @@ -1,67 +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. - */ - -// Should be deleted once all all the remaining tests in this folder get moved to the new /security_solution_api_integration folder - -import type SuperTest from 'supertest'; - -import type { ExceptionList, NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; - -import { ToolingLog } from '@kbn/tooling-log'; -import { countDownTest } from './count_down_test'; - -/** - * Remove all exceptions from both the "single" and "agnostic" spaces. - * This will retry 50 times before giving up and hopefully still not interfere with other tests - * @param supertest The supertest handle - */ -export const deleteAllExceptions = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog -): Promise => { - await deleteAllExceptionsByType(supertest, log, 'single'); - await deleteAllExceptionsByType(supertest, log, 'agnostic'); -}; - -/** - * Remove all exceptions by a given type such as "agnostic" or "single". - * This will retry 50 times before giving up and hopefully still not interfere with other tests - * @param supertest The supertest handle - */ -export const deleteAllExceptionsByType = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - type: NamespaceType -): Promise => { - await countDownTest( - async () => { - const { body } = await supertest - .get(`${EXCEPTION_LIST_URL}/_find?per_page=9999&namespace_type=${type}`) - .set('kbn-xsrf', 'true') - .send(); - const ids: string[] = body.data.map((exception: ExceptionList) => exception.id); - for await (const id of ids) { - await supertest - .delete(`${EXCEPTION_LIST_URL}?id=${id}&namespace_type=${type}`) - .set('kbn-xsrf', 'true') - .send(); - } - const { body: finalCheck } = await supertest - .get(`${EXCEPTION_LIST_URL}/_find?namespace_type=${type}`) - .set('kbn-xsrf', 'true') - .send(); - return { - passed: finalCheck.data.length === 0, - }; - }, - `deleteAllExceptions by type: "${type}"`, - log, - 50, - 1000 - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts b/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts deleted file mode 100644 index d1f158506a4f1..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_all_rule_execution_info.ts +++ /dev/null @@ -1,36 +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 type { Client } from '@elastic/elasticsearch'; -import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import type { ToolingLog } from '@kbn/tooling-log'; -import { countDownES } from './count_down_es'; - -/** - * Remove all rules execution info saved objects from the security solution savedObjects index - * This will retry 50 times before giving up and hopefully still not interfere with other tests - * @param es The ElasticSearch handle - * @param log The tooling logger - */ -export const deleteAllRuleExecutionInfo = async (es: Client, log: ToolingLog): Promise => { - return countDownES( - async () => { - return es.deleteByQuery( - { - index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - q: 'type:siem-detection-engine-rule-execution-info', - wait_for_completion: true, - refresh: true, - body: {}, - }, - { meta: true } - ); - }, - 'deleteAllRuleExecutionInfo', - log - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts b/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts deleted file mode 100644 index 6c5558a005b97..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts +++ /dev/null @@ -1,37 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -/** - * Helper to cut down on the noise in some of the tests. Does a delete of an exception list. - * It does not check for a 200 "ok" on this. - * @param supertest The supertest deps - * @param listId The exception list to delete - * @param log The tooling logger - */ -export const deleteExceptionList = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - listId: string -): Promise => { - const response = await supertest - .delete(`${EXCEPTION_LIST_URL}?list_id=${listId}`) - .set('kbn-xsrf', 'true'); - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when deleting an exception list (deleteExceptionList). 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/detection_engine_api_integration/utils/downgrade_immutable_rule.ts b/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts deleted file mode 100644 index 19e15e9b4679b..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/downgrade_immutable_rule.ts +++ /dev/null @@ -1,43 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type { Client } from '@elastic/elasticsearch'; -import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import { countDownES } from './count_down_es'; - -export const downgradeImmutableRule = async ( - es: Client, - log: ToolingLog, - ruleId: string -): Promise => { - return countDownES( - async () => { - return es.updateByQuery( - { - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - refresh: true, - wait_for_completion: true, - body: { - script: { - lang: 'painless', - source: 'ctx._source.alert.params.version--', - }, - query: { - term: { - 'alert.params.ruleId': ruleId, - }, - }, - }, - }, - { meta: true } - ); - }, - 'downgradeImmutableRule', - log - ); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/finalize_signals_migration.ts b/x-pack/test/detection_engine_api_integration/utils/finalize_signals_migration.ts deleted file mode 100644 index e52c85c229edb..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/finalize_signals_migration.ts +++ /dev/null @@ -1,44 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL } from '@kbn/security-solution-plugin/common/constants'; - -interface FinalizeMigrationResponse { - id: string; - completed?: boolean; - error?: unknown; -} - -export const finalizeSignalsMigration = async ({ - migrationIds, - supertest, - log, -}: { - supertest: SuperTest.SuperTest; - log: ToolingLog; - migrationIds: string[]; -}): Promise => { - const response = await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: migrationIds }); - - const { - body: { migrations }, - }: { body: { migrations: FinalizeMigrationResponse[] } } = response; - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when finalizing signals migration (finalizeSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - } - return migrations; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts deleted file mode 100644 index 55e7375c48986..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts +++ /dev/null @@ -1,44 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; - -/** - * Helper to cut down on the noise in some of the tests. This - * uses the find API to get an immutable rule by id. - * @param supertest The supertest deps - */ -export const findImmutableRuleById = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - ruleId: string -): Promise<{ - page: number; - perPage: number; - total: number; - data: RuleResponse[]; -}> => { - const response = await supertest - .get( - `${DETECTION_ENGINE_RULES_URL}/_find?filter=alert.attributes.params.immutable: true AND alert.attributes.params.ruleId: "${ruleId}"` - ) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(); - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when finding an immutable rule by id (findImmutableRuleById). 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/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts deleted file mode 100644 index fbd2bd7f238e3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts +++ /dev/null @@ -1,27 +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 type { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; - -/** - * This is a typical signal testing rule that is easy for most basic testing of output of EQL signals. - * It starts out in an enabled true state. The 'from' is set very far back to test the basics of signal - * creation for EQL and testing by getting all the signals at once. - * @param ruleId The optional ruleId which is eql-rule by default. - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getEqlRuleForSignalTesting = ( - index: string[], - ruleId = 'eql-rule', - enabled = true -): EqlRuleCreateProps => ({ - ...getRuleForSignalTesting(index, ruleId, enabled), - type: 'eql', - language: 'eql', - query: 'any where true', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts deleted file mode 100644 index c56cb7a98b440..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts +++ /dev/null @@ -1,28 +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 type { Client } from '@elastic/elasticsearch'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import { SavedObjectReference } from '@kbn/core/server'; -import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; - -interface LegacyActionNotificationSO extends LegacyRuleNotificationAlertTypeParams { - references: SavedObjectReference[]; -} - -/** - * Fetch all legacy action sidecar notification SOs from the alerting savedObjects index - * @param es The ElasticSearch service - */ -export const getLegacyActionNotificationSO = async ( - es: Client -): Promise> => - es.search({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: 'alert.alertTypeId:siem.notifications', - }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts deleted file mode 100644 index e714dfcec28cc..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts +++ /dev/null @@ -1,25 +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 type { Client } from '@elastic/elasticsearch'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import type { SavedObjectReference } from '@kbn/core/server'; -import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; - -interface LegacyActionSO extends LegacyRuleActions { - references: SavedObjectReference[]; -} - -/** - * Fetch all legacy action sidecar SOs from the security solution savedObjects index - * @param es The ElasticSearch service - */ -export const getLegacyActionSO = async (es: Client): Promise> => - es.search({ - index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - q: 'type:siem-detection-engine-rule-actions', - }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts b/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts deleted file mode 100644 index ac44acef91f06..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts +++ /dev/null @@ -1,36 +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 type SuperTest from 'supertest'; -import type { Client } from '@elastic/elasticsearch'; -import type { ToolingLog } from '@kbn/tooling-log'; -import { - RuleExecutionStatus, - RuleExecutionStatusEnum, -} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { waitForRuleStatus } from './wait_for_rule_status'; -import { refreshIndex } from './refresh_index'; -import { getSignalsByIds } from './get_signals_by_ids'; - -export const getOpenSignals = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - es: Client, - rule: RuleResponse, - status: RuleExecutionStatus = RuleExecutionStatusEnum.succeeded, - size?: number, - afterDate?: Date -) => { - await waitForRuleStatus(status, { supertest, log, id: rule.id, afterDate }); - // Critically important that we wait for rule success AND refresh the write index in that order before we - // assert that no signals were created. Otherwise, signals could be written but not available to query yet - // when we search, causing tests that check that signals are NOT created to pass when they should fail. - await refreshIndex(es, '.alerts-security.alerts-default*'); - return getSignalsByIds(supertest, log, [rule.id], size); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts b/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts deleted file mode 100644 index 098611b4ce3bf..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts +++ /dev/null @@ -1,48 +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 type { Client } from '@elastic/elasticsearch'; -import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; -import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/enrichments/types'; -import { refreshIndex } from './refresh_index'; - -/** - * Refresh an index, making changes available to search. - * Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions. - * @param es The ElasticSearch handle - */ -export const getPreviewAlerts = async ({ - es, - previewId, - size, - sort, -}: { - es: Client; - previewId: string; - size?: number; - sort?: string[]; -}) => { - const index = '.preview.alerts-security.alerts-*'; - await refreshIndex(es, index); - const query = { - bool: { - filter: { - term: { - [ALERT_RULE_UUID]: previewId, - }, - }, - }, - }; - const result = await es.search({ - index, - size, - query, - sort, - }); - return result.hits.hits; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_query_all_signals.ts b/x-pack/test/detection_engine_api_integration/utils/get_query_all_signals.ts deleted file mode 100644 index fd003857fdc1f..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_query_all_signals.ts +++ /dev/null @@ -1,10 +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. - */ - -export const getQueryAllSignals = () => ({ - query: { match_all: {} }, -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_query_signals_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_query_signals_rule_id.ts deleted file mode 100644 index 5644d1bff1fbd..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_query_signals_rule_id.ts +++ /dev/null @@ -1,21 +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 { ALERT_RULE_RULE_ID } from '@kbn/rule-data-utils'; - -/** - * Given an array of ruleIds for a test this will get the signals - * created from that rule_id. - * @param ruleIds The rule_id to search for signals - */ -export const getQuerySignalsRuleId = (ruleIds: string[]) => ({ - query: { - terms: { - [ALERT_RULE_RULE_ID]: ruleIds, - }, - }, -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts deleted file mode 100644 index 32b2c0b1d5df6..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts +++ /dev/null @@ -1,38 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; - -/** - * Helper to cut down on the noise in some of the tests. This gets - * a particular rule. - * @param supertest The supertest deps - * @param rule The rule to create - */ -export const getRule = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - ruleId: string -): Promise => { - const response = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31'); - - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when getting a rule (getRule). 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/detection_engine_api_integration/utils/get_rule_actions.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_actions.ts deleted file mode 100644 index 519cd9136c604..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_actions.ts +++ /dev/null @@ -1,96 +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 type SuperTest from 'supertest'; - -import { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types'; -import { getSlackAction } from './get_slack_action'; -import { getWebHookAction } from './get_web_hook_action'; - -const createConnector = async ( - supertest: SuperTest.SuperTest, - payload: Record -) => - (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) - .body; -const createWebHookConnector = (supertest: SuperTest.SuperTest) => - createConnector(supertest, getWebHookAction()); -const createSlackConnector = (supertest: SuperTest.SuperTest) => - createConnector(supertest, getSlackAction()); - -export const getActionsWithoutFrequencies = async ( - supertest: SuperTest.SuperTest -): Promise => { - const webHookAction = await createWebHookConnector(supertest); - const slackConnector = await createSlackConnector(supertest); - return [ - { - group: 'default', - id: webHookAction.id, - action_type_id: '.webhook', - params: { message: 'Email message' }, - }, - { - group: 'default', - id: slackConnector.id, - action_type_id: '.slack', - params: { message: 'Slack message' }, - }, - ]; -}; - -export const getActionsWithFrequencies = async ( - supertest: SuperTest.SuperTest -): Promise => { - const webHookAction = await createWebHookConnector(supertest); - const slackConnector = await createSlackConnector(supertest); - return [ - { - group: 'default', - id: webHookAction.id, - action_type_id: '.webhook', - params: { message: 'Email message' }, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - { - group: 'default', - id: slackConnector.id, - action_type_id: '.slack', - params: { message: 'Slack message' }, - frequency: { summary: false, throttle: '3d', notifyWhen: 'onThrottleInterval' }, - }, - ]; -}; - -export const getSomeActionsWithFrequencies = async ( - supertest: SuperTest.SuperTest -): Promise => { - const webHookAction = await createWebHookConnector(supertest); - const slackConnector = await createSlackConnector(supertest); - return [ - { - group: 'default', - id: webHookAction.id, - action_type_id: '.webhook', - params: { message: 'Email message' }, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - { - group: 'default', - id: slackConnector.id, - action_type_id: '.slack', - params: { message: 'Slack message' }, - frequency: { summary: false, throttle: '3d', notifyWhen: 'onThrottleInterval' }, - }, - { - group: 'default', - id: slackConnector.id, - action_type_id: '.slack', - params: { message: 'Slack message' }, - }, - ]; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts deleted file mode 100644 index 16d9613909f94..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts +++ /dev/null @@ -1,27 +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 type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -export const getRuleForSignalTestingWithTimestampOverride = ( - index: string[], - ruleId = 'rule-1', - enabled = true, - timestampOverride = 'event.ingested' -): QueryRuleCreateProps => ({ - name: 'Signal Testing Query', - description: 'Tests a simple query', - enabled, - risk_score: 1, - rule_id: ruleId, - severity: 'high', - index, - type: 'query', - query: '*:*', - timestamp_override: timestampOverride, - from: '1900-01-01T00:00:00.000Z', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts deleted file mode 100644 index 584b49e379ee7..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_so_by_id.ts +++ /dev/null @@ -1,29 +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 type { Client } from '@elastic/elasticsearch'; -import { SavedObjectReference } from '@kbn/core/server'; -import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { Rule } from '@kbn/alerting-plugin/common'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; - -interface RuleSO { - alert: Rule; - references: SavedObjectReference[]; -} - -/** - * Fetch legacy action sidecar SOs from the alerting savedObjects index - * @param es The ElasticSearch service - * @param id SO id - */ -export const getRuleSOById = async (es: Client, id: string): Promise> => - es.search({ - index: ALERTING_CASES_SAVED_OBJECT_INDEX, - q: `type:alert AND _id:"alert:${id}"`, - }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_legacy_investigation_fields.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_with_legacy_investigation_fields.ts deleted file mode 100644 index 55056748a0945..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_legacy_investigation_fields.ts +++ /dev/null @@ -1,124 +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 { InternalRuleCreate } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; - -export const getRuleSavedObjectWithLegacyInvestigationFields = (): InternalRuleCreate => - ({ - name: 'Test investigation fields', - tags: ['migration'], - rule_type_id: 'siem.queryRule', - consumer: 'siem', - params: { - author: [], - buildingBlockType: undefined, - falsePositives: [], - description: 'a', - ruleId: '2297be91-894c-4831-830f-b424a0ec84f0', - from: '1900-01-01T00:00:00.000Z', - immutable: false, - license: '', - outputIndex: '', - investigationFields: ['client.address', 'agent.name'], - maxSignals: 100, - meta: undefined, - riskScore: 21, - riskScoreMapping: [], - severity: 'low', - severityMapping: [], - threat: [], - to: 'now', - references: [], - timelineId: undefined, - timelineTitle: undefined, - ruleNameOverride: undefined, - timestampOverride: undefined, - timestampOverrideFallbackDisabled: undefined, - namespace: undefined, - note: undefined, - requiredFields: undefined, - version: 1, - exceptionsList: [], - relatedIntegrations: [], - setup: '', - type: 'query', - language: 'kuery', - index: ['auditbeat-*'], - query: '_id:BhbXBmkBR346wHgn4PeZ or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi', - filters: [], - savedId: undefined, - responseActions: undefined, - alertSuppression: undefined, - dataViewId: undefined, - }, - schedule: { - interval: '5m', - }, - enabled: false, - actions: [], - throttle: null, - // cast is due to alerting API expecting rule_type_id - // and our internal schema expecting alertTypeId - } as unknown as InternalRuleCreate); - -export const getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray = (): InternalRuleCreate => - ({ - name: 'Test investigation fields empty array', - tags: ['migration'], - rule_type_id: 'siem.queryRule', - consumer: 'siem', - params: { - author: [], - description: 'a', - ruleId: '2297be91-894c-4831-830f-b424a0ec5678', - falsePositives: [], - from: '1900-01-01T00:00:00.000Z', - immutable: false, - license: '', - outputIndex: '', - investigationFields: [], - maxSignals: 100, - riskScore: 21, - riskScoreMapping: [], - severity: 'low', - severityMapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptionsList: [], - type: 'query', - language: 'kuery', - index: ['auditbeat-*'], - query: '_id:BhbXBmkBR346wHgn4PeZ or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi', - filters: [], - relatedIntegrations: [], - setup: '', - buildingBlockType: undefined, - meta: undefined, - timelineId: undefined, - timelineTitle: undefined, - ruleNameOverride: undefined, - timestampOverride: undefined, - timestampOverrideFallbackDisabled: undefined, - namespace: undefined, - note: undefined, - requiredFields: undefined, - savedId: undefined, - responseActions: undefined, - alertSuppression: undefined, - dataViewId: undefined, - }, - schedule: { - interval: '5m', - }, - enabled: false, - actions: [], - throttle: null, - // cast is due to alerting API expecting rule_type_id - // and our internal schema expecting alertTypeId - } as unknown as InternalRuleCreate); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts deleted file mode 100644 index 6437df274098d..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts +++ /dev/null @@ -1,34 +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 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/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts deleted file mode 100644 index d0d6e2924b352..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts +++ /dev/null @@ -1,26 +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 type { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; - -/** - * This is a typical signal testing rule that is easy for most basic testing of output of Saved Query signals. - * It starts out in an enabled true state. The 'from' is set very far back to test the basics of signal - * creation for SavedQuery and testing by getting all the signals at once. - * @param ruleId The optional ruleId which is threshold-rule by default. - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getSavedQueryRuleForSignalTesting = ( - index: string[], - ruleId = 'saved-query-rule', - enabled = true -): SavedQueryRuleCreateProps => ({ - ...getRuleForSignalTesting(index, ruleId, enabled), - type: 'saved_query', - saved_id: 'abcd', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_signals_by_id.ts deleted file mode 100644 index e68023837f770..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_id.ts +++ /dev/null @@ -1,53 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; -import { getQuerySignalsId } from './get_query_signals_ids'; - -/** - * Given a single rule id this will return only signals based on that rule id. - * @param supertest agent - * @param ids Rule id - */ -export const getSignalsById = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - id: string -): Promise> => { - const signalsOpen = await countDownTest>( - async () => { - const response = await supertest - .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) - .set('kbn-xsrf', 'true') - .send(getQuerySignalsId([id])); - if (response.status !== 200) { - return { - passed: false, - returnValue: undefined, - }; - } else { - return { - passed: true, - returnValue: response.body, - }; - } - }, - 'getSignalsById', - log - ); - if (signalsOpen == null) { - throw new Error('Signals not defined after countdown, cannot continue'); - } else { - return signalsOpen; - } -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_rule_ids.ts b/x-pack/test/detection_engine_api_integration/utils/get_signals_by_rule_ids.ts deleted file mode 100644 index 7a0a93d90855d..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_signals_by_rule_ids.ts +++ /dev/null @@ -1,52 +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 type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants'; -import { countDownTest } from './count_down_test'; -import { getQuerySignalsRuleId } from './get_query_signals_rule_id'; - -/** - * Returns all signals both closed and opened by ruleId - * @param supertest Deps - */ -export const getSignalsByRuleIds = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - ruleIds: string[] -): Promise> => { - const signalsOpen = await countDownTest>( - async () => { - const response = await supertest - .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) - .set('kbn-xsrf', 'true') - .send(getQuerySignalsRuleId(ruleIds)); - if (response.status !== 200) { - return { - passed: false, - errorMessage: `Status is not 200 as expected, it is: ${response.status}`, - }; - } else { - return { - passed: true, - returnValue: response.body, - }; - } - }, - 'getSignalsByRuleIds', - log - ); - if (signalsOpen == null) { - throw new Error('Signals not defined after countdown, cannot continue'); - } else { - return signalsOpen; - } -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts deleted file mode 100644 index 5fc6225622c20..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts +++ /dev/null @@ -1,41 +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 type { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; - -/** - * This is a typical signal testing rule that is easy for most basic testing of output of Threat Match signals. - * It starts out in an enabled true state. The 'from' is set very far back to test the basics of signal - * creation for Threat Match and testing by getting all the signals at once. - * @param ruleId The optional ruleId which is threshold-rule by default. - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getThreatMatchRuleForSignalTesting = ( - index: string[], - ruleId = 'threat-match-rule', - enabled = true -): ThreatMatchRuleCreateProps => ({ - ...getRuleForSignalTesting(index, ruleId, enabled), - type: 'threat_match', - language: 'kuery', - query: '*:*', - threat_query: '*:*', - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_index: index, // match against same index for simplicity -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts deleted file mode 100644 index f2cb502e12a2e..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts +++ /dev/null @@ -1,32 +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 type { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; - -/** - * This is a typical signal testing rule that is easy for most basic testing of output of Threshold signals. - * It starts out in an enabled true state. The 'from' is set very far back to test the basics of signal - * creation for Threshold and testing by getting all the signals at once. - * @param ruleId The optional ruleId which is threshold-rule by default. - * @param enabled Enables the rule on creation or not. Defaulted to true. - */ -export const getThresholdRuleForSignalTesting = ( - index: string[], - ruleId = 'threshold-rule', - enabled = true -): ThresholdRuleCreateProps => ({ - ...getRuleForSignalTesting(index, ruleId, enabled), - type: 'threshold', - language: 'kuery', - query: '*:*', - threshold: { - field: 'process.name', - value: 21, - }, - alert_suppression: undefined, -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 1b18044d95449..1938a069a2f53 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -8,82 +8,35 @@ export * from './binary_to_string'; export * from './count_down_es'; export * from './count_down_test'; -export * from './create_container_with_endpoint_entries'; -export * from './create_container_with_entries'; -export * from './create_exception_list'; -export * from './create_exception_list_item'; -export * from './create_legacy_rule_action'; export * from './create_rule'; -export * from './create_rule_with_exception_entries'; export * from './create_rule_saved_object'; export * from './create_signals_index'; export * from './delete_all_rules'; -export * from './delete_all_event_log_execution_events'; -export * from './delete_all_rule_execution_info'; export * from './delete_all_alerts'; export * from './delete_all_timelines'; -export * from './delete_exception_list'; -export * from './delete_rule'; -export * from './finalize_signals_migration'; -export * from './find_immutable_rule_by_id'; export * from './get_complex_rule'; export * from './get_complex_rule_output'; -export * from './get_eql_rule_for_signal_testing'; -export * from './get_event_log_execute_complete_by_id'; -export * from './get_legacy_action_notification_so'; -export * from './get_open_signals'; -export * from './get_preview_alerts'; -export * from './get_query_all_signals'; -export * from './get_query_signals_rule_id'; -export * from './get_query_signal_ids'; -export * from './get_rule'; -export * from './get_rules_as_ndjson'; -export * from './get_rule_for_signal_testing'; -export * from './get_rule_so_by_id'; -export * from './get_rule_for_signal_testing_with_timestamp_override'; -export * from './get_rule_with_web_hook_action'; -export * from './get_rule_with_legacy_investigation_fields'; -export * from './get_saved_query_rule_for_signal_testing'; -export * from './get_signals_by_id'; -export * from './get_signals_by_ids'; -export * from './get_signals_by_rule_ids'; -export * from './get_simple_ml_rule'; -export * from './get_simple_ml_rule_output'; -export * from './get_simple_ml_rule_update'; export * from './get_simple_rule'; -export * from './get_simple_rule_as_ndjson'; export * from './get_simple_rule_output'; export * from './get_simple_rule_output_without_rule_id'; -export * from './get_simple_rule_update'; export * from './get_simple_rule_without_rule_id'; -export * from './get_simple_saved_query_rule'; -export * from './get_threat_match_rule_for_signal_testing'; -export * from './get_threshold_rule_for_signal_testing'; -export * from './get_slack_action'; -export * from './get_web_hook_action'; -export * from './index_event_log_execution_events'; -export * from './machine_learning_setup'; -export * from './perform_search_query'; -export * from './preview_rule_with_exception_entries'; -export * from './preview_rule'; -export * from './refresh_index'; export * from './route_with_namespace'; export * from './remove_server_generated_properties'; export * from './remove_server_generated_properties_including_rule_id'; -export * from './resolve_simple_rule_output'; -export * from './rule_to_ndjson'; export * from './rule_to_update_schema'; -export * from './set_signal_status'; -export * from './start_signals_migration'; export * from './update_rule'; export * from './wait_for'; -export * from './wait_for_alert_to_complete'; -export * from './wait_for_event_log_execute_complete'; export * from './wait_for_rule_status'; -export * from './wait_for_signals_to_be_present'; export * from './prebuilt_rules/create_prebuilt_rule_saved_objects'; -export * from './prebuilt_rules/delete_all_prebuilt_rule_assets'; -export * from './prebuilt_rules/install_mock_prebuilt_rules'; export * from './prebuilt_rules/install_prebuilt_rules_and_timelines'; -export * from './get_legacy_action_so'; -export * from './delete_all_exceptions'; +export * from './get_simple_rule_update'; +export * from './get_simple_ml_rule_update'; +export * from './create_non_security_rule'; +export * from './get_simple_rule_as_ndjson'; +export * from './rule_to_ndjson'; +export * from './delete_rule'; +export * from './get_query_signal_ids'; +export * from './get_query_signals_ids'; +export * from './get_signals_by_ids'; +export * from './wait_for_signals_to_be_present'; +export * from './get_rule_for_signal_testing'; diff --git a/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts b/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts deleted file mode 100644 index 47b870496642b..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts +++ /dev/null @@ -1,55 +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 type SuperTest from 'supertest'; -import { getCommonRequestHeader } from '../../functional/services/ml/common_api'; - -export const executeSetupModuleRequest = async ({ - module, - rspCode, - supertest, -}: { - module: string; - rspCode: number; - supertest: SuperTest.SuperTest; -}) => { - const { body } = await supertest - .post(`/internal/ml/modules/setup/${module}`) - .set(getCommonRequestHeader('1')) - .send({ - prefix: '', - groups: ['auditbeat'], - indexPatternName: 'auditbeat-*', - startDatafeed: false, - useDedicatedIndex: true, - applyToAllSpaces: true, - }) - .expect(rspCode); - - return body; -}; - -export const forceStartDatafeeds = async ({ - jobId, - rspCode, - supertest, -}: { - jobId: string; - rspCode: number; - supertest: SuperTest.SuperTest; -}) => { - const { body } = await supertest - .post(`/internal/ml/jobs/force_start_datafeeds`) - .set(getCommonRequestHeader('1')) - .send({ - datafeedIds: [`datafeed-${jobId}`], - start: new Date().getUTCMilliseconds(), - }) - .expect(rspCode); - - return body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/perform_search_query.ts b/x-pack/test/detection_engine_api_integration/utils/perform_search_query.ts deleted file mode 100644 index 6afd1eebb501c..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/perform_search_query.ts +++ /dev/null @@ -1,44 +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 type { Client } from '@elastic/elasticsearch'; - -import type { - QueryDslQueryContainer, - MappingRuntimeFields, - IndexName, - Field, -} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -interface PerformSearchQueryArgs { - es: Client; - query: QueryDslQueryContainer; - index: IndexName; - size?: number; - runtimeMappings?: MappingRuntimeFields; - fields?: Field[]; -} - -/** - * run ES search query - */ -export const performSearchQuery = async ({ - es, - query, - index, - size = 10, - runtimeMappings, - fields, -}: PerformSearchQueryArgs) => { - return es.search({ - index, - size, - fields, - query, - runtime_mappings: runtimeMappings, - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts deleted file mode 100644 index 899d5ddd7f83f..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/delete_all_prebuilt_rule_assets.ts +++ /dev/null @@ -1,23 +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 type { Client } from '@elastic/elasticsearch'; -import { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; - -/** - * Remove all prebuilt rule assets from the security solution savedObjects index - * @param es The ElasticSearch handle - */ -export const deleteAllPrebuiltRuleAssets = async (es: Client): Promise => { - await es.deleteByQuery({ - index: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, - q: 'type:security-rule', - wait_for_completion: true, - refresh: true, - body: {}, - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts deleted file mode 100644 index 0e15f416e1238..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts +++ /dev/null @@ -1,28 +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 { Client } from '@elastic/elasticsearch'; -import { InstallPrebuiltRulesAndTimelinesResponse } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; -import type SuperTest from 'supertest'; -import { createPrebuiltRuleAssetSavedObjects } from './create_prebuilt_rule_saved_objects'; -import { installPrebuiltRulesAndTimelines } from './install_prebuilt_rules_and_timelines'; - -/** - * Creates prebuilt rule mocks and installs them - * - * @param supertest Supertest instance - * @param es Elasticsearch client - * @returns Install prebuilt rules response - */ -export const installMockPrebuiltRules = async ( - supertest: SuperTest.SuperTest, - es: Client -): Promise => { - // Ensure there are prebuilt rule saved objects before installing rules - await createPrebuiltRuleAssetSavedObjects(es); - return installPrebuiltRulesAndTimelines(es, supertest); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts deleted file mode 100644 index a97c304f5ffaa..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts +++ /dev/null @@ -1,50 +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 type SuperTest from 'supertest'; -import type { - RuleCreateProps, - PreviewRulesSchema, - RulePreviewLogs, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { DETECTION_ENGINE_RULES_PREVIEW } from '@kbn/security-solution-plugin/common/constants'; - -/** - * Runs the preview for a rule. Any generated alerts will be written to .preview.alerts. - * This is much faster than actually running the rule, and can also quickly simulate multiple - * consecutive rule runs, e.g. for ensuring that rule state is properly handled across runs. - * @param supertest The supertest deps - * @param rule The rule to create - */ -export const previewRule = async ({ - supertest, - rule, - invocationCount = 1, - timeframeEnd = new Date(), -}: { - supertest: SuperTest.SuperTest; - rule: RuleCreateProps; - invocationCount?: number; - timeframeEnd?: Date; -}): Promise<{ - previewId: string; - logs: RulePreviewLogs[]; - isAborted: boolean; -}> => { - const previewRequest: PreviewRulesSchema = { - ...rule, - invocationCount, - timeframeEnd: timeframeEnd.toISOString(), - }; - const response = await supertest - .post(DETECTION_ENGINE_RULES_PREVIEW) - .set('kbn-xsrf', 'true') - .send(previewRequest) - .expect(200); - return response.body; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts deleted file mode 100644 index d53d731f755f9..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts +++ /dev/null @@ -1,63 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; -import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; -import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -import { createContainerWithEntries } from './create_container_with_entries'; -import { createContainerWithEndpointEntries } from './create_container_with_endpoint_entries'; -import { previewRule } from './preview_rule'; - -/** - * Convenience testing function where you can pass in just the entries and you will - * get a rule created with the entries added to an exception list and exception list item - * all auto-created at once. - * @param supertest super test agent - * @param rule The rule to create and attach an exception list to - * @param entries The entries to create the rule and exception list from - * @param endpointEntries The endpoint entries to create the rule and exception list from - * @param osTypes The os types to optionally add or not to add to the container - */ -export const previewRuleWithExceptionEntries = async ({ - supertest, - log, - rule, - entries, - endpointEntries, - invocationCount, - timeframeEnd, -}: { - supertest: SuperTest.SuperTest; - log: ToolingLog; - rule: RuleCreateProps; - entries: NonEmptyEntriesArray[]; - endpointEntries?: Array<{ - entries: NonEmptyEntriesArray; - osTypes: OsTypeArray | undefined; - }>; - invocationCount?: number; - timeframeEnd?: Date; -}) => { - const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); - const maybeEndpointList = await createContainerWithEndpointEntries( - supertest, - log, - endpointEntries ?? [] - ); - - return previewRule({ - supertest, - rule: { - ...rule, - exceptions_list: [...maybeExceptionList, ...maybeEndpointList], - }, - invocationCount, - timeframeEnd, - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/refresh_index.ts b/x-pack/test/detection_engine_api_integration/utils/refresh_index.ts deleted file mode 100644 index f888216cb6eed..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/refresh_index.ts +++ /dev/null @@ -1,19 +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 type { Client } from '@elastic/elasticsearch'; - -/** - * Refresh an index, making changes available to search. - * Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions. - * @param es The ElasticSearch handle - */ -export const refreshIndex = async (es: Client, index?: string) => { - await es.indices.refresh({ - index, - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts deleted file mode 100644 index 4f8b24e623ac3..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts +++ /dev/null @@ -1,13 +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 { getSimpleRuleOutput } from './get_simple_rule_output'; - -export const resolveSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ - ...getSimpleRuleOutput(ruleId, enabled), - outcome: 'exactMatch', -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts b/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts deleted file mode 100644 index 1ebcabae149b6..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/set_signal_status.ts +++ /dev/null @@ -1,22 +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 type { Status, SignalIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; - -export const setSignalStatus = ({ - signalIds, - query, - status, -}: { - signalIds?: SignalIds; - query?: object; - status: Status; -}) => ({ - signal_ids: signalIds, - query, - status, -}); diff --git a/x-pack/test/detection_engine_api_integration/utils/start_signals_migration.ts b/x-pack/test/detection_engine_api_integration/utils/start_signals_migration.ts deleted file mode 100644 index 0470266714fd2..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/start_signals_migration.ts +++ /dev/null @@ -1,44 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '@kbn/security-solution-plugin/common/constants'; - -interface CreateMigrationResponse { - index: string; - migration_index: string; - migration_id: string; -} - -export const startSignalsMigration = async ({ - indices, - supertest, - log, -}: { - supertest: SuperTest.SuperTest; - log: ToolingLog; - indices: string[]; -}): Promise => { - const response = await supertest - .post(DETECTION_ENGINE_SIGNALS_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ index: indices }); - - const { - body: { indices: created }, - }: { body: { indices: CreateMigrationResponse[] } } = response; - if (response.status !== 200) { - log.error( - `Did not get an expected 200 "ok" when starting a signals migration (startSignalsMigration). CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( - response.body - )}, status: ${JSON.stringify(response.status)}` - ); - } - return created; -}; diff --git a/x-pack/test/detection_engine_api_integration/utils/wait_for_alert_to_complete.ts b/x-pack/test/detection_engine_api_integration/utils/wait_for_alert_to_complete.ts deleted file mode 100644 index 381116b5a65d2..0000000000000 --- a/x-pack/test/detection_engine_api_integration/utils/wait_for_alert_to_complete.ts +++ /dev/null @@ -1,33 +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 type { ToolingLog } from '@kbn/tooling-log'; -import type SuperTest from 'supertest'; - -import { waitFor } from './wait_for'; - -export const waitForAlertToComplete = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - id: string -): Promise => { - await waitFor( - async () => { - const response = await supertest.get(`/api/alerts/alert/${id}/state`).set('kbn-xsrf', 'true'); - if (response.status !== 200) { - log.debug( - `Did not get an expected 200 "ok" when waiting for an alert to complete (waitForAlertToComplete). 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.previousStartedAt != null; - }, - 'waitForAlertToComplete', - log - ); -}; diff --git a/x-pack/test/functional/es_archives/security_solution/alias/mappings.json b/x-pack/test/functional/es_archives/security_solution/alias/mappings.json index 280ec9377df64..c9ed8841cb910 100644 --- a/x-pack/test/functional/es_archives/security_solution/alias/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/alias/mappings.json @@ -27,7 +27,6 @@ }, "settings": { "index": { - "refresh_interval": "1s", "number_of_replicas": "1", "number_of_shards": "1" } diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index 05299d0db6b96..3c083278eedce 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -48,11 +48,7 @@ "exception_operators_ips_text_array:qa:serverless": "npm run run-tests:dr:default exceptions/operators_data_types/ips_text_array serverless qaEnv", "exception_operators_ips_text_array:server:ess": "npm run initialize-server:dr:default exceptions/operators_data_types/ips_text_array ess", "exception_operators_ips_text_array:runner:ess": "npm run run-tests:dr:default exceptions/operators_data_types/ips_text_array ess essEnv", - "rule_creation:server:serverless": "npm run initialize-server:dr:default rule_creation serverless", - "rule_creation:runner:serverless": "npm run run-tests:dr:default rule_creation serverless serverlessEnv", - "rule_creation:qa:serverless": "npm run run-tests:dr:default rule_creation serverless qaEnv", - "rule_creation:server:ess": "npm run initialize-server:dr:default rule_creation ess", - "rule_creation:runner:ess": "npm run run-tests:dr:default rule_creation ess essEnv", + "actions:server:serverless": "npm run initialize-server:dr:default actions serverless", "actions:runner:serverless": "npm run run-tests:dr:default actions serverless serverlessEnv", "actions:qa:serverless": "npm run run-tests:dr:default actions serverless qaEnv", @@ -88,31 +84,85 @@ "prebuilt_rules_update_prebuilt_rules_package:qa:serverless": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package serverless qaEnv", "prebuilt_rules_update_prebuilt_rules_package:server:ess": "npm run initialize-server:dr:default prebuilt_rules/update_prebuilt_rules_package ess", "prebuilt_rules_update_prebuilt_rules_package:runner:ess": "npm run run-tests:dr:default prebuilt_rules/update_prebuilt_rules_package ess essEnvs", + "rule_execution_logic:server:serverless": "npm run initialize-server:dr:default rule_execution_logic serverless", "rule_execution_logic:runner:serverless": "npm run run-tests:dr:default rule_execution_logic serverless serverlessEnv", "rule_execution_logic:qa:serverless": "npm run run-tests:dr:default rule_execution_logic serverless qaEnv", "rule_execution_logic:server:ess": "npm run initialize-server:dr:default rule_execution_logic ess", "rule_execution_logic:runner:ess": "npm run run-tests:dr:default rule_execution_logic ess essEnv", + "user_roles:server:serverless": "npm run initialize-server:dr:default user_roles serverless", "user_roles:runner:serverless": "npm run run-tests:dr:default user_roles serverless serverlessEnv", "user_roles:qa:serverless": "npm run run-tests:dr:default user_roles serverless qaEnv", "user_roles:server:ess": "npm run initialize-server:dr:default user_roles ess", "user_roles:runner:ess": "npm run run-tests:dr:default user_roles ess essEnv", + "telemetry:server:serverless": "npm run initialize-server:dr:default telemetry serverless", "telemetry:runner:serverless": "npm run run-tests:dr:default telemetry serverless serverlessEnv", "telemetry:qa:serverless": "npm run run-tests:dr:default telemetry serverless qaEnv", "telemetry:server:ess": "npm run initialize-server:dr:default telemetry ess", "telemetry:runner:ess": "npm run run-tests:dr:default telemetry ess essEnv", + + "rule_creation:server:serverless": "npm run initialize-server:dr:default rule_creation serverless", + "rule_creation:runner:serverless": "npm run run-tests:dr:default rule_creation serverless serverlessEnv", + "rule_creation:qa:serverless": "npm run run-tests:dr:default rule_creation serverless qaEnv", + "rule_creation:server:ess": "npm run initialize-server:dr:default rule_creation ess", + "rule_creation:runner:ess": "npm run run-tests:dr:default rule_creation ess essEnv", + + "rule_delete:server:serverless": "npm run initialize-server:dr:default rule_delete serverless", + "rule_delete:runner:serverless": "npm run run-tests:dr:default rule_delete serverless serverlessEnv", + "rule_delete:qa:serverless": "npm run run-tests:dr:default rule_delete serverless qaEnv", + "rule_delete:server:ess": "npm run initialize-server:dr:default rule_delete ess", + "rule_delete:runner:ess": "npm run run-tests:dr:default rule_delete ess essEnv", + + "rule_update:server:serverless": "npm run initialize-server:dr:default rule_update serverless", + "rule_update:runner:serverless": "npm run run-tests:dr:default rule_update serverless serverlessEnv", + "rule_update:qa:serverless": "npm run run-tests:dr:default rule_update serverless qaEnv", + "rule_update:server:ess": "npm run initialize-server:dr:default rule_update ess", + "rule_update:runner:ess": "npm run run-tests:dr:default rule_update ess essEnv", + + "rule_patch:server:serverless": "npm run initialize-server:dr:default rule_patch serverless", + "rule_patch:runner:serverless": "npm run run-tests:dr:default rule_patch serverless serverlessEnv", + "rule_patch:qa:serverless": "npm run run-tests:dr:default rule_patch serverless qaEnv", + "rule_patch:server:ess": "npm run initialize-server:dr:default rule_patch ess", + "rule_patch:runner:ess": "npm run run-tests:dr:default rule_patch ess essEnv", + + "rule_import_export:server:serverless": "npm run initialize-server:dr:default rule_import_export serverless", + "rule_import_export:runner:serverless": "npm run run-tests:dr:default rule_import_export serverless serverlessEnv", + "rule_import_export:qa:serverless": "npm run run-tests:dr:default rule_import_export serverless qaEnv", + "rule_import_export:server:ess": "npm run initialize-server:dr:default rule_import_export ess", + "rule_import_export:runner:ess": "npm run run-tests:dr:default rule_import_export ess essEnv", + + "rule_management:server:serverless": "npm run initialize-server:dr:default rule_management serverless", + "rule_management:runner:serverless": "npm run run-tests:dr:default rule_management serverless serverlessEnv", + "rule_management:qa:serverless": "npm run run-tests:dr:default rule_management serverless qaEnv", + "rule_management:server:ess": "npm run initialize-server:dr:default rule_management ess", + "rule_management:runner:ess": "npm run run-tests:dr:default rule_management ess essEnv", + + "rule_bulk_actions:server:serverless": "npm run initialize-server:dr:default rule_bulk_actions serverless", + "rule_bulk_actions:runner:serverless": "npm run run-tests:dr:default rule_bulk_actions serverless serverlessEnv", + "rule_bulk_actions:qa:serverless": "npm run run-tests:dr:default rule_bulk_actions serverless qaEnv", + "rule_bulk_actions:server:ess": "npm run initialize-server:dr:default rule_bulk_actions ess", + "rule_bulk_actions:runner:ess": "npm run run-tests:dr:default rule_bulk_actions ess essEnv", + + "rule_read:server:serverless": "npm run initialize-server:dr:default rule_read serverless", + "rule_read:runner:serverless": "npm run run-tests:dr:default rule_read serverless serverlessEnv", + "rule_read:qa:serverless": "npm run run-tests:dr:default rule_read serverless qaEnv", + "rule_read:server:ess": "npm run initialize-server:dr:default rule_read ess", + "rule_read:runner:ess": "npm run run-tests:dr:default rule_read ess essEnv", + "detection_engine_basicessentionals:server:serverless": "npm run initialize-server:dr:basicEssentials detection_engine serverless", "detection_engine_basicessentionals:runner:serverless": "npm run run-tests:dr:basicEssentials detection_engine serverless serverlessEnv", "detection_engine_basicessentionals:qa:serverless": "npm run run-tests:dr:basicEssentials detection_engine serverless qaEnv", "detection_engine_basicessentionals:server:ess": "npm run initialize-server:dr:basicEssentials detection_engine ess", "detection_engine_basicessentionals:runner:ess": "npm run run-tests:dr:basicEssentials detection_engine ess essEnv", + "exception_lists_items:server:serverless": "npm run initialize-server:lists:default exception_lists_items serverless", "exception_lists_items:runner:serverless": "npm run run-tests:lists:default exception_lists_items serverless serverlessEnv", "exception_lists_items:qa:serverless": "npm run run-tests:lists:default exception_lists_items serverless qaEnv", "exception_lists_items:server:ess": "npm run initialize-server:lists:default exception_lists_items ess", "exception_lists_items:runner:ess": "npm run run-tests:lists:default exception_lists_items ess essEnv", + "lists_items:server:serverless": "npm run initialize-server:lists:default lists_items serverless", "lists_items:runner:serverless": "npm run run-tests:lists:default lists_items serverless serverlessEnv", "lists_items:qa:serverless": "npm run run-tests:lists:default lists_items serverless qaEnv", diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts new file mode 100644 index 0000000000000..92303ddd2445f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_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: 'Rule Bulk Actions API Integration Tests - ESS - Rule bulk actions logic', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts new file mode 100644 index 0000000000000..9e4f790d3ded7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Bulk Actions API Integration Tests - Serverless - Rule bulk actions logic', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/index.ts new file mode 100644 index 0000000000000..87022ecd347aa --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/index.ts @@ -0,0 +1,17 @@ +/* + * 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('Rule Bulk Action API', function () { + loadTestFile(require.resolve('./perform_bulk_action_dry_run')); + loadTestFile(require.resolve('./perform_bulk_action_dry_run_ess')); + loadTestFile(require.resolve('./perform_bulk_action')); + loadTestFile(require.resolve('./perform_bulk_action_ess')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts similarity index 74% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts index d14d82edbba51..f7e48ac30a6eb 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action.ts @@ -5,16 +5,12 @@ * 2.0. */ -import { Rule } from '@kbn/alerting-plugin/common'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import expect from '@kbn/expect'; -import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, NOTIFICATION_THROTTLE_RULE, } from '@kbn/security-solution-plugin/common/constants'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { BulkActionTypeEnum, BulkActionEditTypeEnum, @@ -25,12 +21,10 @@ import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/c import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants'; import { binaryToString, - createLegacyRuleAction, createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getLegacyActionSO, getSimpleMlRule, getSimpleRule, getSimpleRuleOutput, @@ -38,21 +32,20 @@ import { getWebHookAction, installMockPrebuiltRules, removeServerGeneratedProperties, - waitForRuleSuccess, - getRuleSOById, - createRuleThroughAlertingEndpoint, - getRuleSavedObjectWithLegacyInvestigationFields, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, - deleteAllExceptions, + updateUsername, } from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteAllExceptions } from '../../../lists_and_exception_lists/utils'; + +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); const esArchiver = getService('esArchiver'); + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); const postBulkAction = () => supertest @@ -90,9 +83,9 @@ export default ({ getService }: FtrProviderContext): void => { const createWebHookConnector = () => createConnector(getWebHookAction()); const createSlackConnector = () => createConnector(getSlackAction()); - describe('perform_bulk_action', () => { + describe('@ess @serverless @brokenInServerless @skipInQA perform_bulk_action', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); @@ -115,7 +108,8 @@ export default ({ getService }: FtrProviderContext): void => { const [ruleJson, exportDetailsJson] = body.toString().split(/\n/); const rule = removeServerGeneratedProperties(JSON.parse(ruleJson)); - expect(rule).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + expect(rule).to.eql(expectedRule); const exportDetails = JSON.parse(exportDetailsJson); expect(exportDetails).to.eql({ @@ -186,8 +180,10 @@ export default ({ getService }: FtrProviderContext): void => { const [ruleJson, connectorsJson, exportDetailsJson] = body.toString().split(/\n/); const rule = removeServerGeneratedProperties(JSON.parse(ruleJson)); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + expect(rule).to.eql({ - ...getSimpleRuleOutput(), + ...expectedRule, actions: [ { action_type_id: '.webhook', @@ -246,45 +242,6 @@ export default ({ getService }: FtrProviderContext): void => { await fetchRule(ruleId).expect(404); }); - it('should delete rules and any associated legacy actions', async () => { - const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, false)), - ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); - - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.delete }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); - - // Check that the deleted rule is returned with the response - expect(body.attributes.results.deleted[0].name).to.eql(rule1.name); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - - // Check that the updates have been persisted - await fetchRule(ruleId).expect(404); - }); - it('should enable rules', async () => { const ruleId = 'ruleId'; await createRule(supertest, log, getSimpleRule(ruleId)); @@ -303,61 +260,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(ruleBody.enabled).to.eql(true); }); - it('should enable rules and migrate actions', async () => { - const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, false)), - ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); - - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.enable }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); - - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(true); - - // Check that the updates have been persisted - const { body: ruleBody } = await fetchRule(ruleId).expect(200); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - - expect(ruleBody.enabled).to.eql(true); - expect(ruleBody.actions).to.eql([ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - uuid: ruleBody.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - // we want to ensure rule is executing successfully, to prevent any AAD issues related to partial update of rule SO - await waitForRuleSuccess({ id: rule1.id, supertest, log }); - }); - it('should disable rules', async () => { const ruleId = 'ruleId'; await createRule(supertest, log, getSimpleRule(ruleId, true)); @@ -376,59 +278,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(ruleBody.enabled).to.eql(false); }); - it('should disable rules and migrate actions', async () => { - const ruleId = 'ruleId'; - const [connector, rule1] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, true)), - ]); - await createLegacyRuleAction(supertest, rule1.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); - - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.disable }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); - - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(false); - - // Check that the updates have been persisted - const { body: ruleBody } = await fetchRule(ruleId).expect(200); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - - expect(ruleBody.enabled).to.eql(false); - expect(ruleBody.actions).to.eql([ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - uuid: ruleBody.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - }); - it('should duplicate rules', async () => { const ruleId = 'ruleId'; const ruleToDuplicate = getSimpleRule(ruleId); @@ -669,70 +518,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); }); - it('should duplicate rule with a legacy action', async () => { - const ruleId = 'ruleId'; - const [connector, ruleToDuplicate] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, true)), - ]); - await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - ruleToDuplicate.id - ); - - const { body } = await postBulkAction() - .send({ - query: '', - action: BulkActionTypeEnum.duplicate, - duplicate: { include_exceptions: false, include_expired_exceptions: false }, - }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); - - // Check that the duplicated rule is returned with the response - expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); - - // Check that the updates have been persisted - const { body: rulesResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - expect(rulesResponse.total).to.eql(2); - - rulesResponse.data.forEach((rule: RuleResponse) => { - const uuid = rule.actions[0].uuid; - expect(rule.actions).to.eql([ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - ...(uuid ? { uuid } : {}), - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - }); - }); - describe('edit action', () => { describe('tags actions', () => { const overwriteTagsCases = [ @@ -1137,36 +922,6 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should return error if index patterns action is applied to ES|QL rule', async () => { - const esqlRule = await createRule(supertest, log, getCreateEsqlRulesSchemaMock()); - - const { body } = await postBulkAction() - .send({ - ids: [esqlRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.add_index_patterns, - value: ['index-*'], - }, - ], - }) - .expect(500); - - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ - message: - "Index patterns can't be added. ES|QL rule doesn't have index patterns property", - status_code: 500, - rules: [ - { - id: esqlRule.id, - name: esqlRule.name, - }, - ], - }); - }); - it('should return error if all index patterns removed from a rule', async () => { const rule = await createRule(supertest, log, { ...getSimpleRule(), @@ -1327,71 +1082,6 @@ export default ({ getService }: FtrProviderContext): void => { ); }); - it('should migrate legacy actions on edit', async () => { - const ruleId = 'ruleId'; - const [connector, ruleToDuplicate] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, true)), - ]); - await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - ruleToDuplicate.id - ); - - const { body: setTagsBody } = await postBulkAction().send({ - query: '', - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.set_tags, - value: ['reset-tag'], - }, - ], - }); - expect(setTagsBody.attributes.summary).to.eql({ - failed: 0, - skipped: 0, - succeeded: 1, - total: 1, - }); - - // Check that the updates have been persisted - const { body: setTagsRule } = await fetchRule(ruleId).expect(200); - - // Sidecar should be removed - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - - expect(setTagsRule.tags).to.eql(['reset-tag']); - - expect(setTagsRule.actions).to.eql([ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - uuid: setTagsRule.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - }); - it('should set timeline template values in rule', async () => { const ruleId = 'ruleId'; const timelineId = '91832785-286d-4ebe-b884-1a208d111a70'; @@ -1787,77 +1477,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(readRule.actions).to.eql([]); }); - - it('should migrate legacy actions on edit when actions edited', async () => { - const ruleId = 'ruleId'; - const [connector, createdRule] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule(ruleId, true)), - ]); - // create a new connector - const webHookConnector = await createWebHookConnector(); - - await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - createdRule.id - ); - - const { body } = await postBulkAction() - .send({ - ids: [createdRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.set_rule_actions, - value: { - throttle: '1h', - actions: [ - { - ...webHookActionMock, - id: webHookConnector.id, - }, - ], - }, - }, - ], - }) - .expect(200); - - const expectedRuleActions = [ - { - ...webHookActionMock, - id: webHookConnector.id, - action_type_id: '.webhook', - uuid: body.attributes.results.updated[0].actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]; - - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); - - // Check that the updates have been persisted - const { body: readRule } = await fetchRule(ruleId).expect(200); - - expect(readRule.actions).to.eql(expectedRuleActions); - - // Sidecar should be removed - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - }); }); describe('add_rule_actions', () => { @@ -3004,421 +2623,5 @@ export default ({ getService }: FtrProviderContext): void => { expect(rule.timeline_id).to.eql(timelineId); expect(rule.timeline_title).to.eql(timelineTitle); }); - - describe('legacy investigation fields', () => { - let ruleWithLegacyInvestigationField: Rule; - let ruleWithLegacyInvestigationFieldEmptyArray: Rule; - - beforeEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); - ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFields() - ); - ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() - ); - await createRule(supertest, log, { - ...getSimpleRule('rule-with-investigation-field'), - name: 'Test investigation fields object', - investigation_fields: { field_names: ['host.name'] }, - }); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('should export rules with legacy investigation_fields and transform legacy field in response', async () => { - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.export }) - .expect(200) - .expect('Content-Type', 'application/ndjson') - .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') - .parse(binaryToString); - - const [rule1, rule2, rule3, exportDetailsJson] = body.toString().split(/\n/); - - const ruleToCompareWithLegacyInvestigationField = removeServerGeneratedProperties( - JSON.parse(rule1) - ); - expect(ruleToCompareWithLegacyInvestigationField.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - - const ruleToCompareWithLegacyInvestigationFieldEmptyArray = removeServerGeneratedProperties( - JSON.parse(rule2) - ); - expect(ruleToCompareWithLegacyInvestigationFieldEmptyArray.investigation_fields).to.eql( - undefined - ); - - const ruleWithInvestigationField = removeServerGeneratedProperties(JSON.parse(rule3)); - expect(ruleWithInvestigationField.investigation_fields).to.eql({ - field_names: ['host.name'], - }); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should not include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, JSON.parse(rule1).id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const exportDetails = JSON.parse(exportDetailsJson); - expect(exportDetails).to.eql({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, - exported_count: 3, - exported_rules_count: 3, - missing_exception_list_item_count: 0, - missing_exception_list_items: [], - missing_exception_lists: [], - missing_exception_lists_count: 0, - missing_rules: [], - missing_rules_count: 0, - excluded_action_connection_count: 0, - excluded_action_connections: [], - exported_action_connector_count: 0, - missing_action_connection_count: 0, - missing_action_connections: [], - }); - }); - - it('should delete rules with investigation fields and transform legacy field in response', async () => { - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.delete }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); - - // Check that the deleted rule is returned with the response - const names = body.attributes.results.deleted.map( - (returnedRule: RuleResponse) => returnedRule.name - ); - expect(names.includes('Test investigation fields')).to.eql(true); - expect(names.includes('Test investigation fields empty array')).to.eql(true); - expect(names.includes('Test investigation fields object')).to.eql(true); - - const ruleWithLegacyField = body.attributes.results.deleted.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId - ); - - expect(ruleWithLegacyField.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - - // Check that the updates have been persisted - await fetchRule(ruleWithLegacyInvestigationField.params.ruleId).expect(404); - await fetchRule(ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId).expect(404); - await fetchRule('rule-with-investigation-field').expect(404); - }); - - it('should enable rules with legacy investigation fields and transform legacy field in response', async () => { - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.enable }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); - - // Check that the updated rule is returned with the response - // and field transformed on response - expect( - body.attributes.results.updated.every( - (returnedRule: RuleResponse) => returnedRule.enabled - ) - ).to.eql(true); - - const ruleWithLegacyField = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId - ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - - const ruleWithEmptyArray = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId - ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); - - const ruleWithIntendedType = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' - ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should not include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - expect(ruleSO?.alert?.enabled).to.eql(true); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - expect(ruleSO?.alert?.enabled).to.eql(true); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); - expect(ruleSO?.alert?.enabled).to.eql(true); - }); - - it('should disable rules with legacy investigation fields and transform legacy field in response', async () => { - const { body } = await postBulkAction() - .send({ query: '', action: BulkActionTypeEnum.disable }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); - - // Check that the updated rule is returned with the response - // and field transformed on response - expect( - body.attributes.results.updated.every( - (returnedRule: RuleResponse) => !returnedRule.enabled - ) - ).to.eql(true); - - const ruleWithLegacyField = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId - ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - - const ruleWithEmptyArray = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId - ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); - - const ruleWithIntendedType = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' - ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should not include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); - }); - - it('should duplicate rules with legacy investigation fields and transform field in response', async () => { - const { body } = await postBulkAction() - .send({ - query: '', - action: BulkActionTypeEnum.duplicate, - duplicate: { include_exceptions: false, include_expired_exceptions: false }, - }) - .expect(200); - - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); - - // Check that the duplicated rule is returned with the response - const names = body.attributes.results.created.map( - (returnedRule: RuleResponse) => returnedRule.name - ); - expect(names.includes('Test investigation fields [Duplicate]')).to.eql(true); - expect(names.includes('Test investigation fields empty array [Duplicate]')).to.eql(true); - expect(names.includes('Test investigation fields object [Duplicate]')).to.eql(true); - - // Check that the updates have been persisted - const { body: rulesResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - expect(rulesResponse.total).to.eql(6); - - const ruleWithLegacyField = body.attributes.results.created.find( - (returnedRule: RuleResponse) => - returnedRule.name === 'Test investigation fields [Duplicate]' - ); - const ruleWithEmptyArray = body.attributes.results.created.find( - (returnedRule: RuleResponse) => - returnedRule.name === 'Test investigation fields empty array [Duplicate]' - ); - const ruleWithIntendedType = body.attributes.results.created.find( - (returnedRule: RuleResponse) => - returnedRule.name === 'Test investigation fields object [Duplicate]' - ); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, duplicated - * rules should NOT have migrated value on write. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyField.id); - - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, the original - * rules selected to be duplicated should not be migrated. - */ - const { - hits: { - hits: [{ _source: ruleSOOriginalLegacy }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - - expect(ruleSOOriginalLegacy?.alert?.params?.investigationFields).to.eql([ - 'client.address', - 'agent.name', - ]); - - const { - hits: { - hits: [{ _source: ruleSOOriginalLegacyEmptyArray }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSOOriginalLegacyEmptyArray?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSOOriginalNoLegacy }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSOOriginalNoLegacy?.alert?.params?.investigationFields).to.eql({ - field_names: ['host.name'], - }); - }); - - it('should edit rules with legacy investigation fields', async () => { - const { body } = await postBulkAction().send({ - query: '', - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.set_tags, - value: ['reset-tag'], - }, - ], - }); - expect(body.attributes.summary).to.eql({ - failed: 0, - skipped: 0, - succeeded: 3, - total: 3, - }); - - // Check that the updated rule is returned with the response - // and field transformed on response - const ruleWithLegacyField = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId - ); - expect(ruleWithLegacyField.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - expect(ruleWithLegacyField.tags).to.eql(['reset-tag']); - - const ruleWithEmptyArray = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => - returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId - ); - expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); - expect(ruleWithEmptyArray.tags).to.eql(['reset-tag']); - - const ruleWithIntendedType = body.attributes.results.updated.find( - (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' - ); - expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); - expect(ruleWithIntendedType.tags).to.eql(['reset-tag']); - - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should not include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); - - const { - hits: { - hits: [{ _source: ruleSO2 }], - }, - } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); - expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); - - const { - hits: { - hits: [{ _source: ruleSO3 }], - }, - } = await getRuleSOById(es, ruleWithIntendedType.id); - expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); - }); - }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run.ts similarity index 81% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run.ts index f1df03187379c..216d00533e100 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run.ts @@ -8,25 +8,23 @@ import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, } from '@kbn/security-solution-plugin/common/constants'; -import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; import expect from 'expect'; import { BulkActionTypeEnum, BulkActionEditTypeEnum, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleMlRule, getSimpleRule, installMockPrebuiltRules, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); @@ -51,9 +49,9 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .set('elastic-api-version', '2023-10-31'); - describe('perform_bulk_action dry_run', () => { + describe('@ess @serverless @skipInQA perform_bulk_action dry_run', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -292,59 +290,6 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }); - - describe('validate updating index pattern for ES|QL rule', () => { - const actions = [ - BulkActionEditTypeEnum.add_index_patterns, - BulkActionEditTypeEnum.set_index_patterns, - BulkActionEditTypeEnum.delete_index_patterns, - ]; - - actions.forEach((editAction) => { - it(`should return error if ${editAction} action is applied to ES|QL rule`, async () => { - const esqlRule = await createRule(supertest, log, getCreateEsqlRulesSchemaMock()); - - const { body } = await postDryRunBulkAction() - .send({ - ids: [esqlRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: editAction, - value: [], - }, - ], - }) - .expect(500); - - expect(body.attributes.summary).toEqual({ - failed: 1, - skipped: 0, - succeeded: 0, - total: 1, - }); - expect(body.attributes.results).toEqual({ - updated: [], - skipped: [], - created: [], - deleted: [], - }); - - expect(body.attributes.errors).toHaveLength(1); - expect(body.attributes.errors[0]).toEqual({ - err_code: 'ESQL_INDEX_PATTERN', - message: "ES|QL rule doesn't have index patterns", - status_code: 500, - rules: [ - { - id: esqlRule.id, - name: esqlRule.name, - }, - ], - }); - }); - }); - }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run_ess.ts new file mode 100644 index 0000000000000..7357d559ffd68 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_dry_run_ess.ts @@ -0,0 +1,95 @@ +/* + * 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 { DETECTION_ENGINE_RULES_BULK_ACTION } from '@kbn/security-solution-plugin/common/constants'; +import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; + +import expect from 'expect'; +import { + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; +import { createRule, createAlertsIndex, deleteAllRules, deleteAllAlerts } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + const postDryRunBulkAction = () => + supertest + .post(DETECTION_ENGINE_RULES_BULK_ACTION) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .query({ dry_run: true }); + + describe('@ess perform_bulk_action dry_run - ESS specific logic', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + describe('edit action', () => { + describe('validate updating index pattern for ES|QL rule', () => { + const actions = [ + BulkActionEditTypeEnum.add_index_patterns, + BulkActionEditTypeEnum.set_index_patterns, + BulkActionEditTypeEnum.delete_index_patterns, + ]; + + actions.forEach((editAction) => { + it(`should return error if ${editAction} action is applied to ES|QL rule`, async () => { + const esqlRule = await createRule(supertest, log, getCreateEsqlRulesSchemaMock()); + + const { body } = await postDryRunBulkAction() + .send({ + ids: [esqlRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: editAction, + value: [], + }, + ], + }) + .expect(500); + + expect(body.attributes.summary).toEqual({ + failed: 1, + skipped: 0, + succeeded: 0, + total: 1, + }); + expect(body.attributes.results).toEqual({ + updated: [], + skipped: [], + created: [], + deleted: [], + }); + + expect(body.attributes.errors).toHaveLength(1); + expect(body.attributes.errors[0]).toEqual({ + err_code: 'ESQL_INDEX_PATTERN', + message: "ES|QL rule doesn't have index patterns", + status_code: 500, + rules: [ + { + id: esqlRule.id, + name: esqlRule.name, + }, + ], + }); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts new file mode 100644 index 0000000000000..9d145dc5a7c75 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/perform_bulk_action_ess.ts @@ -0,0 +1,885 @@ +/* + * 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 { Rule } from '@kbn/alerting-plugin/common'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import expect from '@kbn/expect'; +import { getCreateEsqlRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; +import { + DETECTION_ENGINE_RULES_BULK_ACTION, + DETECTION_ENGINE_RULES_URL, +} from '@kbn/security-solution-plugin/common/constants'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { + BulkActionTypeEnum, + BulkActionEditTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; +import { + binaryToString, + createLegacyRuleAction, + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getLegacyActionSO, + getSimpleRule, + getWebHookAction, + removeServerGeneratedProperties, + waitForRuleSuccess, + getRuleSOById, + createRuleThroughAlertingEndpoint, + getRuleSavedObjectWithLegacyInvestigationFields, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, +} from '../../utils'; + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + + const postBulkAction = () => + supertest + .post(DETECTION_ENGINE_RULES_BULK_ACTION) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const fetchRule = (ruleId: string) => + supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31'); + + const createConnector = async (payload: Record) => + (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) + .body; + + const createWebHookConnector = () => createConnector(getWebHookAction()); + + describe('@ess perform_bulk_action - ESS specific logic', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + it('should delete rules and any associated legacy actions', async () => { + const ruleId = 'ruleId'; + const [connector, rule1] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, false)), + ]); + await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.delete }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + + // Check that the deleted rule is returned with the response + expect(body.attributes.results.deleted[0].name).to.eql(rule1.name); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + + // Check that the updates have been persisted + await fetchRule(ruleId).expect(404); + }); + + it('should enable rules and migrate actions', async () => { + const ruleId = 'ruleId'; + const [connector, rule1] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, false)), + ]); + await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.enable }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].enabled).to.eql(true); + + // Check that the updates have been persisted + const { body: ruleBody } = await fetchRule(ruleId).expect(200); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + + expect(ruleBody.enabled).to.eql(true); + expect(ruleBody.actions).to.eql([ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + uuid: ruleBody.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + // we want to ensure rule is executing successfully, to prevent any AAD issues related to partial update of rule SO + await waitForRuleSuccess({ id: rule1.id, supertest, log }); + }); + + it('should disable rules and migrate actions', async () => { + const ruleId = 'ruleId'; + const [connector, rule1] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, true)), + ]); + await createLegacyRuleAction(supertest, rule1.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule1.id); + + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.disable }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].enabled).to.eql(false); + + // Check that the updates have been persisted + const { body: ruleBody } = await fetchRule(ruleId).expect(200); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + + expect(ruleBody.enabled).to.eql(false); + expect(ruleBody.actions).to.eql([ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + uuid: ruleBody.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + }); + + it('should duplicate rule with a legacy action', async () => { + const ruleId = 'ruleId'; + const [connector, ruleToDuplicate] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, true)), + ]); + await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + ruleToDuplicate.id + ); + + const { body } = await postBulkAction() + .send({ + query: '', + action: BulkActionTypeEnum.duplicate, + duplicate: { include_exceptions: false, include_expired_exceptions: false }, + }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + + // Check that the duplicated rule is returned with the response + expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + + // Check that the updates have been persisted + const { body: rulesResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + expect(rulesResponse.total).to.eql(2); + + rulesResponse.data.forEach((rule: RuleResponse) => { + const uuid = rule.actions[0].uuid; + expect(rule.actions).to.eql([ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + ...(uuid ? { uuid } : {}), + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + }); + }); + + describe('edit action', () => { + describe('index patterns actions', () => { + it('should return error if index patterns action is applied to ES|QL rule', async () => { + const esqlRule = await createRule(supertest, log, getCreateEsqlRulesSchemaMock()); + + const { body } = await postBulkAction() + .send({ + ids: [esqlRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_index_patterns, + value: ['index-*'], + }, + ], + }) + .expect(500); + + expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).to.eql({ + message: + "Index patterns can't be added. ES|QL rule doesn't have index patterns property", + status_code: 500, + rules: [ + { + id: esqlRule.id, + name: esqlRule.name, + }, + ], + }); + }); + }); + + it('should migrate legacy actions on edit', async () => { + const ruleId = 'ruleId'; + const [connector, ruleToDuplicate] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, true)), + ]); + await createLegacyRuleAction(supertest, ruleToDuplicate.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + ruleToDuplicate.id + ); + + const { body: setTagsBody } = await postBulkAction().send({ + query: '', + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_tags, + value: ['reset-tag'], + }, + ], + }); + expect(setTagsBody.attributes.summary).to.eql({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); + + // Check that the updates have been persisted + const { body: setTagsRule } = await fetchRule(ruleId).expect(200); + + // Sidecar should be removed + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + + expect(setTagsRule.tags).to.eql(['reset-tag']); + + expect(setTagsRule.actions).to.eql([ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + uuid: setTagsRule.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + }); + + describe('rule actions', () => { + const webHookActionMock = { + group: 'default', + params: { + body: '{"test":"action to be saved in a rule"}', + }, + }; + + describe('set_rule_actions', () => { + it('should migrate legacy actions on edit when actions edited', async () => { + const ruleId = 'ruleId'; + const [connector, createdRule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule(ruleId, true)), + ]); + // create a new connector + const webHookConnector = await createWebHookConnector(); + + await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createdRule.id + ); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_rule_actions, + value: { + throttle: '1h', + actions: [ + { + ...webHookActionMock, + id: webHookConnector.id, + }, + ], + }, + }, + ], + }) + .expect(200); + + const expectedRuleActions = [ + { + ...webHookActionMock, + id: webHookConnector.id, + action_type_id: '.webhook', + uuid: body.attributes.results.updated[0].actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + + // Check that the updates have been persisted + const { body: readRule } = await fetchRule(ruleId).expect(200); + + expect(readRule.actions).to.eql(expectedRuleActions); + + // Sidecar should be removed + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + }); + }); + }); + + describe('legacy investigation fields', () => { + let ruleWithLegacyInvestigationField: Rule; + let ruleWithLegacyInvestigationFieldEmptyArray: Rule; + + beforeEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ); + ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ); + await createRule(supertest, log, { + ...getSimpleRule('rule-with-investigation-field'), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }); + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('should export rules with legacy investigation_fields and transform legacy field in response', async () => { + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.export }) + .expect(200) + .expect('Content-Type', 'application/ndjson') + .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') + .parse(binaryToString); + + const [rule1, rule2, rule3, exportDetailsJson] = body.toString().split(/\n/); + + const ruleToCompareWithLegacyInvestigationField = removeServerGeneratedProperties( + JSON.parse(rule1) + ); + expect(ruleToCompareWithLegacyInvestigationField.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + + const ruleToCompareWithLegacyInvestigationFieldEmptyArray = removeServerGeneratedProperties( + JSON.parse(rule2) + ); + expect(ruleToCompareWithLegacyInvestigationFieldEmptyArray.investigation_fields).to.eql( + undefined + ); + + const ruleWithInvestigationField = removeServerGeneratedProperties(JSON.parse(rule3)); + expect(ruleWithInvestigationField.investigation_fields).to.eql({ + field_names: ['host.name'], + }); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should not include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, JSON.parse(rule1).id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + + const exportDetails = JSON.parse(exportDetailsJson); + expect(exportDetails).to.eql({ + exported_exception_list_count: 0, + exported_exception_list_item_count: 0, + exported_count: 3, + exported_rules_count: 3, + missing_exception_list_item_count: 0, + missing_exception_list_items: [], + missing_exception_lists: [], + missing_exception_lists_count: 0, + missing_rules: [], + missing_rules_count: 0, + excluded_action_connection_count: 0, + excluded_action_connections: [], + exported_action_connector_count: 0, + missing_action_connection_count: 0, + missing_action_connections: [], + }); + }); + + it('should delete rules with investigation fields and transform legacy field in response', async () => { + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.delete }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + + // Check that the deleted rule is returned with the response + const names = body.attributes.results.deleted.map( + (returnedRule: RuleResponse) => returnedRule.name + ); + expect(names.includes('Test investigation fields')).to.eql(true); + expect(names.includes('Test investigation fields empty array')).to.eql(true); + expect(names.includes('Test investigation fields object')).to.eql(true); + + const ruleWithLegacyField = body.attributes.results.deleted.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId + ); + + expect(ruleWithLegacyField.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + + // Check that the updates have been persisted + await fetchRule(ruleWithLegacyInvestigationField.params.ruleId).expect(404); + await fetchRule(ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId).expect(404); + await fetchRule('rule-with-investigation-field').expect(404); + }); + + it('should enable rules with legacy investigation fields and transform legacy field in response', async () => { + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.enable }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + + // Check that the updated rule is returned with the response + // and field transformed on response + expect( + body.attributes.results.updated.every( + (returnedRule: RuleResponse) => returnedRule.enabled + ) + ).to.eql(true); + + const ruleWithLegacyField = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId + ); + expect(ruleWithLegacyField.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + + const ruleWithEmptyArray = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId + ); + expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); + + const ruleWithIntendedType = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' + ); + expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should not include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, ruleWithLegacyField.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + expect(ruleSO?.alert?.enabled).to.eql(true); + + const { + hits: { + hits: [{ _source: ruleSO2 }], + }, + } = await getRuleSOById(es, ruleWithEmptyArray.id); + expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); + expect(ruleSO?.alert?.enabled).to.eql(true); + + const { + hits: { + hits: [{ _source: ruleSO3 }], + }, + } = await getRuleSOById(es, ruleWithIntendedType.id); + expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + expect(ruleSO?.alert?.enabled).to.eql(true); + }); + + it('should disable rules with legacy investigation fields and transform legacy field in response', async () => { + const { body } = await postBulkAction() + .send({ query: '', action: BulkActionTypeEnum.disable }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + + // Check that the updated rule is returned with the response + // and field transformed on response + expect( + body.attributes.results.updated.every( + (returnedRule: RuleResponse) => !returnedRule.enabled + ) + ).to.eql(true); + + const ruleWithLegacyField = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId + ); + expect(ruleWithLegacyField.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + + const ruleWithEmptyArray = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId + ); + expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); + + const ruleWithIntendedType = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' + ); + expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should not include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, ruleWithLegacyField.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + + const { + hits: { + hits: [{ _source: ruleSO2 }], + }, + } = await getRuleSOById(es, ruleWithEmptyArray.id); + expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); + + const { + hits: { + hits: [{ _source: ruleSO3 }], + }, + } = await getRuleSOById(es, ruleWithIntendedType.id); + expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + }); + + it('should duplicate rules with legacy investigation fields and transform field in response', async () => { + const { body } = await postBulkAction() + .send({ + query: '', + action: BulkActionTypeEnum.duplicate, + duplicate: { include_exceptions: false, include_expired_exceptions: false }, + }) + .expect(200); + + expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 3, total: 3 }); + + // Check that the duplicated rule is returned with the response + const names = body.attributes.results.created.map( + (returnedRule: RuleResponse) => returnedRule.name + ); + expect(names.includes('Test investigation fields [Duplicate]')).to.eql(true); + expect(names.includes('Test investigation fields empty array [Duplicate]')).to.eql(true); + expect(names.includes('Test investigation fields object [Duplicate]')).to.eql(true); + + // Check that the updates have been persisted + const { body: rulesResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + expect(rulesResponse.total).to.eql(6); + + const ruleWithLegacyField = body.attributes.results.created.find( + (returnedRule: RuleResponse) => + returnedRule.name === 'Test investigation fields [Duplicate]' + ); + const ruleWithEmptyArray = body.attributes.results.created.find( + (returnedRule: RuleResponse) => + returnedRule.name === 'Test investigation fields empty array [Duplicate]' + ); + const ruleWithIntendedType = body.attributes.results.created.find( + (returnedRule: RuleResponse) => + returnedRule.name === 'Test investigation fields object [Duplicate]' + ); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, duplicated + * rules should NOT have migrated value on write. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, ruleWithLegacyField.id); + + expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + + const { + hits: { + hits: [{ _source: ruleSO2 }], + }, + } = await getRuleSOById(es, ruleWithEmptyArray.id); + expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); + + const { + hits: { + hits: [{ _source: ruleSO3 }], + }, + } = await getRuleSOById(es, ruleWithIntendedType.id); + expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, the original + * rules selected to be duplicated should not be migrated. + */ + const { + hits: { + hits: [{ _source: ruleSOOriginalLegacy }], + }, + } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); + + expect(ruleSOOriginalLegacy?.alert?.params?.investigationFields).to.eql([ + 'client.address', + 'agent.name', + ]); + + const { + hits: { + hits: [{ _source: ruleSOOriginalLegacyEmptyArray }], + }, + } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); + expect(ruleSOOriginalLegacyEmptyArray?.alert?.params?.investigationFields).to.eql([]); + + const { + hits: { + hits: [{ _source: ruleSOOriginalNoLegacy }], + }, + } = await getRuleSOById(es, ruleWithIntendedType.id); + expect(ruleSOOriginalNoLegacy?.alert?.params?.investigationFields).to.eql({ + field_names: ['host.name'], + }); + }); + + it('should edit rules with legacy investigation fields', async () => { + const { body } = await postBulkAction().send({ + query: '', + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.set_tags, + value: ['reset-tag'], + }, + ], + }); + expect(body.attributes.summary).to.eql({ + failed: 0, + skipped: 0, + succeeded: 3, + total: 3, + }); + + // Check that the updated rule is returned with the response + // and field transformed on response + const ruleWithLegacyField = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationField.params.ruleId + ); + expect(ruleWithLegacyField.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + expect(ruleWithLegacyField.tags).to.eql(['reset-tag']); + + const ruleWithEmptyArray = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => + returnedRule.rule_id === ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId + ); + expect(ruleWithEmptyArray.investigation_fields).to.eql(undefined); + expect(ruleWithEmptyArray.tags).to.eql(['reset-tag']); + + const ruleWithIntendedType = body.attributes.results.updated.find( + (returnedRule: RuleResponse) => returnedRule.rule_id === 'rule-with-investigation-field' + ); + expect(ruleWithIntendedType.investigation_fields).to.eql({ field_names: ['host.name'] }); + expect(ruleWithIntendedType.tags).to.eql(['reset-tag']); + + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should not include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, ruleWithLegacyInvestigationField.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(['client.address', 'agent.name']); + + const { + hits: { + hits: [{ _source: ruleSO2 }], + }, + } = await getRuleSOById(es, ruleWithLegacyInvestigationFieldEmptyArray.id); + expect(ruleSO2?.alert?.params?.investigationFields).to.eql([]); + + const { + hits: { + hits: [{ _source: ruleSO3 }], + }, + } = await getRuleSOById(es, ruleWithIntendedType.id); + expect(ruleSO3?.alert?.params?.investigationFields).to.eql({ field_names: ['host.name'] }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts similarity index 96% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts index 982557130717e..aa07404205652 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_creation/create_rules_bulk.ts @@ -17,12 +17,11 @@ import { import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, - getRuleForSignalTesting, + getRuleForAlertTesting, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -30,22 +29,23 @@ import { removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, waitForRuleSuccess, -} from '../../utils'; -import { getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, -} from '../../utils/get_rule_actions'; -import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions'; + removeUUIDFromActions, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); const es = getService('es'); - describe('create_rules_bulk', () => { + // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already + // deprecated, it should cease being exposed in Serverless prior to GA, in which case this + // test would be run for ESS only. + describe('@ess @brokenInServerless @skipInQA create_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { await deleteAllRules(supertest, log); @@ -75,7 +75,7 @@ export default ({ getService }: FtrProviderContext): void => { }); beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -116,7 +116,7 @@ export default ({ getService }: FtrProviderContext): void => { */ it('should create a single rule with a rule_id and validate it ran successfully', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; const { body } = await supertest diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/ess.config.ts new file mode 100644 index 0000000000000..11f644695b9dc --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/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: 'Rule Management API Integration Tests - ESS - Rule Delete logic', + }, + }; +} diff --git a/x-pack/test/detection_engine_api_integration/utils/get_web_hook_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts similarity index 50% rename from x-pack/test/detection_engine_api_integration/utils/get_web_hook_action.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts index 27a93b396b93f..ed7c4e3d11a71 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_web_hook_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/configs/serverless.config.ts @@ -4,16 +4,11 @@ * 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 const getWebHookAction = () => ({ - actionTypeId: '.webhook', - config: { - method: 'post', - url: 'http://localhost', +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule Delete logic', }, - secrets: { - user: 'example', - password: 'example', - }, - name: 'Some connector', }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts new file mode 100644 index 0000000000000..1966ab101ab0c --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules.ts @@ -0,0 +1,126 @@ +/* + * 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 { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getSimpleRule, + getSimpleRuleOutput, + getSimpleRuleOutputWithoutRuleId, + getSimpleRuleWithoutRuleId, + removeServerGeneratedProperties, + removeServerGeneratedPropertiesIncludingRuleId, + updateUsername, +} from '../../utils'; + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + 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'); + + describe('@ess @serverless delete_rules', () => { + describe('deleting rules', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should delete a single rule with a rule_id', async () => { + await createRule(supertest, log, getSimpleRule('rule-1')); + + // delete the rule by its rule_id + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should delete a single rule using an auto generated rule_id', async () => { + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); + + // delete that rule by its auto-generated rule_id + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${bodyWithCreatedRule.rule_id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should delete a single rule using an auto generated id', async () => { + const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); + + // delete that rule by its auto-generated id + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?id=${bodyWithCreatedRule.id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should return an error if the id does not exist when trying to delete it', async () => { + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(404); + + expect(body).to.eql({ + message: 'id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" not found', + status_code: 404, + }); + }); + + it('should return an error if the rule_id does not exist when trying to delete it', async () => { + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=fake_id`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(404); + + expect(body).to.eql({ + message: 'rule_id: "fake_id" not found', + status_code: 404, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts similarity index 64% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts index dd476803a9f24..10d768152ddc3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk.ts @@ -8,37 +8,38 @@ import expect from '@kbn/expect'; import { Rule } from '@kbn/alerting-plugin/common'; import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; -import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_BULK_DELETE } from '@kbn/security-solution-plugin/common/constants'; import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { - createLegacyRuleAction, createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, + updateUsername, getSimpleRuleOutputWithoutRuleId, getSimpleRuleWithoutRuleId, - getSlackAction, - getWebHookAction, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, - getLegacyActionSO, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - - describe('delete_rules_bulk', () => { + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + + // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already + // deprecated, it should cease being exposed in Serverless prior to GA, in which case this + // test would be run for ESS only. + describe('@ess @brokenInServerless @skipInQA delete_rules_bulk', () => { describe('deprecations', () => { it('should return a warning header', async () => { await createRule(supertest, log, getSimpleRule()); @@ -58,7 +59,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('deleting rules bulk using DELETE', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -78,7 +79,9 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated rule_id', async () => { @@ -93,7 +96,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated id', async () => { @@ -108,7 +116,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { @@ -160,8 +173,13 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + const expectedRule = updateUsername( getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect([bodyToCompare, body[1]]).to.eql([ + expectedRule, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', error: { @@ -176,7 +194,7 @@ export default ({ getService }: FtrProviderContext): void => { // This is a repeat of the tests above but just using POST instead of DELETE describe('deleting rules bulk using POST', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -196,7 +214,8 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated rule_id', async () => { @@ -211,7 +230,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should delete a single rule using an auto generated id', async () => { @@ -226,7 +250,13 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); + + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); }); it('should return an error if the ruled_id does not exist when trying to delete a rule_id', async () => { @@ -278,8 +308,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + const expectedRule = updateUsername( getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + expect([bodyToCompare, body[1]]).to.eql([ + expectedRule, { id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', error: { @@ -289,165 +323,6 @@ export default ({ getService }: FtrProviderContext): void => { }, ]); }); - - /** - * @deprecated Once the legacy notification system is removed, remove this test too. - */ - it('should return the legacy action in the response body when it deletes a rule that has one', async () => { - // create an action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getSlackAction()) - .expect(200); - - // create a rule without actions - const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); - - // Add a legacy rule action to the body of the rule - await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); - - // delete the rule with the legacy action - const { body } = await supertest - .delete(DETECTION_ENGINE_RULES_BULK_DELETE) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send([{ id: createRuleBody.id }]) - .expect(200); - - // ensure we only get one body back - expect(body.length).to.eql(1); - - // ensure that its actions equal what we expect - expect(body[0].actions).to.eql([ - { - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - group: 'default', - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - }); - - /** - * @deprecated Once the legacy notification system is removed, remove this test too. - */ - it('should return 2 legacy actions in the response body when it deletes 2 rules', async () => { - // create two different actions - const { body: hookAction1 } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getSlackAction()) - .expect(200); - const { body: hookAction2 } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getSlackAction()) - .expect(200); - - // create 2 rules without actions - const createRuleBody1 = await createRule(supertest, log, getSimpleRule('rule-1')); - const createRuleBody2 = await createRule(supertest, log, getSimpleRule('rule-2')); - - // Add a legacy rule action to the body of the 2 rules - await createLegacyRuleAction(supertest, createRuleBody1.id, hookAction1.id); - await createLegacyRuleAction(supertest, createRuleBody2.id, hookAction2.id); - - // delete 2 rules where both have legacy actions - const { body } = await supertest - .delete(DETECTION_ENGINE_RULES_BULK_DELETE) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send([{ id: createRuleBody1.id }, { id: createRuleBody2.id }]) - .expect(200); - - // ensure we only get two bodies back - expect(body.length).to.eql(2); - - // ensure that its actions equal what we expect for both responses - expect(body[0].actions).to.eql([ - { - id: hookAction1.id, - action_type_id: hookAction1.actionTypeId, - group: 'default', - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - expect(body[1].actions).to.eql([ - { - id: hookAction2.id, - action_type_id: hookAction2.actionTypeId, - group: 'default', - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]); - }); - - /** - * @deprecated Once the legacy notification system is removed, remove this test too. - */ - it('should delete a legacy action when it deletes a rule that has one', async () => { - // create an action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - // create a rule without actions - const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); - - // Add a legacy rule action to the body of the rule - await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - createRuleBody.id - ); - - // bulk delete the rule - await supertest - .delete(DETECTION_ENGINE_RULES_BULK_DELETE) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send([{ id: createRuleBody.id }]) - .expect(200); - - // Test to ensure that we have exactly 0 legacy actions by querying the Alerting client REST API directly - // See: https://www.elastic.co/guide/en/kibana/current/find-rules-api.html - // Note: We specifically query for both the filter of type "siem.notifications" and the "has_reference" to keep it very specific - const { body: bodyAfterDelete } = await supertest - .get(`${BASE_ALERTING_API_PATH}/rules/_find`) - .query({ - page: 1, - per_page: 10, - filter: 'alert.attributes.alertTypeId:(siem.notifications)', - has_reference: JSON.stringify({ id: createRuleBody.id, type: 'alert' }), - }) - .set('kbn-xsrf', 'true') - .send(); - - // Expect that we have exactly 0 legacy rules after the deletion - expect(bodyAfterDelete.total).to.eql(0); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - }); }); describe('legacy investigation fields', () => { @@ -457,7 +332,7 @@ export default ({ getService }: FtrProviderContext): void => { beforeEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( supertest, getRuleSavedObjectWithLegacyInvestigationFields() diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts new file mode 100644 index 0000000000000..85a5814fdf732 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_bulk_legacy.ts @@ -0,0 +1,200 @@ +/* + * 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 { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '@kbn/security-solution-plugin/common/constants'; +import { + createLegacyRuleAction, + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getSimpleRule, + getSlackAction, + getWebHookAction, + getLegacyActionSO, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + describe('@ess delete_rules_bulk_legacy', () => { + describe('deleting rules bulk using POST', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + /** + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + it('should return the legacy action in the response body when it deletes a rule that has one', async () => { + // create an action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getSlackAction()) + .expect(200); + + // create a rule without actions + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); + + // Add a legacy rule action to the body of the rule + await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); + + // delete the rule with the legacy action + const { body } = await supertest + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send([{ id: createRuleBody.id }]) + .expect(200); + + // ensure we only get one body back + expect(body.length).to.eql(1); + + // ensure that its actions equal what we expect + expect(body[0].actions).to.eql([ + { + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + }); + + /** + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + it('should return 2 legacy actions in the response body when it deletes 2 rules', async () => { + // create two different actions + const { body: hookAction1 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getSlackAction()) + .expect(200); + const { body: hookAction2 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getSlackAction()) + .expect(200); + + // create 2 rules without actions + const createRuleBody1 = await createRule(supertest, log, getSimpleRule('rule-1')); + const createRuleBody2 = await createRule(supertest, log, getSimpleRule('rule-2')); + + // Add a legacy rule action to the body of the 2 rules + await createLegacyRuleAction(supertest, createRuleBody1.id, hookAction1.id); + await createLegacyRuleAction(supertest, createRuleBody2.id, hookAction2.id); + + // delete 2 rules where both have legacy actions + const { body } = await supertest + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send([{ id: createRuleBody1.id }, { id: createRuleBody2.id }]) + .expect(200); + + // ensure we only get two bodies back + expect(body.length).to.eql(2); + + // ensure that its actions equal what we expect for both responses + expect(body[0].actions).to.eql([ + { + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + expect(body[1].actions).to.eql([ + { + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + group: 'default', + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]); + }); + + /** + * @deprecated Once the legacy notification system is removed, remove this test too. + */ + it('should delete a legacy action when it deletes a rule that has one', async () => { + // create an action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // create a rule without actions + const createRuleBody = await createRule(supertest, log, getSimpleRule('rule-1')); + + // Add a legacy rule action to the body of the rule + await createLegacyRuleAction(supertest, createRuleBody.id, hookAction.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createRuleBody.id + ); + + // bulk delete the rule + await supertest + .delete(DETECTION_ENGINE_RULES_BULK_DELETE) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send([{ id: createRuleBody.id }]) + .expect(200); + + // Test to ensure that we have exactly 0 legacy actions by querying the Alerting client REST API directly + // See: https://www.elastic.co/guide/en/kibana/current/find-rules-api.html + // Note: We specifically query for both the filter of type "siem.notifications" and the "has_reference" to keep it very specific + const { body: bodyAfterDelete } = await supertest + .get(`${BASE_ALERTING_API_PATH}/rules/_find`) + .query({ + page: 1, + per_page: 10, + filter: 'alert.attributes.alertTypeId:(siem.notifications)', + has_reference: JSON.stringify({ id: createRuleBody.id, type: 'alert' }), + }) + .set('kbn-xsrf', 'true') + .send(); + + // Expect that we have exactly 0 legacy rules after the deletion + expect(bodyAfterDelete.total).to.eql(0); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_ess.ts new file mode 100644 index 0000000000000..48612358459cd --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_ess.ts @@ -0,0 +1,101 @@ +/* + * 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 { Rule } from '@kbn/alerting-plugin/common'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getSimpleRule, + removeServerGeneratedProperties, + createRuleThroughAlertingEndpoint, + getRuleSavedObjectWithLegacyInvestigationFields, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, +} from '../../utils'; + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + + describe('@ess delete_rules - ESS specific logic', () => { + describe('deleting rules', () => { + describe('legacy investigation fields', () => { + let ruleWithLegacyInvestigationField: Rule; + let ruleWithLegacyInvestigationFieldEmptyArray: Rule; + + beforeEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ); + ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ); + await createRule(supertest, log, { + ...getSimpleRule('rule-with-investigation-field'), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }); + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('deletes rule with investigation fields as array', async () => { + const { body } = await supertest + .delete( + `${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleWithLegacyInvestigationField.params.ruleId}` + ) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + }); + + it('deletes rule with investigation fields as empty array', async () => { + const { body } = await supertest + .delete( + `${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId}` + ) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql(undefined); + }); + + it('deletes rule with investigation fields as intended object type', async () => { + const { body } = await supertest + .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-with-investigation-field`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql({ field_names: ['host.name'] }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts similarity index 50% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts index 655299c0808a6..9db8143c6ad3c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/delete_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/delete_rules_legacy.ts @@ -6,41 +6,31 @@ */ import expect from '@kbn/expect'; -import { Rule } from '@kbn/alerting-plugin/common'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createLegacyRuleAction, createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, - getSimpleRuleOutput, - getSimpleRuleOutputWithoutRuleId, - getSimpleRuleWithoutRuleId, getSlackAction, getWebHookAction, - removeServerGeneratedProperties, - removeServerGeneratedPropertiesIncludingRuleId, getLegacyActionSO, - createRuleThroughAlertingEndpoint, - getRuleSavedObjectWithLegacyInvestigationFields, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../utils'; -// eslint-disable-next-line import/no-default-export +import { FtrProviderContext } from '../../../../ftr_provider_context'; + export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - describe('delete_rules', () => { + describe('@ess delete_rules_legacy', () => { describe('deleting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -48,74 +38,6 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('should delete a single rule with a rule_id', async () => { - await createRule(supertest, log, getSimpleRule('rule-1')); - - // delete the rule by its rule_id - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); - }); - - it('should delete a single rule using an auto generated rule_id', async () => { - const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); - - // delete that rule by its auto-generated rule_id - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${bodyWithCreatedRule.rule_id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); - }); - - it('should delete a single rule using an auto generated id', async () => { - const bodyWithCreatedRule = await createRule(supertest, log, getSimpleRule()); - - // delete that rule by its auto-generated id - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?id=${bodyWithCreatedRule.id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); - }); - - it('should return an error if the id does not exist when trying to delete it', async () => { - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(404); - - expect(body).to.eql({ - message: 'id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" not found', - status_code: 404, - }); - }); - - it('should return an error if the rule_id does not exist when trying to delete it', async () => { - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=fake_id`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(404); - - expect(body).to.eql({ - message: 'rule_id: "fake_id" not found', - status_code: 404, - }); - }); - /** * @deprecated Once the legacy notification system is removed, remove this test too. */ @@ -243,72 +165,5 @@ export default ({ getService }: FtrProviderContext): void => { expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); }); }); - - describe('legacy investigation fields', () => { - let ruleWithLegacyInvestigationField: Rule; - let ruleWithLegacyInvestigationFieldEmptyArray: Rule; - - beforeEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); - ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFields() - ); - ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() - ); - await createRule(supertest, log, { - ...getSimpleRule('rule-with-investigation-field'), - name: 'Test investigation fields object', - investigation_fields: { field_names: ['host.name'] }, - }); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('deletes rule with investigation fields as array', async () => { - const { body } = await supertest - .delete( - `${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleWithLegacyInvestigationField.params.ruleId}` - ) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - }); - - it('deletes rule with investigation fields as empty array', async () => { - const { body } = await supertest - .delete( - `${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId}` - ) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql(undefined); - }); - - it('deletes rule with investigation fields as intended object type', async () => { - const { body } = await supertest - .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-with-investigation-field`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql({ field_names: ['host.name'] }); - }); - }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/index.ts new file mode 100644 index 0000000000000..559ddcfefd23e --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_delete/index.ts @@ -0,0 +1,18 @@ +/* + * 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('Rule Delete API', function () { + loadTestFile(require.resolve('./delete_rules')); + loadTestFile(require.resolve('./delete_rules_ess')); + loadTestFile(require.resolve('./delete_rules_legacy')); + loadTestFile(require.resolve('./delete_rules_bulk')); + loadTestFile(require.resolve('./delete_rules_bulk_legacy')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/runtime.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/runtime.ts index 030f2eafd6a1e..7d9b772d1f5c5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/runtime.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_execution_logic/runtime.ts @@ -31,7 +31,6 @@ export default ({ getService }: FtrProviderContext) => { hostname: string; } - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/138923 describe('@ess @serverless Tests involving runtime fields of source indexes and the alerts index', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/security_solution/runtime'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/ess.config.ts new file mode 100644 index 0000000000000..0221afa650a09 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/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: 'Rule Management API Integration Tests - ESS - Rule Import and Export logic', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.ts new file mode 100644 index 0000000000000..5be8cda08a16d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/configs/serverless.config.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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule Import and Export logic', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts new file mode 100644 index 0000000000000..bde3148c84320 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules.ts @@ -0,0 +1,467 @@ +/* + * 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 'expect'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { + binaryToString, + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getSimpleRule, + getSimpleRuleOutput, + getWebHookAction, + removeServerGeneratedProperties, + waitForRulePartialFailure, + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + 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'); + + describe('@ess @brokenInServerless @skipInQA export_rules', () => { + describe('exporting rules', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should set the response content types to be expected', async () => { + await createRule(supertest, log, getSimpleRule()); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .expect('Content-Type', 'application/ndjson') + .expect('Content-Disposition', 'attachment; filename="export.ndjson"'); + }); + + it('should validate exported rule schema when its exported by its rule_id', async () => { + const ruleId = 'rule-1'; + + await createRule(supertest, log, getSimpleRule(ruleId, true)); + + await waitForRulePartialFailure({ + supertest, + log, + ruleId, + }); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + objects: [{ rule_id: 'rule-1' }], + }) + .expect(200) + .parse(binaryToString); + + const exportedRule = JSON.parse(body.toString().split(/\n/)[0]); + + expectToMatchRuleSchema(exportedRule); + }); + + it('should validate all exported rules schema', async () => { + const ruleId1 = 'rule-1'; + const ruleId2 = 'rule-2'; + + await createRule(supertest, log, getSimpleRule(ruleId1, true)); + await createRule(supertest, log, getSimpleRule(ruleId2, true)); + + await waitForRulePartialFailure({ + supertest, + log, + ruleId: ruleId1, + }); + await waitForRulePartialFailure({ + supertest, + log, + ruleId: ruleId2, + }); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const exportedRule1 = JSON.parse(body.toString().split(/\n/)[1]); + const exportedRule2 = JSON.parse(body.toString().split(/\n/)[0]); + + expectToMatchRuleSchema(exportedRule1); + expectToMatchRuleSchema(exportedRule2); + }); + + it('should export a exported count with a single rule_id', async () => { + await createRule(supertest, log, getSimpleRule()); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const bodySplitAndParsed = JSON.parse(body.toString().split(/\n/)[1]); + + expect(bodySplitAndParsed).toEqual({ + exported_exception_list_count: 0, + exported_exception_list_item_count: 0, + exported_count: 1, + exported_rules_count: 1, + missing_exception_list_item_count: 0, + missing_exception_list_items: [], + missing_exception_lists: [], + missing_exception_lists_count: 0, + missing_rules: [], + missing_rules_count: 0, + excluded_action_connection_count: 0, + excluded_action_connections: [], + exported_action_connector_count: 0, + missing_action_connection_count: 0, + missing_action_connections: [], + }); + }); + + it('should export exactly two rules given two rules', async () => { + await createRule(supertest, log, getSimpleRule('rule-1')); + await createRule(supertest, log, getSimpleRule('rule-2')); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + const secondRule = removeServerGeneratedProperties(secondRuleParsed); + const expectedRule1 = updateUsername( + getSimpleRuleOutput(firstRule.rule_id), + ELASTICSEARCH_USERNAME + ); + const expectedRule2 = updateUsername( + getSimpleRuleOutput(secondRule.rule_id), + ELASTICSEARCH_USERNAME + ); + + expect(firstRule).toEqual(expectedRule1); + expect(secondRule).toEqual(expectedRule2); + }); + + it('should export multiple actions attached to 1 rule', async () => { + // 1st action + const { body: hookAction1 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + // 2nd action + const { body: hookAction2 } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action1 = { + group: 'default', + id: hookAction1.id, + action_type_id: hookAction1.actionTypeId, + params: {}, + }; + const action2 = { + group: 'default', + id: hookAction2.id, + action_type_id: hookAction2.actionTypeId, + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action1, action2], + }; + + await createRule(supertest, log, rule1); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + const expectedRule = updateUsername(getSimpleRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); + + const outputRule1: ReturnType = { + ...expectedRule, + actions: [ + { + ...action1, + uuid: firstRule.actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + { + ...action2, + uuid: firstRule.actions[1].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ], + }; + expect(firstRule).toEqual(outputRule1); + }); + + it('should export actions attached to 2 rules', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + + const rule2: ReturnType = { + ...getSimpleRule('rule-2'), + actions: [action], + }; + + await createRule(supertest, log, rule1); + await createRule(supertest, log, rule2); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); + const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); + const firstRule = removeServerGeneratedProperties(firstRuleParsed); + const secondRule = removeServerGeneratedProperties(secondRuleParsed); + const expectedRule2 = updateUsername(getSimpleRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); + + const outputRule1: ReturnType = { + ...expectedRule2, + actions: [ + { + ...action, + uuid: firstRule.actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ], + }; + const expectedRule1 = updateUsername(getSimpleRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); + + const outputRule2: ReturnType = { + ...expectedRule1, + actions: [ + { + ...action, + uuid: secondRule.actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ], + }; + expect(firstRule).toEqual(outputRule1); + expect(secondRule).toEqual(outputRule2); + }); + + it('should export actions connectors with the rule', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + + await createRule(supertest, log, rule1); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const connectorsObjectParsed = JSON.parse(body.toString().split(/\n/)[1]); + const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[2]); + + expect(connectorsObjectParsed).toEqual( + expect.objectContaining({ + attributes: { + actionTypeId: '.webhook', + config: { + hasAuth: true, + headers: null, + method: 'post', + url: 'http://localhost', + }, + isMissingSecrets: true, + name: 'Some connector', + secrets: {}, + }, + references: [], + type: 'action', + }) + ); + expect(exportDetailsParsed).toEqual({ + exported_exception_list_count: 0, + exported_exception_list_item_count: 0, + exported_count: 2, + exported_rules_count: 1, + missing_exception_list_item_count: 0, + missing_exception_list_items: [], + missing_exception_lists: [], + missing_exception_lists_count: 0, + missing_rules: [], + missing_rules_count: 0, + excluded_action_connection_count: 0, + excluded_action_connections: [], + exported_action_connector_count: 1, + missing_action_connection_count: 0, + missing_action_connections: [], + }); + }); + it('should export rule without the action connector if it is Preconfigured Connector', async () => { + const action = { + group: 'default', + id: 'my-test-email', + action_type_id: '.email', + params: {}, + }; + + const rule1: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + + await createRule(supertest, log, rule1); + + const { body } = await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_export`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send() + .expect(200) + .parse(binaryToString); + + const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[1]); + + expect(exportDetailsParsed).toEqual({ + exported_exception_list_count: 0, + exported_exception_list_item_count: 0, + exported_count: 1, + exported_rules_count: 1, + missing_exception_list_item_count: 0, + missing_exception_list_items: [], + missing_exception_lists: [], + missing_exception_lists_count: 0, + missing_rules: [], + missing_rules_count: 0, + excluded_action_connection_count: 0, + excluded_action_connections: [], + exported_action_connector_count: 0, + missing_action_connection_count: 0, + missing_action_connections: [], + }); + }); + }); + }); +}; + +function expectToMatchRuleSchema(obj: RuleResponse): void { + expect(obj.throttle).toBeUndefined(); + expect(obj).toEqual({ + id: expect.any(String), + rule_id: expect.any(String), + enabled: expect.any(Boolean), + immutable: false, + updated_at: expect.any(String), + updated_by: expect.any(String), + created_at: expect.any(String), + created_by: expect.any(String), + name: expect.any(String), + tags: expect.arrayContaining([]), + interval: expect.any(String), + description: expect.any(String), + risk_score: expect.any(Number), + severity: expect.any(String), + output_index: expect.any(String), + author: expect.arrayContaining([]), + false_positives: expect.arrayContaining([]), + from: expect.any(String), + max_signals: expect.any(Number), + revision: expect.any(Number), + risk_score_mapping: expect.arrayContaining([]), + severity_mapping: expect.arrayContaining([]), + threat: expect.arrayContaining([]), + to: expect.any(String), + references: expect.arrayContaining([]), + version: expect.any(Number), + exceptions_list: expect.arrayContaining([]), + related_integrations: expect.arrayContaining([]), + required_fields: expect.arrayContaining([]), + setup: expect.any(String), + type: expect.any(String), + language: expect.any(String), + index: expect.arrayContaining([]), + query: expect.any(String), + actions: expect.arrayContaining([]), + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts similarity index 55% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts index d03cb681dd62c..0a58efd57359f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/export_rules_ess.ts @@ -14,35 +14,36 @@ import { DETECTION_ENGINE_RULES_URL, UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '@kbn/security-solution-plugin/common/constants'; -import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, getWebHookAction, removeServerGeneratedProperties, - waitForRulePartialFailure, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, getRuleSOById, + updateUsername, createRuleThroughAlertingEndpoint, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); 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'); - describe('export_rules', () => { + describe('@ess export_rules - ESS specific logic', () => { describe('exporting rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -50,375 +51,6 @@ export default ({ getService }: FtrProviderContext): void => { await deleteAllRules(supertest, log); }); - it('should set the response content types to be expected', async () => { - await createRule(supertest, log, getSimpleRule()); - - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .expect('Content-Type', 'application/ndjson') - .expect('Content-Disposition', 'attachment; filename="export.ndjson"'); - }); - - it('should validate exported rule schema when its exported by its rule_id', async () => { - const ruleId = 'rule-1'; - - await createRule(supertest, log, getSimpleRule(ruleId, true)); - - await waitForRulePartialFailure({ - supertest, - log, - ruleId, - }); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ - objects: [{ rule_id: 'rule-1' }], - }) - .expect(200) - .parse(binaryToString); - - const exportedRule = JSON.parse(body.toString().split(/\n/)[0]); - - expectToMatchRuleSchema(exportedRule); - }); - - it('should validate all exported rules schema', async () => { - const ruleId1 = 'rule-1'; - const ruleId2 = 'rule-2'; - - await createRule(supertest, log, getSimpleRule(ruleId1, true)); - await createRule(supertest, log, getSimpleRule(ruleId2, true)); - - await waitForRulePartialFailure({ - supertest, - log, - ruleId: ruleId1, - }); - await waitForRulePartialFailure({ - supertest, - log, - ruleId: ruleId2, - }); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const exportedRule1 = JSON.parse(body.toString().split(/\n/)[1]); - const exportedRule2 = JSON.parse(body.toString().split(/\n/)[0]); - - expectToMatchRuleSchema(exportedRule1); - expectToMatchRuleSchema(exportedRule2); - }); - - it('should export a exported count with a single rule_id', async () => { - await createRule(supertest, log, getSimpleRule()); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const bodySplitAndParsed = JSON.parse(body.toString().split(/\n/)[1]); - - expect(bodySplitAndParsed).toEqual({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, - exported_count: 1, - exported_rules_count: 1, - missing_exception_list_item_count: 0, - missing_exception_list_items: [], - missing_exception_lists: [], - missing_exception_lists_count: 0, - missing_rules: [], - missing_rules_count: 0, - excluded_action_connection_count: 0, - excluded_action_connections: [], - exported_action_connector_count: 0, - missing_action_connection_count: 0, - missing_action_connections: [], - }); - }); - - it('should export exactly two rules given two rules', async () => { - await createRule(supertest, log, getSimpleRule('rule-1')); - await createRule(supertest, log, getSimpleRule('rule-2')); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); - const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); - const firstRule = removeServerGeneratedProperties(firstRuleParsed); - const secondRule = removeServerGeneratedProperties(secondRuleParsed); - - expect([firstRule, secondRule]).toEqual([ - getSimpleRuleOutput('rule-2'), - getSimpleRuleOutput('rule-1'), - ]); - }); - - it('should export multiple actions attached to 1 rule', async () => { - // 1st action - const { body: hookAction1 } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - // 2nd action - const { body: hookAction2 } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action1 = { - group: 'default', - id: hookAction1.id, - action_type_id: hookAction1.actionTypeId, - params: {}, - }; - const action2 = { - group: 'default', - id: hookAction2.id, - action_type_id: hookAction2.actionTypeId, - params: {}, - }; - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action1, action2], - }; - - await createRule(supertest, log, rule1); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); - const firstRule = removeServerGeneratedProperties(firstRuleParsed); - - const outputRule1: ReturnType = { - ...getSimpleRuleOutput('rule-1'), - actions: [ - { - ...action1, - uuid: firstRule.actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - { - ...action2, - uuid: firstRule.actions[1].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ], - }; - expect(firstRule).toEqual(outputRule1); - }); - - it('should export actions attached to 2 rules', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action], - }; - - const rule2: ReturnType = { - ...getSimpleRule('rule-2'), - actions: [action], - }; - - await createRule(supertest, log, rule1); - await createRule(supertest, log, rule2); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]); - const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]); - const firstRule = removeServerGeneratedProperties(firstRuleParsed); - const secondRule = removeServerGeneratedProperties(secondRuleParsed); - - const outputRule1: ReturnType = { - ...getSimpleRuleOutput('rule-2'), - actions: [ - { - ...action, - uuid: firstRule.actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ], - }; - const outputRule2: ReturnType = { - ...getSimpleRuleOutput('rule-1'), - actions: [ - { - ...action, - uuid: secondRule.actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ], - }; - expect(firstRule).toEqual(outputRule1); - expect(secondRule).toEqual(outputRule2); - }); - - it('should export actions connectors with the rule', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action], - }; - - await createRule(supertest, log, rule1); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const connectorsObjectParsed = JSON.parse(body.toString().split(/\n/)[1]); - const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[2]); - - expect(connectorsObjectParsed).toEqual( - expect.objectContaining({ - attributes: { - actionTypeId: '.webhook', - config: { - hasAuth: true, - headers: null, - method: 'post', - url: 'http://localhost', - }, - isMissingSecrets: true, - name: 'Some connector', - secrets: {}, - }, - references: [], - type: 'action', - }) - ); - expect(exportDetailsParsed).toEqual({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, - exported_count: 2, - exported_rules_count: 1, - missing_exception_list_item_count: 0, - missing_exception_list_items: [], - missing_exception_lists: [], - missing_exception_lists_count: 0, - missing_rules: [], - missing_rules_count: 0, - excluded_action_connection_count: 0, - excluded_action_connections: [], - exported_action_connector_count: 1, - missing_action_connection_count: 0, - missing_action_connections: [], - }); - }); - it('should export rule without the action connector if it is Preconfigured Connector', async () => { - const action = { - group: 'default', - id: 'my-test-email', - action_type_id: '.email', - params: {}, - }; - - const rule1: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action], - }; - - await createRule(supertest, log, rule1); - - const { body } = await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_export`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200) - .parse(binaryToString); - - const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[1]); - - expect(exportDetailsParsed).toEqual({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, - exported_count: 1, - exported_rules_count: 1, - missing_exception_list_item_count: 0, - missing_exception_list_items: [], - missing_exception_lists: [], - missing_exception_lists_count: 0, - missing_rules: [], - missing_rules_count: 0, - excluded_action_connection_count: 0, - excluded_action_connections: [], - exported_action_connector_count: 0, - missing_action_connection_count: 0, - missing_action_connections: [], - }); - }); - /** * Tests the legacy actions to ensure we can export legacy notifications * @deprecated Once the legacy notification system is removed, remove this test too. @@ -465,9 +97,13 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200) .parse(binaryToString); + const expectedRule1 = updateUsername( + getSimpleRuleOutput('rule-1'), + ELASTICSEARCH_USERNAME + ); const outputRule1: ReturnType = { - ...getSimpleRuleOutput('rule-1'), + ...expectedRule1, actions: [ { group: 'default', @@ -544,9 +180,13 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200) .parse(binaryToString); + const expectedRule1 = updateUsername( + getSimpleRuleOutput('rule-1'), + ELASTICSEARCH_USERNAME + ); const outputRule1: ReturnType = { - ...getSimpleRuleOutput('rule-1'), + ...expectedRule1, actions: [ { group: 'default', @@ -666,8 +306,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200) .parse(binaryToString); + const expectedRule1 = updateUsername( + getSimpleRuleOutput('rule-1'), + ELASTICSEARCH_USERNAME + ); const outputRule1: ReturnType = { - ...getSimpleRuleOutput('rule-1'), + ...expectedRule1, actions: [ { group: 'default', @@ -692,8 +336,12 @@ export default ({ getService }: FtrProviderContext): void => { ], }; + const expectedRule2 = updateUsername( + getSimpleRuleOutput('rule-2'), + ELASTICSEARCH_USERNAME + ); const outputRule2: ReturnType = { - ...getSimpleRuleOutput('rule-2'), + ...expectedRule2, actions: [ { group: 'default', @@ -846,44 +494,3 @@ export default ({ getService }: FtrProviderContext): void => { }); }); }; - -function expectToMatchRuleSchema(obj: RuleResponse): void { - expect(obj.throttle).toBeUndefined(); - expect(obj).toEqual({ - id: expect.any(String), - rule_id: expect.any(String), - enabled: expect.any(Boolean), - immutable: false, - updated_at: expect.any(String), - updated_by: expect.any(String), - created_at: expect.any(String), - created_by: expect.any(String), - name: expect.any(String), - tags: expect.arrayContaining([]), - interval: expect.any(String), - description: expect.any(String), - risk_score: expect.any(Number), - severity: expect.any(String), - output_index: expect.any(String), - author: expect.arrayContaining([]), - false_positives: expect.arrayContaining([]), - from: expect.any(String), - max_signals: expect.any(Number), - revision: expect.any(Number), - risk_score_mapping: expect.arrayContaining([]), - severity_mapping: expect.arrayContaining([]), - threat: expect.arrayContaining([]), - to: expect.any(String), - references: expect.arrayContaining([]), - version: expect.any(Number), - exceptions_list: expect.arrayContaining([]), - related_integrations: expect.arrayContaining([]), - required_fields: expect.arrayContaining([]), - setup: expect.any(String), - type: expect.any(String), - language: expect.any(String), - index: expect.arrayContaining([]), - query: expect.any(String), - actions: expect.arrayContaining([]), - }); -} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_export_rules.ts similarity index 97% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_export_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_export_rules.ts index aee267db951d5..d40e92fca197e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_export_rules.ts @@ -18,31 +18,34 @@ import { } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, - deleteAllExceptions, } from '../../utils'; -import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../common/services/security_solution'; +import { deleteAllExceptions } from '../../../lists_and_exception_lists/utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; // This test was meant to be more full flow, ensuring that // exported rules are able to be reimported as opposed to // testing the import/export functionality separately -// eslint-disable-next-line import/no-default-export + export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const es = getService('es'); - describe('import_export_rules_flow', () => { + describe('@ess import_export_rules_flow', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await createUserAndRole(getService, ROLES.soc_manager); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules.ts similarity index 82% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules.ts index 6517c46bddcaf..5999962cf33db 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules.ts @@ -8,7 +8,6 @@ import expect from '@kbn/expect'; import { - InvestigationFields, QueryRuleCreateProps, RuleCreateProps, } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -21,26 +20,19 @@ import { getImportExceptionsListSchemaMock, getImportExceptionsListItemNewerVersionSchemaMock, } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; -import { ROLES } from '@kbn/security-solution-plugin/common/test'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, getSimpleRule, getSimpleRuleAsNdjson, - getRulesAsNdjson, getSimpleRuleOutput, - getThresholdRuleForSignalTesting, + getThresholdRuleForAlertTesting, getWebHookAction, + getRulesAsNdjson, removeServerGeneratedProperties, ruleToNdjson, - createLegacyRuleAction, - getLegacyActionSO, - createRule, - getRule, - getRuleSOById, - deleteAllExceptions, } from '../../utils'; -import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution'; +import { deleteAllExceptions } from '../../../lists_and_exception_lists/utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; const getImportRuleBuffer = (connectorId: string) => { const rule1 = { @@ -193,215 +185,19 @@ export const getSimpleRuleAsNdjsonWithLegacyInvestigationField = ( return Buffer.from(stringOfRules.join('\n')); }; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const esArchiver = getService('esArchiver'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('es'); - describe('import_rules', () => { + describe('@ess @brokenInServerless @skipInQA import_rules', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); - describe('importing rules with different roles', () => { - before(async () => { - await createUserAndRole(getService, ROLES.hunter_no_actions); - await createUserAndRole(getService, ROLES.hunter); - }); - after(async () => { - await deleteUserAndRole(getService, ROLES.hunter_no_actions); - await deleteUserAndRole(getService, ROLES.hunter); - }); - it('should successfully import rules without actions when user has no actions privileges', async () => { - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter_no_actions, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') - .expect(200); - - expect(body).to.eql({ - errors: [], - success: true, - success_count: 1, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: true, - action_connectors_success_count: 0, - action_connectors_errors: [], - action_connectors_warnings: [], - }); - }); - - it('should not import rules with actions when user has "read" actions privileges', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const ruleWithConnector = { - id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', - type: 'action', - updated_at: '2023-01-25T14:35:52.852Z', - created_at: '2023-01-25T14:35:52.852Z', - version: 'WzUxNTksMV0=', - attributes: { - actionTypeId: '.webhook', - name: 'webhook', - isMissingSecrets: true, - config: {}, - secrets: {}, - }, - references: [], - migrationVersion: { action: '8.3.0' }, - coreMigrationVersion: '8.7.0', - }; - - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - Buffer.from(toNdJsonString([simpleRule, ruleWithConnector])), - 'rules.ndjson' - ) - .expect(200); - - expect(body).to.eql({ - errors: [ - { - error: { - message: - 'You may not have actions privileges required to import rules with actions: Unable to bulk_create action', - status_code: 403, - }, - rule_id: '(unknown id)', - }, - ], - success: false, - success_count: 0, - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: false, - action_connectors_success_count: 0, - action_connectors_errors: [ - { - error: { - message: - 'You may not have actions privileges required to import rules with actions: Unable to bulk_create action', - status_code: 403, - }, - rule_id: '(unknown id)', - }, - ], - action_connectors_warnings: [], - }); - }); - it('should not import rules with actions when a user has no actions privileges', async () => { - // create a new action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - const simpleRule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [ - { - group: 'default', - id: 'cabc78e0-9031-11ed-b076-53cc4d57axy1', - action_type_id: hookAction.actionTypeId, - params: {}, - }, - ], - }; - const ruleWithConnector = { - id: 'cabc78e0-9031-11ed-b076-53cc4d57axy1', - type: 'action', - updated_at: '2023-01-25T14:35:52.852Z', - created_at: '2023-01-25T14:35:52.852Z', - version: 'WzUxNTksMV0=', - attributes: { - actionTypeId: '.webhook', - name: 'webhook', - isMissingSecrets: true, - config: {}, - secrets: {}, - }, - references: [], - migrationVersion: { action: '8.3.0' }, - coreMigrationVersion: '8.7.0', - }; - - const { body } = await supertestWithoutAuth - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .auth(ROLES.hunter_no_actions, 'changeme') - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - Buffer.from(toNdJsonString([simpleRule, ruleWithConnector])), - 'rules.ndjson' - ) - .expect(200); - expect(body).to.eql({ - success: false, - success_count: 0, - errors: [ - { - error: { - message: - 'You may not have actions privileges required to import rules with actions: Unauthorized to get actions', - status_code: 403, - }, - rule_id: '(unknown id)', - }, - ], - rules_count: 1, - exceptions_errors: [], - exceptions_success: true, - exceptions_success_count: 0, - action_connectors_success: false, - action_connectors_success_count: 0, - action_connectors_errors: [ - { - error: { - message: - 'You may not have actions privileges required to import rules with actions: Unauthorized to get actions', - status_code: 403, - }, - rule_id: '(unknown id)', - }, - ], - action_connectors_warnings: [], - }); - }); - }); describe('threshold validation', () => { it('should result in partial success if no threshold-specific fields are provided', async () => { - const { threshold, ...rule } = getThresholdRuleForSignalTesting(['*']); + const { threshold, ...rule } = getThresholdRuleForAlertTesting(['*']); const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') @@ -416,7 +212,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should result in partial success if more than 3 threshold fields', async () => { - const baseRule = getThresholdRuleForSignalTesting(['*']); + const baseRule = getThresholdRuleForAlertTesting(['*']); const rule = { ...baseRule, threshold: { @@ -441,7 +237,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should result in partial success if threshold value is less than 1', async () => { - const baseRule = getThresholdRuleForSignalTesting(['*']); + const baseRule = getThresholdRuleForAlertTesting(['*']); const rule = { ...baseRule, threshold: { @@ -466,7 +262,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should result in 400 error if cardinality is also an agg field', async () => { - const baseRule = getThresholdRuleForSignalTesting(['*']); + const baseRule = getThresholdRuleForAlertTesting(['*']); const rule = { ...baseRule, threshold: { @@ -813,46 +609,6 @@ export default ({ getService }: FtrProviderContext): void => { expect(bodyToCompare).to.eql(ruleOutput); }); - it('should migrate legacy actions in existing rule if overwrite is set to true', async () => { - const simpleRule = getSimpleRule('rule-1'); - - const [connector, createdRule] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, simpleRule), - ]); - await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - createdRule.id - ); - - simpleRule.name = 'some other name'; - const ndjson = ruleToNdjson(simpleRule); - - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach('file', ndjson, 'rules.ndjson') - .expect(200); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - }); - it('should report a conflict if there is an attempt to import a rule with a rule_id that already exists, but still have some successes with other rules', async () => { await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) @@ -1952,108 +1708,5 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); }); }); - - describe('legacy investigation fields', () => { - it('imports rule with investigation fields as array', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { - // mimicking what an 8.10 rule would look like - // we don't want to support this type in our APIs any longer, but do - // want to allow users to import rules from 8.10 - investigation_fields: ['foo', 'bar'] as unknown as InvestigationFields, - }), - 'rules.ndjson' - ) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); - - const rule = await getRule(supertest, log, 'rule-1'); - expect(rule.investigation_fields).to.eql({ field_names: ['foo', 'bar'] }); - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should - * include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo', 'bar'] }); - }); - - it('imports rule with investigation fields as empty array', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { - // mimicking what an 8.10 rule would look like - // we don't want to support this type in our APIs any longer, but do - // want to allow users to import rules from 8.10 - investigation_fields: [] as unknown as InvestigationFields, - }), - 'rules.ndjson' - ) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); - - const rule = await getRule(supertest, log, 'rule-1'); - expect(rule.investigation_fields).to.eql(undefined); - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should - * include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(undefined); - }); - - it('imports rule with investigation fields as intended object type', async () => { - await supertest - .post(`${DETECTION_ENGINE_RULES_URL}/_import`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .attach( - 'file', - getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { - investigation_fields: { - field_names: ['foo'], - }, - }), - 'rules.ndjson' - ) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); - - const rule = await getRule(supertest, log, 'rule-1'); - expect(rule.investigation_fields).to.eql({ field_names: ['foo'] }); - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should - * include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, rule.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo'] }); - }); - }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts new file mode 100644 index 0000000000000..aaeb01904e066 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/import_rules_ess.ts @@ -0,0 +1,393 @@ +/* + * 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 { + InvestigationFields, + QueryRuleCreateProps, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { toNdJsonString } from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock'; +import { + deleteAllRules, + getSimpleRule, + ruleToNdjson, + createLegacyRuleAction, + getLegacyActionSO, + createRule, + fetchRule, + getRuleSOById, + getWebHookAction, + getSimpleRuleAsNdjson, +} from '../../utils'; +import { + createUserAndRole, + deleteUserAndRole, +} from '../../../../../common/services/security_solution'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export const getSimpleRuleAsNdjsonWithLegacyInvestigationField = ( + ruleIds: string[], + enabled = false, + overwrites: Partial +): Buffer => { + const stringOfRules = ruleIds.map((ruleId) => { + const simpleRule = { ...getSimpleRule(ruleId, enabled), ...overwrites }; + return JSON.stringify(simpleRule); + }); + return Buffer.from(stringOfRules.join('\n')); +}; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('@ess import_rules - ESS specific logic', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('should migrate legacy actions in existing rule if overwrite is set to true', async () => { + const simpleRule = getSimpleRule('rule-1'); + + const [connector, createdRule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, simpleRule), + ]); + await createLegacyRuleAction(supertest, createdRule.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(createdRule.id); + + simpleRule.name = 'some other name'; + const ndjson = ruleToNdjson(simpleRule); + + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', ndjson, 'rules.ndjson') + .expect(200); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + + describe('importing rules with different roles', () => { + before(async () => { + await createUserAndRole(getService, ROLES.hunter_no_actions); + await createUserAndRole(getService, ROLES.hunter); + }); + after(async () => { + await deleteUserAndRole(getService, ROLES.hunter_no_actions); + await deleteUserAndRole(getService, ROLES.hunter); + }); + it('should successfully import rules without actions when user has no actions privileges', async () => { + const { body } = await supertestWithoutAuth + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .auth(ROLES.hunter_no_actions, 'changeme') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach('file', getSimpleRuleAsNdjson(['rule-1']), 'rules.ndjson') + .expect(200); + + expect(body).to.eql({ + errors: [], + success: true, + success_count: 1, + rules_count: 1, + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: true, + action_connectors_success_count: 0, + action_connectors_errors: [], + action_connectors_warnings: [], + }); + }); + + it('should not import rules with actions when user has "read" actions privileges', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + const simpleRule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + const ruleWithConnector = { + id: 'cabc78e0-9031-11ed-b076-53cc4d57aaf1', + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.webhook', + name: 'webhook', + isMissingSecrets: true, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }; + + const { body } = await supertestWithoutAuth + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .auth(ROLES.hunter, 'changeme') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach( + 'file', + Buffer.from(toNdJsonString([simpleRule, ruleWithConnector])), + 'rules.ndjson' + ) + .expect(200); + + expect(body).to.eql({ + errors: [ + { + error: { + message: + 'You may not have actions privileges required to import rules with actions: Unable to bulk_create action', + status_code: 403, + }, + rule_id: '(unknown id)', + }, + ], + success: false, + success_count: 0, + rules_count: 1, + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: false, + action_connectors_success_count: 0, + action_connectors_errors: [ + { + error: { + message: + 'You may not have actions privileges required to import rules with actions: Unable to bulk_create action', + status_code: 403, + }, + rule_id: '(unknown id)', + }, + ], + action_connectors_warnings: [], + }); + }); + it('should not import rules with actions when a user has no actions privileges', async () => { + // create a new action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + const simpleRule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [ + { + group: 'default', + id: 'cabc78e0-9031-11ed-b076-53cc4d57axy1', + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }; + const ruleWithConnector = { + id: 'cabc78e0-9031-11ed-b076-53cc4d57axy1', + type: 'action', + updated_at: '2023-01-25T14:35:52.852Z', + created_at: '2023-01-25T14:35:52.852Z', + version: 'WzUxNTksMV0=', + attributes: { + actionTypeId: '.webhook', + name: 'webhook', + isMissingSecrets: true, + config: {}, + secrets: {}, + }, + references: [], + migrationVersion: { action: '8.3.0' }, + coreMigrationVersion: '8.7.0', + }; + + const { body } = await supertestWithoutAuth + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .auth(ROLES.hunter_no_actions, 'changeme') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach( + 'file', + Buffer.from(toNdJsonString([simpleRule, ruleWithConnector])), + 'rules.ndjson' + ) + .expect(200); + expect(body).to.eql({ + success: false, + success_count: 0, + errors: [ + { + error: { + message: + 'You may not have actions privileges required to import rules with actions: Unauthorized to get actions', + status_code: 403, + }, + rule_id: '(unknown id)', + }, + ], + rules_count: 1, + exceptions_errors: [], + exceptions_success: true, + exceptions_success_count: 0, + action_connectors_success: false, + action_connectors_success_count: 0, + action_connectors_errors: [ + { + error: { + message: + 'You may not have actions privileges required to import rules with actions: Unauthorized to get actions', + status_code: 403, + }, + rule_id: '(unknown id)', + }, + ], + action_connectors_warnings: [], + }); + }); + }); + + describe('legacy investigation fields', () => { + it('imports rule with investigation fields as array', async () => { + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach( + 'file', + getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { + // mimicking what an 8.10 rule would look like + // we don't want to support this type in our APIs any longer, but do + // want to allow users to import rules from 8.10 + investigation_fields: ['foo', 'bar'] as unknown as InvestigationFields, + }), + 'rules.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + expect(rule.investigation_fields).to.eql({ field_names: ['foo', 'bar'] }); + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should + * include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, rule.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo', 'bar'] }); + }); + + it('imports rule with investigation fields as empty array', async () => { + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach( + 'file', + getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { + // mimicking what an 8.10 rule would look like + // we don't want to support this type in our APIs any longer, but do + // want to allow users to import rules from 8.10 + investigation_fields: [] as unknown as InvestigationFields, + }), + 'rules.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + expect(rule.investigation_fields).to.eql(undefined); + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should + * include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, rule.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(undefined); + }); + + it('imports rule with investigation fields as intended object type', async () => { + await supertest + .post(`${DETECTION_ENGINE_RULES_URL}/_import`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .attach( + 'file', + getSimpleRuleAsNdjsonWithLegacyInvestigationField(['rule-1'], false, { + investigation_fields: { + field_names: ['foo'], + }, + }), + 'rules.ndjson' + ) + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + const rule = await fetchRule(supertest, { ruleId: 'rule-1' }); + expect(rule.investigation_fields).to.eql({ field_names: ['foo'] }); + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should + * include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, rule.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql({ field_names: ['foo'] }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/index.ts new file mode 100644 index 0000000000000..747f79eac3839 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_import_export/index.ts @@ -0,0 +1,18 @@ +/* + * 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('Rule import export API', function () { + loadTestFile(require.resolve('./export_rules')); + loadTestFile(require.resolve('./export_rules_ess')); + loadTestFile(require.resolve('./import_export_rules')); + loadTestFile(require.resolve('./import_rules')); + loadTestFile(require.resolve('./import_rules_ess')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/ess.config.ts new file mode 100644 index 0000000000000..94ea13264eaab --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/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: 'Rule Management API Integration Tests - ESS - Rule management logic', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.ts new file mode 100644 index 0000000000000..0f86bfe4d5ebb --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/configs/serverless.config.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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule management logic', + }, +}); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts similarity index 86% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts index acb8043ab3a25..3463518a51af4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_execution_results.ts @@ -12,15 +12,17 @@ import moment from 'moment'; import { set } from '@kbn/safer-lodash-set'; import { v4 as uuidv4 } from 'uuid'; import { getRuleExecutionResultsUrl } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; - -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllEventLogExecutionEvents, deleteAllAlerts, - getRuleForSignalTesting, + getRuleForAlertTesting, indexEventLogExecutionEvents, waitForEventLogExecuteComplete, waitForRulePartialFailure, @@ -31,23 +33,29 @@ import { failedRanAfterDisabled, successfulExecution, } from './template_data/execution_events'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; +import { EsArchivePathBuilder } from '../../../../es_archive_path_builder'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); const log = getService('log'); + // TODO: add a new service + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const auditbeatPath = dataPathBuilder.getPath('auditbeat/hosts'); - describe('Get Rule Execution Results', () => { + describe('@ess @serverless Get Rule Execution Results', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load(auditbeatPath); await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alias'); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload(auditbeatPath); await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alias'); await deleteAllAlerts(supertest, log, es); }); @@ -63,7 +71,8 @@ export default ({ getService }: FtrProviderContext) => { const response = await supertest .get(getRuleExecutionResultsUrl('1')) .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query({ start, end }); expect(response.status).to.eql(404); @@ -74,7 +83,7 @@ export default ({ getService }: FtrProviderContext) => { it('should return execution events for a rule that has executed successfully', async () => { const rule = { - ...getRuleForSignalTesting(['auditbeat-*']), + ...getRuleForAlertTesting(['auditbeat-*']), query: 'process.executable: "/usr/bin/sudo"', }; const { id } = await createRule(supertest, log, rule); @@ -85,8 +94,9 @@ export default ({ getService }: FtrProviderContext) => { const end = dateMath.parse('now', { roundUp: true })?.utc().toISOString(); const response = await supertest .get(getRuleExecutionResultsUrl(id)) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .query({ start, end }); expect(response.status).to.eql(200); @@ -103,7 +113,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return execution events for a rule that has executed in a warning state', async () => { - const rule = getRuleForSignalTesting(['no-name-index']); + const rule = getRuleForAlertTesting(['no-name-index']); const { id } = await createRule(supertest, log, rule); await waitForRulePartialFailure({ supertest, log, id }); await waitForEventLogExecuteComplete(es, log, id); @@ -113,7 +123,8 @@ export default ({ getService }: FtrProviderContext) => { const response = await supertest .get(getRuleExecutionResultsUrl(id)) .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query({ start, end }); expect(response.status).to.eql(200); @@ -132,7 +143,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return execution events for a rule that has executed in a failure state with a gap', async () => { - const rule = getRuleForSignalTesting(['auditbeat-*'], uuidv4(), false); + const rule = getRuleForAlertTesting(['auditbeat-*'], uuidv4(), false); const { id } = await createRule(supertest, log, rule); const start = dateMath.parse('now')?.utc().toISOString(); @@ -160,8 +171,9 @@ export default ({ getService }: FtrProviderContext) => { const response = await supertest .get(getRuleExecutionResultsUrl(id)) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .query({ start, end }); expect(response.status).to.eql(200); @@ -181,7 +193,7 @@ export default ({ getService }: FtrProviderContext) => { // For details, see: https://github.com/elastic/kibana/issues/131382 it('should return execution events ordered by @timestamp desc when a status filter is active and there are more than 1000 executions', async () => { - const rule = getRuleForSignalTesting(['auditbeat-*'], uuidv4(), false); + const rule = getRuleForAlertTesting(['auditbeat-*'], uuidv4(), false); const { id } = await createRule(supertest, log, rule); // Daterange for which we'll generate execution events between @@ -232,9 +244,9 @@ export default ({ getService }: FtrProviderContext) => { // Be sure to provide between 1-2 filters so that the server must prefetch events const response = await supertest .get(getRuleExecutionResultsUrl(id)) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'true') - .set('x-elastic-internal-origin', 'Kibana') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .query({ start, end, status_filters: 'failed,succeeded' }); // Verify the most recent execution was one of the failedRanAfterDisabled executions, which have a duration of 3ms and are made up of 2 docs per execution, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_rule_management_filters.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_management_filters.ts similarity index 91% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_rule_management_filters.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_management_filters.ts index d505342cfb236..37853b865e16d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_rule_management_filters.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/get_rule_management_filters.ts @@ -9,17 +9,21 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { RULE_MANAGEMENT_FILTERS_URL } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { deleteAllRules, getSimpleRule, installMockPrebuiltRules } from '../../utils'; -import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; -// eslint-disable-next-line import/no-default-export +import { + deleteAllRules, + getSimpleRule, + installMockPrebuiltRules, + deleteAllPrebuiltRuleAssets, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); const log = getService('log'); - describe('get_rule_management_filters', () => { + describe('@ess @serverless get_rule_management_filters', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/index.ts new file mode 100644 index 0000000000000..717c394c20fde --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/index.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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Rule Management API', function () { + loadTestFile(require.resolve('./get_rule_execution_results')); + loadTestFile(require.resolve('./get_rule_management_filters')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/template_data/execution_events.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/template_data/execution_events.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/template_data/execution_events.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_management/template_data/execution_events.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts similarity index 62% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts index 2430b8f2148d9..f8c742a881ded 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/ess.config.ts @@ -7,12 +7,16 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - ESS - Rule Patch logic', + }, }; } diff --git a/x-pack/test/detection_engine_api_integration/utils/get_slack_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts similarity index 50% rename from x-pack/test/detection_engine_api_integration/utils/get_slack_action.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts index 1d88f2cdbce73..7ed12808c452e 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_slack_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/configs/serverless.config.ts @@ -4,11 +4,11 @@ * 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 const getSlackAction = () => ({ - actionTypeId: '.slack', - secrets: { - webhookUrl: 'http://localhost:123', +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule Patch logic', }, - name: 'Slack connector', }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/index.ts new file mode 100644 index 0000000000000..609c70f10c944 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/index.ts @@ -0,0 +1,16 @@ +/* + * 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('Rule Patch API', function () { + loadTestFile(require.resolve('./patch_rules_bulk')); + loadTestFile(require.resolve('./patch_rules')); + loadTestFile(require.resolve('./patch_rules_ess')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts similarity index 72% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts index b2000305a4dbb..d267e6398eca0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules.ts @@ -6,8 +6,6 @@ */ import expect from '@kbn/expect'; -import { Rule } from '@kbn/alerting-plugin/common'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL, @@ -17,9 +15,9 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; + import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, @@ -30,31 +28,27 @@ import { getSimpleMlRuleOutput, createRule, getSimpleMlRule, - createLegacyRuleAction, - getLegacyActionSO, getSimpleRuleWithoutRuleId, - getRuleSOById, - createRuleThroughAlertingEndpoint, - getRuleSavedObjectWithLegacyInvestigationFields, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, -} from '../../utils'; -import { + removeUUIDFromActions, getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, -} from '../../utils/get_rule_actions'; -import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions'; + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); 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'); - describe('patch_rules', () => { + describe('@ess @serverless @skipInQA patch_rules', () => { describe('patch rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -73,7 +67,8 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', name: 'some other name' }) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -91,7 +86,8 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', machine_learning_job_id: 'some_job_id' }) .expect(200); - const outputRule = getSimpleMlRuleOutput(); + const outputRule = updateUsername(getSimpleMlRuleOutput(), ELASTICSEARCH_USERNAME); + const bodyToCompare = removeServerGeneratedProperties(body); expect(bodyToCompare).to.eql(outputRule); }); @@ -107,7 +103,8 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', name: 'some other name' }) .expect(200); - const outputRule = getSimpleMlRuleOutput(); + const outputRule = updateUsername(getSimpleMlRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -127,7 +124,11 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: createRuleBody.rule_id, name: 'some other name' }) .expect(200); - const outputRule = getSimpleRuleOutputWithoutRuleId(); + const outputRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); @@ -144,8 +145,8 @@ export default ({ getService }: FtrProviderContext) => { .set('elastic-api-version', '2023-10-31') .send({ id: createdBody.id, name: 'some other name' }) .expect(200); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - const outputRule = getSimpleRuleOutput(); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -163,7 +164,8 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', enabled: false }) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.enabled = false; const bodyToCompare = removeServerGeneratedProperties(body); @@ -181,7 +183,7 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', severity: 'low', enabled: false }) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; @@ -209,7 +211,7 @@ export default ({ getService }: FtrProviderContext) => { .send({ rule_id: 'rule-1', name: 'some other name' }) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.timeline_title = 'some title'; outputRule.timeline_id = 'some id'; @@ -370,58 +372,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - it('should return the rule with migrated actions after the enable patch', async () => { - const [connector, rule] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, getSimpleRule('rule-1')), - ]); - await createLegacyRuleAction(supertest, rule.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule.id); - - // patch disable the rule - const patchResponse = await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ id: rule.id, enabled: false }) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(patchResponse.body); - const outputRule = getSimpleRuleOutput(); - outputRule.actions = [ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: - 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - uuid: bodyToCompare.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ]; - outputRule.revision = 1; - expect(bodyToCompare).to.eql(outputRule); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - }); - it('should give a 404 if it is given a fake id', async () => { const { body } = await supertest .patch(DETECTION_ENGINE_RULES_URL) @@ -470,7 +420,7 @@ export default ({ getService }: FtrProviderContext) => { describe('actions without frequencies', () => { [undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach( (throttle) => { - it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => { + it(`@brokenInServerless it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => { const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest); // create simple rule @@ -482,8 +432,11 @@ export default ({ getService }: FtrProviderContext) => { throttle, actionsWithoutFrequencies ); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -497,7 +450,7 @@ export default ({ getService }: FtrProviderContext) => { // Action throttle cannot be shorter than the schedule interval which is by default is 5m ['300s', '5m', '3h', '4d'].forEach((throttle) => { - it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => { + it(`@brokenInServerless it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => { const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest); // create simple rule @@ -509,8 +462,11 @@ export default ({ getService }: FtrProviderContext) => { throttle, actionsWithoutFrequencies ); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -532,7 +488,7 @@ export default ({ getService }: FtrProviderContext) => { '10h', '2d', ].forEach((throttle) => { - it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => { + it(`@brokenInServerless it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => { const actionsWithFrequencies = await getActionsWithFrequencies(supertest); // create simple rule @@ -545,7 +501,11 @@ export default ({ getService }: FtrProviderContext) => { actionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + expectedRule.revision = 1; expectedRule.actions = actionsWithFrequencies; @@ -554,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('some actions with frequencies', () => { + describe('@brokenInServerless some actions with frequencies', () => { [undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach( (throttle) => { it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => { @@ -569,8 +529,11 @@ export default ({ getService }: FtrProviderContext) => { throttle, someActionsWithFrequencies ); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -597,7 +560,10 @@ export default ({ getService }: FtrProviderContext) => { someActionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -618,7 +584,7 @@ export default ({ getService }: FtrProviderContext) => { describe('investigation fields', () => { describe('investigation_field', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -687,102 +653,6 @@ export default ({ getService }: FtrProviderContext) => { expect(body.investigation_fields.field_names).to.eql(['blob', 'boop']); }); }); - - describe('investigation_fields legacy', () => { - let ruleWithLegacyInvestigationField: Rule; - let ruleWithLegacyInvestigationFieldEmptyArray: Rule; - - beforeEach(async () => { - ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFields() - ); - ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() - ); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('errors if trying to patch investigation fields using legacy format', async () => { - const { body } = await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ - rule_id: ruleWithLegacyInvestigationField.params.ruleId, - name: 'some other name', - investigation_fields: ['client.foo'], - }) - .expect(400); - - expect(body.message).to.eql( - '[request body]: investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 3 more' - ); - }); - - it('should patch a rule with a legacy investigation field and transform response', async () => { - const { body } = await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ - rule_id: ruleWithLegacyInvestigationField.params.ruleId, - name: 'some other name', - }) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql({ - field_names: ['client.address', 'agent.name'], - }); - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should - * NOT include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([ - 'client.address', - 'agent.name', - ]); - }); - - it('should patch a rule with a legacy investigation field - empty array - and transform response', async () => { - const { body } = await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send({ - rule_id: ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId, - name: 'some other name', - }) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql(undefined); - /** - * Confirm type on SO so that it's clear in the tests whether it's expected that - * the SO itself is migrated to the inteded object type, or if the transformation is - * happening just on the response. In this case, change should - * NOT include a migration on SO. - */ - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql([]); - }); - }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts similarity index 92% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts index 91c18b2cfa6cc..0bf1bd43ab99c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_bulk.ts @@ -12,9 +12,8 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { Rule } from '@kbn/alerting-plugin/common'; import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, @@ -26,18 +25,25 @@ import { createLegacyRuleAction, getLegacyActionSO, getRuleSOById, + updateUsername, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, getRuleSavedObjectWithLegacyInvestigationFields, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - - describe('patch_rules_bulk', () => { + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + + // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already + // deprecated, it should cease being exposed in Serverless prior to GA, in which case this + // test would be run for ESS only. + describe('@ess @brokenInServerless @skipInQA patch_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { await deleteAllRules(supertest, log); @@ -61,7 +67,7 @@ export default ({ getService }: FtrProviderContext) => { describe('patch rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -80,7 +86,8 @@ export default ({ getService }: FtrProviderContext) => { .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -102,11 +109,13 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(200); - const outputRule1 = getSimpleRuleOutput(); + const outputRule1 = updateUsername(getSimpleRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); + outputRule1.name = 'some other name'; outputRule1.revision = 1; - const outputRule2 = getSimpleRuleOutput('rule-2'); + const outputRule2 = updateUsername(getSimpleRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); + outputRule2.name = 'some other name'; outputRule2.revision = 1; @@ -127,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { .send([{ id: createRuleBody.id, name: 'some other name' }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -149,11 +158,18 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(200); - const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1'); + const outputRule1 = updateUsername( + getSimpleRuleOutputWithoutRuleId('rule-1'), + ELASTICSEARCH_USERNAME + ); + outputRule1.name = 'some other name'; outputRule1.revision = 1; - const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2'); + const outputRule2 = updateUsername( + getSimpleRuleOutputWithoutRuleId('rule-2'), + ELASTICSEARCH_USERNAME + ); outputRule2.name = 'some other name'; outputRule2.revision = 1; @@ -208,7 +224,11 @@ export default ({ getService }: FtrProviderContext) => { // @ts-expect-error body.forEach((response) => { const bodyToCompare = removeServerGeneratedProperties(response); - const outputRule = getSimpleRuleOutput(response.rule_id, false); + const outputRule = updateUsername( + getSimpleRuleOutput(response.rule_id, false), + ELASTICSEARCH_USERNAME + ); + outputRule.actions = [ { action_type_id: '.slack', @@ -238,7 +258,7 @@ export default ({ getService }: FtrProviderContext) => { .send([{ id: createdBody.id, name: 'some other name' }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -256,7 +276,8 @@ export default ({ getService }: FtrProviderContext) => { .send([{ rule_id: 'rule-1', enabled: false }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.enabled = false; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -274,7 +295,8 @@ export default ({ getService }: FtrProviderContext) => { .send([{ rule_id: 'rule-1', severity: 'low', enabled: false }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; @@ -302,7 +324,8 @@ export default ({ getService }: FtrProviderContext) => { .send([{ rule_id: 'rule-1', name: 'some other name' }]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.timeline_title = 'some title'; outputRule.timeline_id = 'some id'; @@ -361,7 +384,8 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; @@ -392,7 +416,8 @@ export default ({ getService }: FtrProviderContext) => { ]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; @@ -513,7 +538,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( supertest, getRuleSavedObjectWithLegacyInvestigationFields() diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts new file mode 100644 index 0000000000000..06b530c113352 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_patch/patch_rules_ess.ts @@ -0,0 +1,201 @@ +/* + * 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 { Rule } from '@kbn/alerting-plugin/common'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; + +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; + +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + removeServerGeneratedProperties, + getRuleSOById, + createRuleThroughAlertingEndpoint, + getRuleSavedObjectWithLegacyInvestigationFields, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, + createRule, + getLegacyActionSO, + getSimpleRuleOutput, + updateUsername, + createLegacyRuleAction, + getSimpleRule, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + 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'); + + describe('@ess patch_rules - ESS specific logic', () => { + describe('patch rules', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should return the rule with migrated actions after the enable patch', async () => { + const [connector, rule] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, getSimpleRule('rule-1')), + ]); + await createLegacyRuleAction(supertest, rule.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql(rule.id); + + // patch disable the rule + const patchResponse = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ id: rule.id, enabled: false }) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(patchResponse.body); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: + 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + uuid: bodyToCompare.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ]; + outputRule.revision = 1; + expect(bodyToCompare).to.eql(outputRule); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + + describe('investigation_fields legacy', () => { + let ruleWithLegacyInvestigationField: Rule; + let ruleWithLegacyInvestigationFieldEmptyArray: Rule; + + beforeEach(async () => { + ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ); + ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ); + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('errors if trying to patch investigation fields using legacy format', async () => { + const { body } = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + rule_id: ruleWithLegacyInvestigationField.params.ruleId, + name: 'some other name', + investigation_fields: ['client.foo'], + }) + .expect(400); + + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 3 more' + ); + }); + + it('should patch a rule with a legacy investigation field and transform response', async () => { + const { body } = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + rule_id: ruleWithLegacyInvestigationField.params.ruleId, + name: 'some other name', + }) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql({ + field_names: ['client.address', 'agent.name'], + }); + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should + * NOT include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, body.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql([ + 'client.address', + 'agent.name', + ]); + }); + + it('should patch a rule with a legacy investigation field - empty array - and transform response', async () => { + const { body } = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send({ + rule_id: ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId, + name: 'some other name', + }) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql(undefined); + /** + * Confirm type on SO so that it's clear in the tests whether it's expected that + * the SO itself is migrated to the inteded object type, or if the transformation is + * happening just on the response. In this case, change should + * NOT include a migration on SO. + */ + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, body.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql([]); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts similarity index 62% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts index 2430b8f2148d9..5c1925861aa39 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts @@ -7,12 +7,16 @@ import { FtrConfigProviderContext } from '@kbn/test'; -// eslint-disable-next-line import/no-default-export export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + const functionalConfig = await readConfigFile( + require.resolve('../../../../../config/ess/config.base.trial') + ); return { ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - ESS - Rule Read logic', + }, }; } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/config.base.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts similarity index 56% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/config.base.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts index ed72e1faa682c..81c0e71881466 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/config.base.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts @@ -4,11 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { createTestConfig } from '../../../../../config/serverless/config.base'; -import { createTestConfig } from '../common/config'; - -// eslint-disable-next-line import/no-default-export export default createTestConfig({ - license: 'trial', - ssl: true, + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule Read logic', + }, }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.ts new file mode 100644 index 0000000000000..8c8804bc59c68 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules.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 { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + createRule, + deleteAllRules, + getComplexRule, + getComplexRuleOutput, + getSimpleRule, + getSimpleRuleOutput, + getWebHookAction, + updateUsername, + removeServerGeneratedProperties, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const log = getService('log'); + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + + describe('@ess @serverless find_rules', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('should return an empty find body correctly if no rules are loaded', async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send() + .expect(200); + + expect(body).to.eql({ + data: [], + page: 1, + perPage: 20, + total: 0, + }); + }); + + it('should return a single rule when a single rule is loaded from a find with defaults added', async () => { + await createRule(supertest, log, getSimpleRule()); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send() + .expect(200); + + body.data = [removeServerGeneratedProperties(body.data[0])]; + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(body).to.eql({ + data: [expectedRule], + page: 1, + perPage: 20, + total: 1, + }); + }); + + it('should return a single rule when a single rule is loaded from a find with everything for the rule added', async () => { + // add a single rule + await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send(getComplexRule()) + .expect(200); + + // query and expect that we get back one record in the find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send() + .expect(200); + + body.data = [removeServerGeneratedProperties(body.data[0])]; + const expectedRule = updateUsername(getComplexRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(body).to.eql({ + data: [expectedRule], + page: 1, + perPage: 20, + total: 1, + }); + }); + + it('should find a single rule with a execute immediately action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + await createRule(supertest, log, rule); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send() + .expect(200); + + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + const ruleWithActions: ReturnType = { + ...expectedRule, + actions: [ + { + ...action, + uuid: body.data[0].actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ], + }; + + body.data = [removeServerGeneratedProperties(body.data[0])]; + expect(body).to.eql({ + data: [ruleWithActions], + page: 1, + perPage: 20, + total: 1, + }); + }); + + it('should be able to find a scheduled action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + throttle: '1h', // <-- throttle makes this a scheduled action + actions: [action], + }; + await createRule(supertest, log, rule); + + // query the single rule from _find + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}/_find`) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .send() + .expect(200); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + const ruleWithActions: ReturnType = { + ...expectedRule, + actions: [ + { + ...action, + uuid: body.data[0].actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ], + }; + + body.data = [removeServerGeneratedProperties(body.data[0])]; + expect(body).to.eql({ + data: [ruleWithActions], + page: 1, + perPage: 20, + total: 1, + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts similarity index 55% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts index 78b4c7f70fc0b..9b380d3a0a40a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/find_rules_ess.ts @@ -9,200 +9,39 @@ import expect from '@kbn/expect'; import { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { Rule } from '@kbn/alerting-plugin/common'; import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { DETECTION_ENGINE_RULES_URL, UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, createRuleThroughAlertingEndpoint, deleteAllRules, - getComplexRule, - getComplexRuleOutput, getRuleSOById, getSimpleRule, getSimpleRuleOutput, getWebHookAction, + updateUsername, removeServerGeneratedProperties, getRuleSavedObjectWithLegacyInvestigationFields, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); 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'); - describe('find_rules', () => { + describe('@ess find_rules - ESS specific logic', () => { beforeEach(async () => { await deleteAllRules(supertest, log); }); - it('should return an empty find body correctly if no rules are loaded', async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - expect(body).to.eql({ - data: [], - page: 1, - perPage: 20, - total: 0, - }); - }); - - it('should return a single rule when a single rule is loaded from a find with defaults added', async () => { - await createRule(supertest, log, getSimpleRule()); - - // query the single rule from _find - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - body.data = [removeServerGeneratedProperties(body.data[0])]; - expect(body).to.eql({ - data: [getSimpleRuleOutput()], - page: 1, - perPage: 20, - total: 1, - }); - }); - - it('should return a single rule when a single rule is loaded from a find with everything for the rule added', async () => { - // add a single rule - await supertest - .post(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getComplexRule()) - .expect(200); - - // query and expect that we get back one record in the find - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - body.data = [removeServerGeneratedProperties(body.data[0])]; - expect(body).to.eql({ - data: [getComplexRuleOutput()], - page: 1, - perPage: 20, - total: 1, - }); - }); - - it('should find a single rule with a execute immediately action correctly', async () => { - // create connector/action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - // create rule with connector/action - const rule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action], - }; - await createRule(supertest, log, rule); - - // query the single rule from _find - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), - actions: [ - { - ...action, - uuid: body.data[0].actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ], - }; - - body.data = [removeServerGeneratedProperties(body.data[0])]; - expect(body).to.eql({ - data: [ruleWithActions], - page: 1, - perPage: 20, - total: 1, - }); - }); - - it('should be able to find a scheduled action correctly', async () => { - // create connector/action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - // create rule with connector/action - const rule: ReturnType = { - ...getSimpleRule('rule-1'), - throttle: '1h', // <-- throttle makes this a scheduled action - actions: [action], - }; - await createRule(supertest, log, rule); - - // query the single rule from _find - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}/_find`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send() - .expect(200); - - const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), - actions: [ - { - ...action, - uuid: body.data[0].actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ], - }; - - body.data = [removeServerGeneratedProperties(body.data[0])]; - expect(body).to.eql({ - data: [ruleWithActions], - page: 1, - perPage: 20, - total: 1, - }); - }); - /** * Tests the legacy actions to ensure we can export legacy notifications * @deprecated Once the legacy notification system is removed, remove this test too. @@ -223,7 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { await supertest .post(`${UPDATE_OR_CREATE_LEGACY_ACTIONS}?alert_id=${createRuleBody.id}`) .set('kbn-xsrf', 'true') - .set('elastic-api-version', '1') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .send({ name: 'Legacy notification with one action', interval: '1h', @@ -245,12 +84,13 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}/_find`) .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') .send() .expect(200); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), + ...expectedRule, actions: [ { id: hookAction.id, @@ -300,7 +140,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .get(`${DETECTION_ENGINE_RULES_URL}/_find`) .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .send() .expect(200); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/index.ts new file mode 100644 index 0000000000000..2c461fc497090 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/index.ts @@ -0,0 +1,18 @@ +/* + * 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('Rule Read API', function () { + loadTestFile(require.resolve('./find_rules')); + loadTestFile(require.resolve('./find_rules_ess')); + loadTestFile(require.resolve('./read_rules')); + loadTestFile(require.resolve('./read_rules_ess')); + loadTestFile(require.resolve('./resolve_read_rules')); + }); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts new file mode 100644 index 0000000000000..a26c3dba358c5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules.ts @@ -0,0 +1,218 @@ +/* + * 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 { + createRule, + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + getSimpleRule, + getSimpleRuleOutput, + getSimpleRuleOutputWithoutRuleId, + getSimpleRuleWithoutRuleId, + getWebHookAction, + removeServerGeneratedProperties, + removeServerGeneratedPropertiesIncludingRuleId, + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + 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'); + + describe('@ess @serverless read_rules', () => { + describe('reading rules', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should be able to read a single rule using rule_id', async () => { + await createRule(supertest, log, getSimpleRule()); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should be able to read a single rule using id', async () => { + const createRuleBody = await createRule(supertest, log, getSimpleRule()); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should be able to read a single rule with an auto-generated rule_id', async () => { + const createRuleBody = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${createRuleBody.rule_id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + + expect(bodyToCompare).to.eql(expectedRule); + }); + + it('should return 404 if given a fake id', async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" not found', + }); + }); + + it('should return 404 if given a fake rule_id', async () => { + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=fake_id`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(404); + + expect(body).to.eql({ + status_code: 404, + message: 'rule_id: "fake_id" not found', + }); + }); + + it('@brokenInServerless @skipInQA should be able to a read a execute immediately action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + actions: [action], + }; + const createRuleBody = await createRule(supertest, log, rule); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + const ruleWithActions: ReturnType = { + ...expectedRule, + actions: [ + { + ...action, + uuid: bodyToCompare.actions[0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ], + }; + expect(bodyToCompare).to.eql(ruleWithActions); + }); + + it('@brokenInServerless should be able to a read a scheduled action correctly', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getWebHookAction()) + .expect(200); + + const action = { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }; + + // create rule with connector/action + const rule: ReturnType = { + ...getSimpleRule('rule-1'), + throttle: '1h', // <-- throttle makes this a scheduled action + actions: [action], + }; + + const createRuleBody = await createRule(supertest, log, rule); + + const { body } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(getSimpleRule()) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + + const ruleWithActions: ReturnType = { + ...expectedRule, + actions: [ + { + ...action, + uuid: bodyToCompare.actions[0].uuid, + frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, + }, + ], + }; + expect(bodyToCompare).to.eql(ruleWithActions); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts similarity index 57% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts index 9c5fddaf8588f..dcbaf8b10615e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/read_rules_ess.ts @@ -12,35 +12,35 @@ import { DETECTION_ENGINE_RULES_URL, UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRule, getSimpleRuleOutput, - getSimpleRuleOutputWithoutRuleId, - getSimpleRuleWithoutRuleId, getWebHookAction, removeServerGeneratedProperties, - removeServerGeneratedPropertiesIncludingRuleId, getRuleSOById, + updateUsername, getRuleSavedObjectWithLegacyInvestigationFields, createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); 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'); - describe('read_rules', () => { + describe('@ess read_rules - ESS specific logic', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -48,164 +48,6 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllRules(supertest, log); }); - it('should be able to read a single rule using rule_id', async () => { - await createRule(supertest, log, getSimpleRule()); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=rule-1`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); - }); - - it('should be able to read a single rule using id', async () => { - const createRuleBody = await createRule(supertest, log, getSimpleRule()); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutput()); - }); - - it('should be able to read a single rule with an auto-generated rule_id', async () => { - const createRuleBody = await createRule(supertest, log, getSimpleRuleWithoutRuleId()); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${createRuleBody.rule_id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(200); - - const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(getSimpleRuleOutputWithoutRuleId()); - }); - - it('should return 404 if given a fake id', async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?id=c1e1b359-7ac1-4e96-bc81-c683c092436f`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(404); - - expect(body).to.eql({ - status_code: 404, - message: 'id: "c1e1b359-7ac1-4e96-bc81-c683c092436f" not found', - }); - }); - - it('should return 404 if given a fake rule_id', async () => { - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=fake_id`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(404); - - expect(body).to.eql({ - status_code: 404, - message: 'rule_id: "fake_id" not found', - }); - }); - - it('should be able to a read a execute immediately action correctly', async () => { - // create connector/action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - // create rule with connector/action - const rule: ReturnType = { - ...getSimpleRule('rule-1'), - actions: [action], - }; - const createRuleBody = await createRule(supertest, log, rule); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), - actions: [ - { - ...action, - uuid: bodyToCompare.actions[0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ], - }; - expect(bodyToCompare).to.eql(ruleWithActions); - }); - - it('should be able to a read a scheduled action correctly', async () => { - // create connector/action - const { body: hookAction } = await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200); - - const action = { - group: 'default', - id: hookAction.id, - action_type_id: hookAction.actionTypeId, - params: {}, - }; - - // create rule with connector/action - const rule: ReturnType = { - ...getSimpleRule('rule-1'), - throttle: '1h', // <-- throttle makes this a scheduled action - actions: [action], - }; - - const createRuleBody = await createRule(supertest, log, rule); - - const { body } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL}?id=${createRuleBody.id}`) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(getSimpleRule()) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), - actions: [ - { - ...action, - uuid: bodyToCompare.actions[0].uuid, - frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, - }, - ], - }; - expect(bodyToCompare).to.eql(ruleWithActions); - }); - /** * Tests the legacy actions to ensure we can export legacy notifications * @deprecated Once the legacy notification system is removed, remove this test too. @@ -253,8 +95,10 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const bodyToCompare = removeServerGeneratedProperties(body); + const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + const ruleWithActions: ReturnType = { - ...getSimpleRuleOutput(), + ...expectedRule, actions: [ { id: hookAction.id, @@ -273,14 +117,14 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('investigation_fields', () => { + describe('legacy investigation_fields', () => { let ruleWithLegacyInvestigationField: Rule; let ruleWithLegacyInvestigationFieldEmptyArray: Rule; beforeEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( supertest, getRuleSavedObjectWithLegacyInvestigationFields() diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/resolve_read_rules.ts similarity index 95% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/resolve_read_rules.ts index 4536526ff5744..b35343589ed65 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/resolve_read_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/resolve_read_rules.ts @@ -9,22 +9,21 @@ import expect from '@kbn/expect'; import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { createSignalsIndex, deleteAllRules, deleteAllAlerts } from '../../utils'; +import { createAlertsIndex, deleteAllRules, deleteAllAlerts } from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; const spaceId = '714-space'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); const log = getService('log'); - describe('resolve_read_rules', () => { + describe('@ess resolve_read_rules', () => { describe('reading rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/resolve_read_rules/7_14' ); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/ess.config.ts new file mode 100644 index 0000000000000..1774ff3ae28ea --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/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: 'Rule Management API Integration Tests - ESS - Rule Update logic', + }, + }; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.ts new file mode 100644 index 0000000000000..017b5dec486b1 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/configs/serverless.config.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 { createTestConfig } from '../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: 'Rule Management API Integration Tests - Serverless - Rule Update logic', + }, +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/index.ts new file mode 100644 index 0000000000000..c132ffcd06028 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/index.ts @@ -0,0 +1,16 @@ +/* + * 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('Rule Update API', function () { + loadTestFile(require.resolve('./update_rules_bulk')); + loadTestFile(require.resolve('./update_rules')); + loadTestFile(require.resolve('./update_rules_ess')); + }); +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts similarity index 77% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts index afb0205ce458f..3c6a3e7735a4a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules.ts @@ -6,8 +6,6 @@ */ import expect from '@kbn/expect'; -import { Rule } from '@kbn/alerting-plugin/common'; -import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL, NOTIFICATION_DEFAULT_FREQUENCY, @@ -16,9 +14,9 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; + import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRuleOutput, @@ -32,32 +30,28 @@ import { getSimpleSavedQueryRule, createRule, getSimpleRule, - createLegacyRuleAction, - getThresholdRuleForSignalTesting, - getLegacyActionSO, + getThresholdRuleForAlertTesting, getSimpleRuleWithoutRuleId, - getRuleSOById, - createRuleThroughAlertingEndpoint, - getRuleSavedObjectWithLegacyInvestigationFields, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, -} from '../../utils'; -import { + removeUUIDFromActions, + updateUsername, getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, -} from '../../utils/get_rule_actions'; -import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions'; +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); 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'); - describe('update_rules', () => { + describe('@ess @serverless @skipInQA update_rules', () => { describe('update rules', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -81,7 +75,8 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -104,7 +99,8 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleMlRuleOutput(); + const outputRule = updateUsername(getSimpleMlRuleOutput(), ELASTICSEARCH_USERNAME); + // @ts-expect-error type narrowing is lost due to Omit<> outputRule.machine_learning_job_id = ['legacy_job_id']; outputRule.revision = 1; @@ -128,7 +124,7 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleMlRuleOutput(); + const outputRule = updateUsername(getSimpleMlRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -153,14 +149,18 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleRuleOutputWithoutRuleId(); + const outputRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); expect(bodyToCompare).to.eql(outputRule); }); - it('should update a single rule property and remove the action', async () => { + it('@brokenInServerless should update a single rule property and remove the action', async () => { const [connector1] = await Promise.all([ supertest .post(`/api/actions/connector`) @@ -203,7 +203,10 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleRuleOutputWithoutRuleId(); + const outputRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); outputRule.name = 'some other name'; outputRule.revision = 1; // Expect an empty array @@ -212,78 +215,6 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare).to.eql(outputRule); }); - it('should update a single rule property of name using an auto-generated rule_id and migrate the actions', async () => { - const rule = getSimpleRule('rule-1'); - delete rule.rule_id; - const [connector, createRuleBody] = await Promise.all([ - supertest - .post(`/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - connector_type_id: '.slack', - secrets: { - webhookUrl: 'http://localhost:1234', - }, - }), - createRule(supertest, log, rule), - ]); - await createLegacyRuleAction(supertest, createRuleBody.id, connector.body.id); - - // check for legacy sidecar action - const sidecarActionsResults = await getLegacyActionSO(es); - expect(sidecarActionsResults.hits.hits.length).to.eql(1); - expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( - createRuleBody.id - ); - - const action1 = { - group: 'default', - id: connector.body.id, - action_type_id: connector.body.connector_type_id, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - }; - // update a simple rule's name - const updatedRule = getSimpleRuleUpdate('rule-1'); - updatedRule.rule_id = createRuleBody.rule_id; - updatedRule.name = 'some other name'; - updatedRule.actions = [action1]; - delete updatedRule.id; - - const { body } = await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule) - .expect(200); - - const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - - const outputRule = getSimpleRuleOutputWithoutRuleId(); - outputRule.name = 'some other name'; - outputRule.revision = 1; - outputRule.actions = [ - { - action_type_id: '.slack', - group: 'default', - id: connector.body.id, - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', - }, - uuid: bodyToCompare.actions![0].uuid, - frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, - }, - ]; - - expect(bodyToCompare).to.eql(outputRule); - - // legacy sidecar action should be gone - const sidecarActionsPostResults = await getLegacyActionSO(es); - expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); - }); - it('should update a single rule property of name using the auto-generated id', async () => { const createdBody = await createRule(supertest, log, getSimpleRule('rule-1')); @@ -300,7 +231,8 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body); @@ -322,7 +254,8 @@ export default ({ getService }: FtrProviderContext) => { .send(updatedRule) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; @@ -357,7 +290,8 @@ export default ({ getService }: FtrProviderContext) => { .send(ruleUpdate2) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); + outputRule.name = 'some other name'; outputRule.revision = 2; @@ -553,7 +487,7 @@ export default ({ getService }: FtrProviderContext) => { describe('threshold validation', () => { it('should result in 400 error if no threshold-specific fields are provided', async () => { - const existingRule = getThresholdRuleForSignalTesting(['*']); + const existingRule = getThresholdRuleForAlertTesting(['*']); await createRule(supertest, log, existingRule); const { threshold, ...rule } = existingRule; @@ -572,7 +506,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should result in 400 error if more than 3 threshold fields', async () => { - const existingRule = getThresholdRuleForSignalTesting(['*']); + const existingRule = getThresholdRuleForAlertTesting(['*']); await createRule(supertest, log, existingRule); const rule = { @@ -596,7 +530,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should result in 400 error if threshold value is less than 1', async () => { - const existingRule = getThresholdRuleForSignalTesting(['*']); + const existingRule = getThresholdRuleForAlertTesting(['*']); await createRule(supertest, log, existingRule); const rule = { @@ -621,7 +555,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should result in 400 error if cardinality is also an agg field', async () => { - const existingRule = getThresholdRuleForSignalTesting(['*']); + const existingRule = getThresholdRuleForAlertTesting(['*']); await createRule(supertest, log, existingRule); const rule = { @@ -724,7 +658,7 @@ export default ({ getService }: FtrProviderContext) => { return removeServerGeneratedPropertiesIncludingRuleId(updatedRule); }; - describe('actions without frequencies', () => { + describe('@brokenInServerless actions without frequencies', () => { [undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach( (throttle) => { it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => { @@ -739,8 +673,11 @@ export default ({ getService }: FtrProviderContext) => { throttle, actionsWithoutFrequencies ); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -767,7 +704,10 @@ export default ({ getService }: FtrProviderContext) => { actionsWithoutFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -779,7 +719,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('actions with frequencies', () => { + describe('@brokenInServerless actions with frequencies', () => { [ undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -802,7 +742,10 @@ export default ({ getService }: FtrProviderContext) => { actionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = actionsWithFrequencies; @@ -811,7 +754,7 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('some actions with frequencies', () => { + describe('@brokenInServerless some actions with frequencies', () => { [undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach( (throttle) => { it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => { @@ -827,7 +770,10 @@ export default ({ getService }: FtrProviderContext) => { someActionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -854,7 +800,10 @@ export default ({ getService }: FtrProviderContext) => { someActionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -914,102 +863,5 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); - - describe('legacy investigation fields', () => { - let ruleWithLegacyInvestigationField: Rule; - let ruleWithLegacyInvestigationFieldEmptyArray: Rule; - - beforeEach(async () => { - await deleteAllAlerts(supertest, log, es); - await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); - ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFields() - ); - ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( - supertest, - getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() - ); - await createRule(supertest, log, { - ...getSimpleRule('rule-with-investigation-field'), - name: 'Test investigation fields object', - investigation_fields: { field_names: ['host.name'] }, - }); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('errors if sending legacy investigation fields type', async () => { - const updatedRule = { - ...getSimpleRuleUpdate(ruleWithLegacyInvestigationField.params.ruleId), - investigation_fields: ['foo'], - }; - - const { body } = await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule) - .expect(400); - - expect(body.message).to.eql( - '[request body]: investigation_fields: Expected object, received array' - ); - }); - - it('unsets legacy investigation fields when field not specified for update', async () => { - // rule_id of a rule with legacy investigation fields set - const updatedRule = getSimpleRuleUpdate(ruleWithLegacyInvestigationField.params.ruleId); - - const { body } = await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql(undefined); - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql(undefined); - }); - - it('updates a rule with legacy investigation fields when field specified for update in intended format', async () => { - // rule_id of a rule with legacy investigation fields set - const updatedRule = { - ...getSimpleRuleUpdate(ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId), - investigation_fields: { - field_names: ['foo'], - }, - }; - - const { body } = await supertest - .put(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .set('elastic-api-version', '2023-10-31') - .send(updatedRule) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare.investigation_fields).to.eql({ - field_names: ['foo'], - }); - const { - hits: { - hits: [{ _source: ruleSO }], - }, - } = await getRuleSOById(es, body.id); - expect(ruleSO?.alert?.params?.investigationFields).to.eql({ - field_names: ['foo'], - }); - }); - }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts similarity index 92% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts index 569069cee3062..49c9ffdd817fd 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_bulk.ts @@ -18,9 +18,8 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; import { - createSignalsIndex, + createAlertsIndex, deleteAllRules, deleteAllAlerts, getSimpleRuleOutput, @@ -37,21 +36,26 @@ import { createRuleThroughAlertingEndpoint, getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, getRuleSOById, -} from '../../utils'; -import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions'; -import { + removeUUIDFromActions, getActionsWithFrequencies, getActionsWithoutFrequencies, getSomeActionsWithFrequencies, -} from '../../utils/get_rule_actions'; + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -// eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); - - describe('update_rules_bulk', () => { + // TODO: add a new service + const config = getService('config'); + const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); + + // Marking as ESS and brokenInServerless as it's currently exposed in both, but if this is already + // deprecated, it should cease being exposed in Serverless prior to GA, in which case this + // test would be run for ESS only. + describe('@ess @brokenInServerless @skipInQA update_rules_bulk', () => { describe('deprecations', () => { afterEach(async () => { await deleteAllRules(supertest, log); @@ -76,7 +80,7 @@ export default ({ getService }: FtrProviderContext) => { describe('update rules bulk', () => { beforeEach(async () => { - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); }); afterEach(async () => { @@ -98,7 +102,7 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -130,11 +134,11 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1, updatedRule2]) .expect(200); - const outputRule1 = getSimpleRuleOutput(); + const outputRule1 = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule1.name = 'some other name'; outputRule1.revision = 1; - const outputRule2 = getSimpleRuleOutput('rule-2'); + const outputRule2 = updateUsername(getSimpleRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); outputRule2.name = 'some other name'; outputRule2.revision = 1; @@ -201,7 +205,10 @@ export default ({ getService }: FtrProviderContext) => { body.forEach((response) => { const bodyToCompare = removeServerGeneratedProperties(response); - const outputRule = getSimpleRuleOutput(response.rule_id); + const outputRule = updateUsername( + getSimpleRuleOutput(response.rule_id), + ELASTICSEARCH_USERNAME + ); outputRule.name = 'some other name'; outputRule.revision = 1; outputRule.actions = [ @@ -264,7 +271,10 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); body.forEach((response) => { - const outputRule = getSimpleRuleOutput(response.rule_id); + const outputRule = updateUsername( + getSimpleRuleOutput(response.rule_id), + ELASTICSEARCH_USERNAME + ); outputRule.name = 'some other name'; outputRule.revision = 1; outputRule.actions = []; @@ -289,7 +299,7 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -318,11 +328,11 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1, updatedRule2]) .expect(200); - const outputRule1 = getSimpleRuleOutput('rule-1'); + const outputRule1 = updateUsername(getSimpleRuleOutput('rule-1'), ELASTICSEARCH_USERNAME); outputRule1.name = 'some other name'; outputRule1.revision = 1; - const outputRule2 = getSimpleRuleOutput('rule-2'); + const outputRule2 = updateUsername(getSimpleRuleOutput('rule-2'), ELASTICSEARCH_USERNAME); outputRule2.name = 'some other name'; outputRule2.revision = 1; @@ -348,7 +358,7 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); @@ -370,7 +380,7 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.enabled = false; outputRule.severity = 'low'; outputRule.revision = 1; @@ -405,7 +415,7 @@ export default ({ getService }: FtrProviderContext) => { .send([ruleUpdate2]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 2; @@ -475,7 +485,7 @@ export default ({ getService }: FtrProviderContext) => { .send([ruleUpdate, ruleUpdate2]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; @@ -513,7 +523,7 @@ export default ({ getService }: FtrProviderContext) => { .send([rule1, rule2]) .expect(200); - const outputRule = getSimpleRuleOutput(); + const outputRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); outputRule.name = 'some other name'; outputRule.revision = 1; @@ -677,7 +687,10 @@ export default ({ getService }: FtrProviderContext) => { actionsWithoutFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -705,7 +718,10 @@ export default ({ getService }: FtrProviderContext) => { actionsWithoutFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = actionsWithoutFrequencies.map((action) => ({ ...action, @@ -740,7 +756,10 @@ export default ({ getService }: FtrProviderContext) => { actionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = actionsWithFrequencies; @@ -765,7 +784,10 @@ export default ({ getService }: FtrProviderContext) => { someActionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -792,7 +814,10 @@ export default ({ getService }: FtrProviderContext) => { someActionsWithFrequencies ); - const expectedRule = getSimpleRuleOutputWithoutRuleId(); + const expectedRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); expectedRule.revision = 1; expectedRule.actions = someActionsWithFrequencies.map((action) => ({ ...action, @@ -817,7 +842,7 @@ export default ({ getService }: FtrProviderContext) => { beforeEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); - await createSignalsIndex(supertest, log); + await createAlertsIndex(supertest, log); ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( supertest, getRuleSavedObjectWithLegacyInvestigationFields() diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts new file mode 100644 index 0000000000000..38a5a5a07a9f0 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_update/update_rules_ess.ts @@ -0,0 +1,225 @@ +/* + * 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 { Rule } from '@kbn/alerting-plugin/common'; +import { BaseRuleParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_schema'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; + +import { + createAlertsIndex, + deleteAllRules, + deleteAllAlerts, + removeServerGeneratedProperties, + removeServerGeneratedPropertiesIncludingRuleId, + getSimpleRuleOutputWithoutRuleId, + getSimpleRuleUpdate, + createRule, + getSimpleRule, + createLegacyRuleAction, + getLegacyActionSO, + getRuleSOById, + createRuleThroughAlertingEndpoint, + getRuleSavedObjectWithLegacyInvestigationFields, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray, + updateUsername, +} from '../../utils'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + 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'); + + describe('@ess update_rules - ESS specific logic', () => { + describe('update rules', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should update a single rule property of name using an auto-generated rule_id and migrate the actions', async () => { + const rule = getSimpleRule('rule-1'); + delete rule.rule_id; + const [connector, createRuleBody] = await Promise.all([ + supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: '.slack', + secrets: { + webhookUrl: 'http://localhost:1234', + }, + }), + createRule(supertest, log, rule), + ]); + await createLegacyRuleAction(supertest, createRuleBody.id, connector.body.id); + + // check for legacy sidecar action + const sidecarActionsResults = await getLegacyActionSO(es); + expect(sidecarActionsResults.hits.hits.length).to.eql(1); + expect(sidecarActionsResults.hits.hits[0]?._source?.references[0].id).to.eql( + createRuleBody.id + ); + + const action1 = { + group: 'default', + id: connector.body.id, + action_type_id: connector.body.connector_type_id, + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + }; + // update a simple rule's name + const updatedRule = getSimpleRuleUpdate('rule-1'); + updatedRule.rule_id = createRuleBody.rule_id; + updatedRule.name = 'some other name'; + updatedRule.actions = [action1]; + delete updatedRule.id; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule) + .expect(200); + + const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); + + const outputRule = updateUsername( + getSimpleRuleOutputWithoutRuleId(), + ELASTICSEARCH_USERNAME + ); + outputRule.name = 'some other name'; + outputRule.revision = 1; + outputRule.actions = [ + { + action_type_id: '.slack', + group: 'default', + id: connector.body.id, + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts', + }, + uuid: bodyToCompare.actions![0].uuid, + frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' }, + }, + ]; + + expect(bodyToCompare).to.eql(outputRule); + + // legacy sidecar action should be gone + const sidecarActionsPostResults = await getLegacyActionSO(es); + expect(sidecarActionsPostResults.hits.hits.length).to.eql(0); + }); + }); + + describe('legacy investigation fields', () => { + let ruleWithLegacyInvestigationField: Rule; + let ruleWithLegacyInvestigationFieldEmptyArray: Rule; + + beforeEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); + ruleWithLegacyInvestigationField = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFields() + ); + ruleWithLegacyInvestigationFieldEmptyArray = await createRuleThroughAlertingEndpoint( + supertest, + getRuleSavedObjectWithLegacyInvestigationFieldsEmptyArray() + ); + await createRule(supertest, log, { + ...getSimpleRule('rule-with-investigation-field'), + name: 'Test investigation fields object', + investigation_fields: { field_names: ['host.name'] }, + }); + }); + + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('errors if sending legacy investigation fields type', async () => { + const updatedRule = { + ...getSimpleRuleUpdate(ruleWithLegacyInvestigationField.params.ruleId), + investigation_fields: ['foo'], + }; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule) + .expect(400); + + expect(body.message).to.eql( + '[request body]: investigation_fields: Expected object, received array' + ); + }); + + it('unsets legacy investigation fields when field not specified for update', async () => { + // rule_id of a rule with legacy investigation fields set + const updatedRule = getSimpleRuleUpdate(ruleWithLegacyInvestigationField.params.ruleId); + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql(undefined); + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, body.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql(undefined); + }); + + it('updates a rule with legacy investigation fields when field specified for update in intended format', async () => { + // rule_id of a rule with legacy investigation fields set + const updatedRule = { + ...getSimpleRuleUpdate(ruleWithLegacyInvestigationFieldEmptyArray.params.ruleId), + investigation_fields: { + field_names: ['foo'], + }, + }; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(updatedRule) + .expect(200); + + const bodyToCompare = removeServerGeneratedProperties(body); + expect(bodyToCompare.investigation_fields).to.eql({ + field_names: ['foo'], + }); + const { + hits: { + hits: [{ _source: ruleSO }], + }, + } = await getRuleSOById(es, body.id); + expect(ruleSO?.alert?.params?.investigationFields).to.eql({ + field_names: ['foo'], + }); + }); + }); + }); +}; 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 9accb2b089280..d5fd6c33dccd0 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 @@ -8,3 +8,4 @@ export * from './get_slack_action'; export * from './get_web_hook_action'; export * from './create_new_webhook_action'; export * from './legacy_actions'; +export * from './remove_uuid_from_actions'; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_uuid_from_actions.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/remove_uuid_from_actions.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/remove_uuid_from_actions.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/actions/remove_uuid_from_actions.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/binary_to_string.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/binary_to_string.ts new file mode 100644 index 0000000000000..47202a385de56 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/binary_to_string.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. + */ + +/** + * Useful for export_api testing to convert from a multi-part binary back to a string + * @param res Response + * @param callback Callback + */ +export const binaryToString = (res: any, callback: any): void => { + res.setEncoding('binary'); + res.data = ''; + res.on('data', (chunk: any) => { + res.data += chunk; + }); + res.on('end', () => { + callback(null, Buffer.from(res.data)); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_event_log_execute_complete_by_id.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/get_event_log_execute_complete_by_id.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/get_event_log_execute_complete_by_id.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/get_event_log_execute_complete_by_id.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index.ts index c10ed51052bc3..76ada3eefd93e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index.ts @@ -5,3 +5,6 @@ * 2.0. */ export * from './delete_all_event_log_execution_events'; +export * from './index_event_log_execution_events'; +export * from './wait_for_event_log_execute_complete'; +export * from './get_event_log_execute_complete_by_id'; diff --git a/x-pack/test/detection_engine_api_integration/utils/index_event_log_execution_events.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index_event_log_execution_events.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/index_event_log_execution_events.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/index_event_log_execution_events.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/wait_for_event_log_execute_complete.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts similarity index 96% rename from x-pack/test/detection_engine_api_integration/utils/wait_for_event_log_execute_complete.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts index 0b86a028d036a..58ac05c9ee3df 100644 --- a/x-pack/test/detection_engine_api_integration/utils/wait_for_event_log_execute_complete.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/event_log/wait_for_event_log_execute_complete.ts @@ -8,7 +8,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type { Client } from '@elastic/elasticsearch'; -import { waitFor } from './wait_for'; +import { waitFor } from '../wait_for'; import { getEventLogExecuteCompleteById } from './get_event_log_execute_complete_by_id'; /** diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts index 0b7c66f57cb2a..0fed526f9ef3f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/index.ts @@ -13,6 +13,7 @@ export * from './telemetry'; export * from './event_log'; export * from './machine_learning'; +export * from './binary_to_string'; export * from './get_index_name_from_load'; export * from './count_down_test'; export * from './count_down_es'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_rule.ts new file mode 100644 index 0000000000000..89bb2bbea5725 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/create_non_security_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 SuperTest from 'supertest'; + +const SIMPLE_APM_RULE_DATA = { + name: 'Test rule', + rule_type_id: 'apm.anomaly', + enabled: false, + consumer: 'alerts', + tags: [], + actions: [], + params: { + windowSize: 30, + windowUnit: 'm', + anomalySeverityType: 'critical', + environment: 'ENVIRONMENT_ALL', + }, + schedule: { + interval: '10m', + }, +}; + +/** + * Created a non security rule. Helpful in tests to verify functionality works with presence of non security rules. + * @param supertest The supertest deps + */ +export async function createNonSecurityRule( + supertest: SuperTest.SuperTest +): Promise { + await supertest + .post('/api/alerting/rule') + .set('kbn-xsrf', 'true') + .set('elastic-api-version', '2023-10-31') + .send(SIMPLE_APM_RULE_DATA) + .expect(200); +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule.ts new file mode 100644 index 0000000000000..3e507259ce685 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule.ts @@ -0,0 +1,97 @@ +/* + * 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 } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * This will return a complex rule with all the outputs possible + * @param ruleId The ruleId to set which is optional and defaults to rule-1 + */ +export const getComplexRule = (ruleId = 'rule-1'): RuleCreateProps => ({ + actions: [], + author: [], + name: 'Complex Rule Query', + description: 'Complex Rule Query', + false_positives: [ + 'https://www.example.com/some-article-about-a-false-positive', + 'some text string about why another condition could be a false positive', + ], + risk_score: 1, + risk_score_mapping: [], + rule_id: ruleId, + filters: [ + { + query: { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + }, + ], + enabled: false, + index: ['auditbeat-*', 'filebeat-*'], + interval: '5m', + output_index: '', + meta: { + anything_you_want_ui_related_or_otherwise: { + as_deep_structured_as_you_need: { + any_data_type: {}, + }, + }, + }, + max_signals: 10, + tags: ['tag 1', 'tag 2', 'any tag you want'], + to: 'now', + from: 'now-6m', + severity: 'high', + severity_mapping: [], + language: 'kuery', + type: 'query', + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + { + framework: 'Some other Framework you want', + tactic: { + id: 'some-other-id', + name: 'Some other name', + reference: 'https://example.com', + }, + technique: [ + { + id: 'some-other-id', + name: 'some other technique name', + reference: 'https://example.com', + }, + ], + }, + ], + references: [ + 'http://www.example.com/some-article-about-attack', + 'Some plain text string here explaining why this is a valid thing to look out for', + ], + timeline_id: 'timeline_id', + timeline_title: 'timeline_title', + note: '# some investigation documentation', + version: 1, + query: 'user.name: root or user.name: admin', + throttle: 'no_actions', + exceptions_list: [], +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule_output.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule_output.ts new file mode 100644 index 0000000000000..ff75d5a065d97 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_complex_rule_output.ts @@ -0,0 +1,108 @@ +/* + * 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 } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +// TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object +// without using Partial +/** + * This will return a complex rule with all the outputs possible + * @param ruleId The ruleId to set which is optional and defaults to rule-1 + */ +export const getComplexRuleOutput = ( + ruleId = 'rule-1', + enabled = false +): Partial => ({ + actions: [], + author: [], + created_by: 'elastic', + name: 'Complex Rule Query', + description: 'Complex Rule Query', + false_positives: [ + 'https://www.example.com/some-article-about-a-false-positive', + 'some text string about why another condition could be a false positive', + ], + risk_score: 1, + risk_score_mapping: [], + rule_id: ruleId, + filters: [ + { + query: { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + }, + ], + enabled, + index: ['auditbeat-*', 'filebeat-*'], + immutable: false, + interval: '5m', + output_index: '', + meta: { + anything_you_want_ui_related_or_otherwise: { + as_deep_structured_as_you_need: { + any_data_type: {}, + }, + }, + }, + max_signals: 10, + tags: ['tag 1', 'tag 2', 'any tag you want'], + to: 'now', + from: 'now-6m', + revision: 0, + severity: 'high', + severity_mapping: [], + language: 'kuery', + type: 'query', + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + technique: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + { + framework: 'Some other Framework you want', + tactic: { + id: 'some-other-id', + name: 'Some other name', + reference: 'https://example.com', + }, + technique: [ + { + id: 'some-other-id', + name: 'some other technique name', + reference: 'https://example.com', + }, + ], + }, + ], + references: [ + 'http://www.example.com/some-article-about-attack', + 'Some plain text string here explaining why this is a valid thing to look out for', + ], + timeline_id: 'timeline_id', + timeline_title: 'timeline_title', + updated_by: 'elastic', + note: '# some investigation documentation', + version: 1, + query: 'user.name: root or user.name: admin', + exceptions_list: [], + related_integrations: [], + required_fields: [], + setup: '', +}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rules_as_ndjson.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rules_as_ndjson.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/get_rules_as_ndjson.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_rules_as_ndjson.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_output.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_output.ts diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_update.ts similarity index 79% rename from x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_update.ts index a8fe28d54f24e..3098ede4f9712 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_ml_rule_update.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import type { RuleUpdateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; /** - * This is a representative ML rule payload as expected by the server + * This is a representative ML rule payload as expected by the server for an update * @param ruleId The rule id * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleMlRule = (ruleId = 'rule-1', enabled = false): RuleCreateProps => ({ +export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', enabled, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_as_ndjson.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_as_ndjson.ts new file mode 100644 index 0000000000000..fd416b1682b3d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_as_ndjson.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getSimpleRule } from './get_simple_rule'; + +/** + * Given an array of rule_id strings this will return a ndjson buffer which is useful + * for testing uploads. + * @param ruleIds Array of strings of rule_ids + */ +export const getSimpleRuleAsNdjson = (ruleIds: string[], enabled = false): Buffer => { + const stringOfRules = ruleIds.map((ruleId) => { + const simpleRule = getSimpleRule(ruleId, enabled); + return JSON.stringify(simpleRule); + }); + return Buffer.from(stringOfRules.join('\n')); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_update.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_update.ts new file mode 100644 index 0000000000000..6764a1d801dd5 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_rule_update.ts @@ -0,0 +1,25 @@ +/* + * 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 { RuleUpdateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * This is a typical simple rule for testing that is easy for most basic testing + * @param ruleId The rule id + * @param enabled Set to true to enable it, by default it is off + */ +export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ + name: 'Simple Rule Query', + description: 'Simple Rule Query', + enabled, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index: ['auditbeat-*'], + type: 'query', + query: 'user.name: root or user.name: admin', +}); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_saved_query_rule.ts similarity index 100% rename from x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/get_simple_saved_query_rule.ts 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 04c4cb778ef4a..90f3ae07871c8 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 @@ -4,43 +4,55 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -export * from './fetch_rule'; -export * from './get_simple_rule'; +export * from './create_legacy_rule_action'; export * from './create_rule'; +export * from './create_rule_with_exception_entries'; +export * from './create_rule_saved_object'; +export * from './create_rule_with_auth'; +export * from './create_non_security_rule'; +export * from './downgrade_immutable_rule'; export * from './delete_all_rules'; export * from './delete_rule'; +export * from './fetch_rule'; +export * from './find_immutable_rule_by_id'; +export * from './get_simple_rule'; export * from './get_rule_params'; export * from './get_simple_rule_output'; -export * from './remove_server_generated_properties'; -export * from './wait_for_rule_status'; +export * from './get_simple_rule_update'; export * from './get_rule_for_alert_testing'; export * from './get_threshold_rule_for_alert_testing'; export * from './get_rule_actions'; -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_simple_preview_rule'; export * from './get_simple_rule_preview_output'; 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 './get_threat_match_rule_for_alert_testing'; export * from './get_saved_query_rule_for_alert_testing'; export * from './get_rule_so_by_id'; -export * from './create_rule_saved_object'; export * from './get_rule_with_legacy_investigation_fields'; -export * from './create_rule_with_auth'; -export * from './preview_rule'; -export * from './preview_rule_with_exception_entries'; -export * from './patch_rule'; export * from './generate_event'; -export * from './create_legacy_rule_action'; export * from './get_simple_threat_match'; export * from './get_simple_ml_rule'; -export * from './remove_server_generated_properties_including_rule_id'; +export * from './get_simple_ml_rule_update'; +export * from './get_simple_ml_rule_output'; export * from './get_simple_rule_output_without_rule_id'; export * from './get_simple_rule_without_rule_id'; +export * from './get_simple_saved_query_rule'; +export * from './get_complex_rule'; +export * from './get_complex_rule_output'; +export * from './get_rule_params'; +export * from './get_simple_rule_as_ndjson'; +export * from './preview_rule'; +export * from './preview_rule_with_exception_entries'; +export * from './patch_rule'; +export * from './rule_to_ndjson'; +export * from './remove_server_generated_properties'; +export * from './remove_server_generated_properties_including_rule_id'; +export * from './rule_to_update_schema'; +export * from './update_rule'; +export * from './wait_for_rule_status'; +export * from './get_rules_as_ndjson'; +export * from './get_simple_rule_as_ndjson'; export * from './prebuilt_rules'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_ndjson.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_ndjson.ts new file mode 100644 index 0000000000000..404f3c1baa962 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/rules/rule_to_ndjson.ts @@ -0,0 +1,18 @@ +/* + * 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 } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * Given a rule this will convert it to an ndjson buffer which is useful for + * testing upload features. + * @param rule The rule to convert to ndjson + */ +export const ruleToNdjson = (rule: RuleCreateProps): Buffer => { + const stringified = JSON.stringify(rule); + return Buffer.from(`${stringified}\n`); +}; diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 6b1ca69f6aed1..77b7f288a5b00 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -37,5 +37,8 @@ "@kbn/fleet-plugin", "@kbn/repo-info", "@kbn/securitysolution-es-utils", + "@kbn/datemath", + "@kbn/safer-lodash-set", + "@kbn/stack-connectors-plugin", ] } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e5c89162f923d..e99384efa14cb 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -123,7 +123,6 @@ "@kbn/discover-plugin", "@kbn/files-plugin", "@kbn/shared-ux-file-types", - "@kbn/securitysolution-io-ts-alerting-types", "@kbn/alerting-state-types", "@kbn/assetManager-plugin", "@kbn/guided-onboarding-plugin",