diff --git a/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_customization.md b/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_customization.md index 66d234fa849f1..f695bc2da5716 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_customization.md +++ b/x-pack/solutions/security/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/prebuilt_rule_customization.md @@ -1,6 +1,6 @@ # Test plan: customizing prebuilt rules -**Status**: `in progress`, matches [Milestone 3](https://github.com/elastic/kibana/issues/174168). +**Status**: `implemented`, matches [Milestone 3](https://github.com/elastic/kibana/issues/174168). > [!TIP] > If you're new to prebuilt rules, get started [here](./prebuilt_rules.md) and check an overview of the features of prebuilt rules in [this section](./prebuilt_rules_common_info.md#features). @@ -16,8 +16,6 @@ This is a test plan for the workflows of customizing prebuilt rules via: - bulk adding or removing index patterns - bulk updating rule schedule -as well as un-customizing prebuilt rules by reverting rule parameters back to their original values. - ## Table of contents + +**Status**: `in progress`, matches [Milestone 3](https://github.com/elastic/kibana/issues/174168). + +> [!TIP] +> If you're new to prebuilt rules, get started [here](./prebuilt_rules.md) and check an overview of the features of prebuilt rules in [this section](./prebuilt_rules_common_info.md#features). + +## Summary + +This is a test plan for the workflows of reverting prebuilt rule customizations via the dedicated API. + +## Table of contents + + + +- [Useful information](#useful-information) + - [Tickets](#tickets) + - [Terminology](#terminology) +- [Requirements](#requirements) + - [Assumptions](#assumptions) + - [Technical requirements](#technical-requirements) + - [Product requirements](#product-requirements) +- [Scenarios](#scenarios) + - [Reverting a rule to stock version](#reverting-a-rule-to-stock-version) + - [**Scenario: Reverting prebuilt rule customizations**](#scenario-reverting-prebuilt-rule-customizations) + - [**Scenario: Showing a customizations diff view in the flyout**](#scenario-showing-a-customizations-diff-view-in-the-flyout) + - [**Scenario: Disabling the "Revert" prebuilt rule button when rule's base version is missing**](#scenario-disabling-the-revert-prebuilt-rule-button-when-rules-base-version-is-missing) + - [**Scenario: Hiding the "Revert" prebuilt rule button when the prebuilt rule is non-customized**](#scenario-hiding-the-revert-prebuilt-rule-button-when-the-prebuilt-rule-is-non-customized) + - [**Scenario: Returning an error for prebuilt rules with missing base version**](#scenario-returning-an-error-for-prebuilt-rules-with-missing-base-version) + - [**Scenario: Making no effect on a non-customized rule**](#scenario-making-no-effect-on-a-non-customized-rule) + - [**Scenario: Returning an error for custom rules**](#scenario-returning-an-error-for-custom-rules) + - [**Scenario: Reverting a prebuilt rule doesn't modify customization adjacent fields**](#scenario-reverting-a-prebuilt-rule-doesnt-modify-customization-adjacent-fields) + - [Reverting a rule to stock version: Concurrency control](#reverting-a-rule-to-stock-version-concurrency-control) + - [**Scenario: Returning an error when someone changed the prebuilt rule concurrently**](#scenario-returning-an-error-when-someone-changed-the-prebuilt-rule-concurrently) + - [**Scenario: Returning an error when someone updated the prebuilt rule concurrently**](#scenario-returning-an-error-when-someone-updated-the-prebuilt-rule-concurrently) + - [**Scenario: Notifying the user when the prebuilt rule's base version has disappeared**](#scenario-notifying-the-user-when-the-prebuilt-rules-base-version-has-disappeared) + +## Useful information + +### Tickets + +- [Users can Customize Prebuilt Detection Rules](https://github.com/elastic/security-team/issues/1974) (internal) +- [Users can Customize Prebuilt Detection Rules: Milestone 3](https://github.com/elastic/kibana/issues/174168) +- [Relax the rules of handling missing base versions of prebuilt rules](https://github.com/elastic/kibana/issues/210358) +- [Tests for prebuilt rule customization workflow](https://github.com/elastic/kibana/issues/202068) + +### Terminology + +- [Common terminology](./prebuilt_rules_common_info.md#common-terminology). +- **Rule source**, or **`ruleSource`**: a rule field that defines the rule's origin. Can be `internal` or `external`. Currently, custom rules have `internal` rule source and prebuilt rules have `external` rule source. +- **`is_customized`**: a field within `ruleSource` that exists when rule source is set to `external`. It is a boolean value based on if the rule has been changed from its base version. +- **rule customization**: a change to a customizable field of a prebuilt rule. Full list of customizable rule fields can be found in [Common information about prebuilt rules](./prebuilt_rules_common_info.md#customizable-rule-fields). + +## Requirements + +### Assumptions + +Assumptions about test environments and scenarios outlined in this test plan. + +- [Common assumptions](./prebuilt_rules_common_info.md#common-assumptions). +- Package with prebuilt rules is already installed, and rule assets from it are stored in Elasticsearch. + +### Technical requirements + +Non-functional requirements for the functionality outlined in this test plan. + +- [Common technical requirements](./prebuilt_rules_common_info.md#common-technical-requirements). + +### Product requirements + +Functional requirements for the functionality outlined in this test plan. + +- [Common product requirements](./prebuilt_rules_common_info.md#common-product-requirements). + +User stories: + +- User can edit a single prebuilt rule from the Rule Details page. +- User can edit single prebuilt rules one-by-one from the Rule Management page. +- User can edit multiple prebuilt rules in bulk via bulk actions on the Rule Management page. For example: + - User can bulk add index patterns to prebuilt rules. + - User can bulk update rule schedule in prebuilt rules. +- User can customize most of the fields of prebuilt rules: + - User can edit and customize almost any field of a prebuilt rule, just like it's possible to do with custom rules, via editing it directly or via bulk editing via bulk actions. + - User can't edit the Author and License fields. +- User can see if the rule is customized on the Rule Details page. +- User can see which rules are customized on the Rule Management page in the Upgrade table. +- User can un-customize a prebuilt rule by editing it and reverting its parameters back to their original values. + +## Scenarios + +### Reverting a rule to stock version + +#### **Scenario: Reverting prebuilt rule customizations** + +**Automation**: 1 cypress test and 1 integration test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +When user reverts that rule customizations +Then rule customizations should be reset +And rule data should match the base version +And the rule's `is_customized` value should be false +``` + +#### **Scenario: Showing a customizations diff view in the flyout** + +**Automation**: 1 cypress test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +When a user clicks the "Revert" rule's action button on the rule's details page +Then a rule diff flyout should open +And this flyout should display a per field JSON diff view +And this flyout should list all fields that are different between the current and base version +And this flyout should contain a button to revert the rule +``` + +#### **Scenario: Disabling the "Revert" prebuilt rule button when rule's base version is missing** + +**Automation**: 1 cypress test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule does not have an existing base version +When user navigates to that rule's details page +And clicks the overflow actions button +Then the "Revert" rule button should be disabled +And have an informational tooltip on hover +``` + +#### **Scenario: Hiding the "Revert" prebuilt rule button when the prebuilt rule is non-customized** + +**Automation**: 1 cypress test. + +```Gherkin +Given a prebuilt rule installed +And that rule is non-customized +When user clicks the overflow actions button on the rule's details page +Then the revert rule button should not be displayed as an option +``` + +#### **Scenario: Returning an error for prebuilt rules with missing base version** + +**Automation**: 1 integration test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule does not have an existing base version +When user makes a request to revert the rule customizations +Then API should return a 500 HTTP error +And the rule should stay unchanged +``` + +#### **Scenario: Making no effect on a non-customized rule** + +**Automation**: 1 integration test. + +```Gherkin +Given a prebuilt rule installed +And that rule is non-customized +And that rule has an existing base version +When user makes a request to revert the rule customizations +Then API should return a successful response +And the rule should stay unchanged +``` + +#### **Scenario: Returning an error for custom rules** + +**Automation**: 1 integration test. + +```Gherkin +Given a custom rule +When user makes a request to revert the rule customizations +Then API should return a 500 HTTP error +And the rule should remain the same +``` + +#### **Scenario: Reverting a prebuilt rule doesn't modify customization adjacent fields** + +**Automation**: one integration test per field. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +And that rule has a custom field different from the base version +When user makes a request to revert the rule customizations +Then the rule's `is_customized` value should be false +And the field stay unchanged +``` + +**Examples:** + +`` = all customization adjacent fields + +### Reverting a rule to stock version: Concurrency control + +#### **Scenario: Returning an error when someone changed the prebuilt rule concurrently** + +**Automation**: 3 integration tests. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +And userA has that prebuilt rule concurrently +When userB makes a request to revert the rule +When a user calls the revert rule API endpoint with an outdated revision field +Then the API should return a 500 HTTP error +And the rule should stay unchanged +``` + +**Examples:** + +`` is + +- customizing the same fields +- customizing the other fields +- reverting the customization via rule edit +- reverting the customization via "Revert" action +- upgrading the rule + +#### **Scenario: Returning an error when someone updated the prebuilt rule concurrently** + +**Automation**: 1 integration test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +And userA has upgraded that prebuilt rule concurrently +When userB makes a request to revert the rule +Then the API should return a 500 HTTP error +And the rule should stay unchanged +``` + +#### **Scenario: Notifying the user when the prebuilt rule's base version has disappeared** + +**Automation**: 1 integration test. + +```Gherkin +Given a prebuilt rule installed +And that rule is customized +And that rule has an existing base version +When user opens a revert rule flyout +And that rule's base version +Then a notification regarding missing base version should be shown +And the flyout should be blocked +``` + +**Examples:** + +`` is + +- base version got removed manually +- a new prebuilt rules package has been installed and it doesn't contain the base rule version diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/calculate_is_customized.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/calculate_is_customized.ts deleted file mode 100644 index e6e05a6517c74..0000000000000 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/calculate_is_customized.ts +++ /dev/null @@ -1,138 +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 'expect'; -import { - BulkActionEditTypeEnum, - BulkActionTypeEnum, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { deleteAllRules } from '../../../../../../config/services/detections_response'; -import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -import { - deleteAllPrebuiltRuleAssets, - createRuleAssetSavedObject, - createPrebuiltRuleAssetSavedObjects, - installPrebuiltRules, -} from '../../../../utils'; - -export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); - const securitySolutionApi = getService('securitySolutionApi'); - const log = getService('log'); - const es = getService('es'); - - const ruleAsset = createRuleAssetSavedObject({ - rule_id: 'test-rule-id', - }); - - describe('@ess @serverless @skipInServerlessMKI Calculate "is_customized"', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('should not allow prebuilt rule customization on import', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - - const { body: findResult } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - const prebuiltRule = findResult.data[0]; - - // Check that the rule has been created and is not customized - expect(prebuiltRule).not.toBeNull(); - expect(prebuiltRule.rule_source.is_customized).toEqual(false); - - const ruleBuffer = Buffer.from( - JSON.stringify({ - ...prebuiltRule, - name: 'some other rule name', - }) - ); - - const { body } = await securitySolutionApi - .importRules({ query: { overwrite: true } }) - .attach('file', ruleBuffer, 'rules.ndjson') - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); - - expect(body).toMatchObject({ - rules_count: 1, - success: true, - success_count: 1, - errors: [], - }); - - // Check that the imported rule is customized - const { body: importedRule } = await securitySolutionApi.readRule({ - query: { rule_id: prebuiltRule.rule_id }, - }); - expect(importedRule.rule_source.is_customized).toEqual(true); - }); - - it('should not allow rule customization on bulk edit', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - - const { body: findResult } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - const prebuiltRule = findResult.data[0]; - - // Check that the rule has been created and is not customized - expect(prebuiltRule).not.toBeNull(); - expect(prebuiltRule.rule_source.is_customized).toEqual(false); - - const { body: bulkResult } = await securitySolutionApi - .performRulesBulkAction({ - query: {}, - body: { - ids: [prebuiltRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.add_tags, - value: ['test'], - }, - ], - }, - }) - .expect(500); - - expect(bulkResult).toMatchObject( - expect.objectContaining({ - attributes: expect.objectContaining({ - summary: { - failed: 1, - skipped: 0, - succeeded: 0, - total: 1, - }, - errors: [expect.objectContaining({ message: "Elastic rule can't be edited" })], - }), - }) - ); - - // Check that the rule has not been customized - const { body: ruleAfterUpdate } = await securitySolutionApi.readRule({ - query: { rule_id: prebuiltRule.rule_id }, - }); - expect(ruleAfterUpdate.rule_source.is_customized).toEqual(false); - }); - }); -}; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/index.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/index.ts index c5b37f0817a3a..38360c9c1ffd4 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/index.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/index.ts @@ -8,6 +8,5 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { - loadTestFile(require.resolve('./calculate_is_customized')); - loadTestFile(require.resolve('./customize_via_bulk_editing')); + loadTestFile(require.resolve('./unable_to_customize_via_bulk_editing')); }; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/customize_via_bulk_editing.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/unable_to_customize_via_bulk_editing.ts similarity index 98% rename from x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/customize_via_bulk_editing.ts rename to x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/unable_to_customize_via_bulk_editing.ts index 7784d1c77073e..f7280716b2280 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/customize_via_bulk_editing.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_disabled/customization/unable_to_customize_via_bulk_editing.ts @@ -34,7 +34,7 @@ export default ({ getService }: FtrProviderContext): void => { return prebuiltRule; }; - describe('@ess @serverless @skipInServerless Customize via bulk editing', () => { + describe('@ess @serverless @skipInServerless Customization under insufficient license', () => { const bulkEditingCases = [ { type: BulkActionEditTypeEnum.add_tags, diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/calculate_is_customized.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/calculate_is_customized.ts deleted file mode 100644 index 7db14b1407015..0000000000000 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/calculate_is_customized.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from 'expect'; -import { - BulkActionEditTypeEnum, - BulkActionTypeEnum, -} from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { deleteAllRules } from '../../../../../../config/services/detections_response'; -import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -import { - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, - deleteAllPrebuiltRuleAssets, - installPrebuiltRules, -} from '../../../../utils'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const securitySolutionApi = getService('securitySolutionApi'); - const log = getService('log'); - - const ruleAsset = createRuleAssetSavedObject({ - rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19', - tags: ['test-tag'], - }); - - describe('@ess @serverless @skipInServerlessMKI Calculate "is_customized"', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('sets "is_customized" to true on bulk prebuilt rule modification', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - - const { body: findResult } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - const prebuiltRule = findResult.data[0]; - expect(prebuiltRule).toBeDefined(); - expect(prebuiltRule.rule_source.is_customized).toEqual(false); - - const { body: bulkResult } = await securitySolutionApi - .performRulesBulkAction({ - query: {}, - body: { - ids: [prebuiltRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.add_tags, - value: ['new-tag'], - }, - ], - }, - }) - .expect(200); - - expect(bulkResult.attributes.summary).toEqual({ - failed: 0, - skipped: 0, - succeeded: 1, - total: 1, - }); - expect(bulkResult.attributes.results.updated[0].rule_source.is_customized).toEqual(true); - }); - - it('leaves "is_customized" intact if the change has been skipped', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - - const { body: findResult } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - const prebuiltRule = findResult.data[0]; - expect(prebuiltRule).toBeDefined(); - expect(prebuiltRule.rule_source.is_customized).toEqual(false); - - const { body: bulkResult } = await securitySolutionApi - .performRulesBulkAction({ - query: {}, - body: { - ids: [prebuiltRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.add_tags, - // This tag is already present on the rule, so the change will be skipped - value: [prebuiltRule.tags[0]], - }, - ], - }, - }) - .expect(200); - - expect(bulkResult.attributes.summary).toEqual({ - failed: 0, - skipped: 1, - succeeded: 0, - total: 1, - }); - - // Check that the rule has not been customized - const { body: findResultAfter } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - expect(findResultAfter.data[0].rule_source.is_customized).toEqual(false); - }); - - it('sets "is_customized" to false if the change has been reverted', async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - - const { body: findResult } = await securitySolutionApi - .findRules({ - query: { - per_page: 1, - filter: `alert.attributes.params.immutable: true`, - }, - }) - .expect(200); - const prebuiltRule = findResult.data[0]; - expect(prebuiltRule).toBeDefined(); - expect(prebuiltRule.rule_source.is_customized).toEqual(false); - - // Add a tag to the rule - const { body: bulkResult } = await securitySolutionApi - .performRulesBulkAction({ - query: {}, - body: { - ids: [prebuiltRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.add_tags, - value: ['new-tag'], - }, - ], - }, - }) - .expect(200); - - expect(bulkResult.attributes.summary).toEqual({ - failed: 0, - skipped: 0, - succeeded: 1, - total: 1, - }); - - // Remove the added tag - const { body: revertResult } = await securitySolutionApi - .performRulesBulkAction({ - query: {}, - body: { - ids: [prebuiltRule.id], - action: BulkActionTypeEnum.edit, - [BulkActionTypeEnum.edit]: [ - { - type: BulkActionEditTypeEnum.delete_tags, - value: ['new-tag'], - }, - ], - }, - }) - .expect(200); - - expect(revertResult.attributes.summary).toEqual({ - failed: 0, - skipped: 0, - succeeded: 1, - total: 1, - }); - - expect(revertResult.attributes.results.updated[0].rule_source.is_customized).toEqual(false); - }); - }); -}; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_prebuilt_rules.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_prebuilt_rules.ts deleted file mode 100644 index 161930df7cde0..0000000000000 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_prebuilt_rules.ts +++ /dev/null @@ -1,924 +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 'expect'; -import { - getPrebuiltRuleMock, - getPrebuiltRuleMockOfType, -} from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; -import { deleteAllRules } from '../../../../../../config/services/detections_response'; -import { FtrProviderContext } from '../../../../../../ftr_provider_context'; -import { - createPrebuiltRuleAssetSavedObjects, - createRuleAssetSavedObject, - deleteAllPrebuiltRuleAssets, - getWebHookAction, - installPrebuiltRules, -} from '../../../../utils'; - -export default ({ getService }: FtrProviderContext): void => { - const es = getService('es'); - const supertest = getService('supertest'); - const securitySolutionApi = getService('securitySolutionApi'); - const log = getService('log'); - - const ruleAsset = createRuleAssetSavedObject({ - rule_id: 'rule_1', - }); - - describe('@ess @serverless @skipInServerlessMKI Customize prebuilt rules', () => { - before(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - describe('detecting rule customizations', () => { - describe('common rule fields', () => { - beforeEach(async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('name field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', name: 'some other name' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('description field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', description: 'some other description' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('interval field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', interval: '30m' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('from field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', from: 'now-10m' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('to field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', to: 'now-1m' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('note field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', note: '# some note markdown' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('severity field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', severity: 'medium' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('tags field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', tags: ['red fish', 'blue fish'] } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('severity_mapping field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - severity_mapping: [ - { - field: 'event.severity', - operator: 'equals', - severity: 'low', - value: 'LOW', - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('risk_score field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', risk_score: 72 } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('risk_score_mapping field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - risk_score_mapping: [{ field: 'event.risk_score', operator: 'equals', value: '' }], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('references field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', references: ['http://test.test'] } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('false_positives field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', false_positives: ['false positive example'] } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - threat: [ - { - framework: 'MITRE ATT&CK', - tactic: { - id: 'TA0000', - name: 'test tactic', - reference: 'https://attack.mitre.org/tactics/TA0000/', - }, - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('setup field', async () => { - const { body } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', setup: '# some setup markdown' } }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('related_integrations field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - related_integrations: [{ package: 'package-a', version: '^1.2.3' }], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('required_fields field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - required_fields: [{ name: '@timestamp', type: 'date' }], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('max_signals field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - max_signals: 42, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('investigation_fields field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', investigation_fields: { field_names: ['blob', 'boop'] } }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('rule_name_override field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', rule_name_override: 'override string' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('timestamp_override field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', timestamp_override: 'event.ingested' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('timeline_template fields', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', timeline_id: '123', timeline_title: 'timeline title' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('building_block_type field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', building_block_type: 'building block string' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('query rule fields', () => { - beforeEach(async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('query field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', query: 'event.action: *' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('language field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', language: 'lucene' }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('filters field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - filters: [ - { - meta: { - negate: false, - disabled: false, - type: 'phrase', - key: 'test', - params: { - query: 'value', - }, - }, - query: { - term: { - field: 'value', - }, - }, - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('index field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', index: ['new-index-pattern-*'] }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('data_view_id field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { rule_id: 'rule_1', data_view_id: 'new-data-view', index: [] }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('alert_suppression field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - alert_suppression: { - group_by: ['host.name'], - duration: { value: 5, unit: 'm' }, - missing_fields_strategy: 'suppress', - }, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('eql rule type fields', () => { - beforeEach(async () => { - const eqlRuleAsset = createRuleAssetSavedObject({ - ...getPrebuiltRuleMockOfType('eql'), - rule_id: 'rule_1', - }); - - await createPrebuiltRuleAssetSavedObjects(es, [eqlRuleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('event_category_override field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - event_category_override: 'host.name', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('timestamp_field field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - timestamp_field: 'event.ingested', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('tiebreaker_field field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - tiebreaker_field: 'event.ingested', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('threat match rule type fields', () => { - beforeEach(async () => { - const threatMatchRuleAsset = createRuleAssetSavedObject({ - ...getPrebuiltRuleMockOfType('threat_match'), - rule_id: 'rule_1', - }); - - await createPrebuiltRuleAssetSavedObjects(es, [threatMatchRuleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('threat_index field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_index: ['blue fish', 'red fish'], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat_mapping field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_mapping: [ - { - entries: [ - { - field: 'Endpoint.capabilities', - type: 'mapping', - value: 'Target.dll.pe.description', - }, - ], - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat_indicator_path field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_indicator_path: 'C:over/there.exe', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat_query field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_query: 'event.action: *', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat_language field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_language: 'lucene', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('threat_filters field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threat_match', - threat_filters: [ - { - meta: { - negate: false, - disabled: false, - type: 'phrase', - key: 'test', - params: { - query: 'value', - }, - }, - query: { - term: { - field: 'value', - }, - }, - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('threshold rule type fields', () => { - beforeEach(async () => { - const thresholdRuleAsset = createRuleAssetSavedObject({ - ...getPrebuiltRuleMockOfType('threshold'), - rule_id: 'rule_1', - }); - - await createPrebuiltRuleAssetSavedObjects(es, [thresholdRuleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('threshold field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'threshold', - threshold: { - field: ['Responses.process.pid'], - value: 100, - cardinality: [{ field: 'host.id', value: 2 }], - }, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('machine learning rule type fields', () => { - beforeEach(async () => { - const machineLearningRuleAsset = createRuleAssetSavedObject({ - ...getPrebuiltRuleMockOfType('machine_learning'), - rule_id: 'rule_1', - }); - - await createPrebuiltRuleAssetSavedObjects(es, [machineLearningRuleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('machine_learning_job_id field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'machine_learning', - machine_learning_job_id: '123', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('anomaly_threshold field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'machine_learning', - anomaly_threshold: 20, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('new terms rule type fields', () => { - beforeEach(async () => { - const newTermsRuleAsset = createRuleAssetSavedObject({ - ...getPrebuiltRuleMockOfType('new_terms'), - rule_id: 'rule_1', - }); - - await createPrebuiltRuleAssetSavedObjects(es, [newTermsRuleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('new_terms_fields field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'new_terms', - new_terms_fields: ['event.action'], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - - it('history_window_start field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - type: 'new_terms', - history_window_start: 'now-7d', - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(true); - expect(body.rule_source.type).toEqual('external'); - }); - }); - }); - - describe('is_customized calculation is not affected by', () => { - beforeEach(async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('actions field', async () => { - // create connector/action - const createConnector = async (payload: Record) => - ( - await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(payload) - .expect(200) - ).body; - - const createWebHookConnector = () => createConnector(getWebHookAction()); - - const webHookAction = await createWebHookConnector(); - - const defaultRuleAction = { - id: webHookAction.id, - action_type_id: '.webhook' as const, - group: 'default' as const, - params: { - body: '{"test":"a default action"}', - }, - frequency: { - notifyWhen: 'onThrottleInterval' as const, - summary: true, - throttle: '1h' as const, - }, - uuid: 'd487ec3d-05f2-44ad-8a68-11c97dc92202', - }; - - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - actions: [defaultRuleAction], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(false); - expect(body.rule_source.type).toEqual('external'); - }); - - it('exceptions_list field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - exceptions_list: [ - { - id: 'some_uuid', - list_id: 'list_id_single', - namespace_type: 'single', - type: 'detection', - }, - ], - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(false); - expect(body.rule_source.type).toEqual('external'); - }); - - it('enabled field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - enabled: true, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(false); - expect(body.rule_source.type).toEqual('external'); - }); - - it('meta field', async () => { - const { body } = await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - meta: { - severity_override_field: 'field', - }, - }, - }) - .expect(200); - - expect(body.rule_source.is_customized).toEqual(false); - expect(body.rule_source.type).toEqual('external'); - }); - }); - - describe('cannot change non-customizable rule fields', () => { - beforeEach(async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('id field', async () => { - await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - id: 'new-id', - }, - }) - .expect(400); - }); - - it('author field', async () => { - await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - author: ['new author'], - }, - }) - .expect(400); - }); - - it('license field', async () => { - await securitySolutionApi - .patchRule({ - body: { - rule_id: 'rule_1', - license: 'custom-license', - }, - }) - .expect(400); - }); - }); - - describe('user can revert a customized prebuilt rule to its non-customized state', () => { - beforeEach(async () => { - await createPrebuiltRuleAssetSavedObjects(es, [ruleAsset]); - await installPrebuiltRules(es, supertest); - }); - - afterEach(async () => { - await deleteAllRules(supertest, log); - await deleteAllPrebuiltRuleAssets(es, log); - }); - - it('using name field', async () => { - // Modify the prebuilt rule - const { body: customizedRuleBody } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', name: 'new name' } }) - .expect(200); - - expect(customizedRuleBody.rule_source.is_customized).toEqual(true); - expect(customizedRuleBody.rule_source.type).toEqual('external'); - - // Change the name field back to the original value - const { body: nonCustomizedRuleBody } = await securitySolutionApi - .patchRule({ body: { rule_id: 'rule_1', name: getPrebuiltRuleMock().name } }) - .expect(200); - - expect(nonCustomizedRuleBody.rule_source.is_customized).toEqual(false); - expect(nonCustomizedRuleBody.rule_source.type).toEqual('external'); - }); - }); - }); -}; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_via_bulk_editing.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_via_bulk_editing.ts index 9e95c73765897..14152146c1fd1 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_via_bulk_editing.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/customize_via_bulk_editing.ts @@ -92,12 +92,14 @@ export default ({ getService }: FtrProviderContext): void => { ): Promise => { const { body: { data: prebuiltRules }, - } = await securitySolutionApi.findRules({ - query: { - filter: 'alert.attributes.params.immutable: true', - per_page: PREBUILT_RULE_ASSETS.length, - }, - }); + } = await securitySolutionApi + .findRules({ + query: { + filter: 'alert.attributes.params.immutable: true', + per_page: PREBUILT_RULE_ASSETS.length, + }, + }) + .expect(200); const { body: bulkEditResponse } = await securitySolutionApi .performRulesBulkAction({ @@ -122,256 +124,225 @@ export default ({ getService }: FtrProviderContext): void => { return bulkEditResponse; }; - it(`applies "${BulkActionEditTypeEnum.add_tags}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); + const testCustomizationViaBulkEditing = ({ hasBaseVersion }: { hasBaseVersion: boolean }) => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); + await installPrebuiltRules(es, supertest); - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.add_tags, - value: ['new-tag'], + if (!hasBaseVersion) { + // Remove the prebuilt rule asset so that the base version is no longer available + await deleteAllPrebuiltRuleAssets(es, log); + } }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.set_tags}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.set_tags, - value: ['new-tag'], + it(`applies "${BulkActionEditTypeEnum.add_tags}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.add_tags, + value: ['new-tag'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + tags: ['existing-tag-1', 'existing-tag-2', 'new-tag'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - tags: ['new-tag'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - tags: ['new-tag'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - tags: ['new-tag'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.delete_tags}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.delete_tags, - value: ['existing-tag-1'], + it(`applies "${BulkActionEditTypeEnum.set_tags}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.set_tags, + value: ['new-tag'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + tags: ['new-tag'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + tags: ['new-tag'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + tags: ['new-tag'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - tags: ['existing-tag-2'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - tags: ['existing-tag-2'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - tags: ['existing-tag-2'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.delete_index_patterns}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.delete_index_patterns, - value: ['existing-index-pattern-1'], + it(`applies "${BulkActionEditTypeEnum.delete_tags}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.delete_tags, + value: ['existing-tag-1'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + tags: ['existing-tag-2'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + tags: ['existing-tag-2'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + tags: ['existing-tag-2'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-2'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-2'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - index: ['existing-index-pattern-2'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.add_index_patterns}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.add_index_patterns, - value: ['test-*'], + it(`applies "${BulkActionEditTypeEnum.delete_index_patterns}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.delete_index_patterns, + value: ['existing-index-pattern-1'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + index: ['existing-index-pattern-2'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + index: ['existing-index-pattern-2'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + index: ['existing-index-pattern-2'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.add_index_patterns}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.add_index_patterns, - value: ['test-*'], + it(`applies "${BulkActionEditTypeEnum.add_index_patterns}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.add_index_patterns, + value: ['test-*'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - index: ['existing-index-pattern-1', 'existing-index-pattern-2', 'test-*'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.set_index_patterns}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.set_index_patterns, - value: ['test-*'], + it(`applies "${BulkActionEditTypeEnum.set_index_patterns}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.set_index_patterns, + value: ['test-*'], + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + index: ['test-*'], + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + index: ['test-*'], + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + index: ['test-*'], + }), + ]) + ); }); - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - index: ['test-*'], - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - index: ['test-*'], - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - index: ['test-*'], - }), - ]) - ); - }); - - it(`applies "${BulkActionEditTypeEnum.set_timeline}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); + it(`applies "${BulkActionEditTypeEnum.set_timeline}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.set_timeline, + value: { timeline_id: 'mock-id', timeline_title: 'mock-title' }, + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + timeline_id: 'mock-id', + timeline_title: 'mock-title', + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + timeline_id: 'mock-id', + timeline_title: 'mock-title', + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + timeline_id: 'mock-id', + timeline_title: 'mock-title', + }), + ]) + ); + }); - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.set_timeline, - value: { timeline_id: 'mock-id', timeline_title: 'mock-title' }, + it(`applies "${BulkActionEditTypeEnum.set_schedule}" bulk edit action to prebuilt rules`, async () => { + const bulkResponse = await performBulkEditOnPrebuiltRules({ + type: BulkActionEditTypeEnum.set_schedule, + value: { interval: '1m', lookback: '1m' }, + }); + + expect(bulkResponse.attributes.results.updated).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: QUERY_PREBUILT_RULE_ID, + interval: '1m', + from: 'now-120s', + to: 'now', + }), + expect.objectContaining({ + rule_id: SAVED_QUERY_PREBUILT_RULE_ID, + interval: '1m', + from: 'now-120s', + to: 'now', + }), + expect.objectContaining({ + rule_id: EQL_PREBUILT_RULE_ID, + interval: '1m', + from: 'now-120s', + to: 'now', + }), + ]) + ); }); + }; - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - timeline_id: 'mock-id', - timeline_title: 'mock-title', - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - timeline_id: 'mock-id', - timeline_title: 'mock-title', - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - timeline_id: 'mock-id', - timeline_title: 'mock-title', - }), - ]) - ); + describe('when base version is available', () => { + testCustomizationViaBulkEditing({ hasBaseVersion: false }); }); - it(`applies "${BulkActionEditTypeEnum.set_schedule}" bulk edit action to prebuilt rules`, async () => { - await createPrebuiltRuleAssetSavedObjects(es, PREBUILT_RULE_ASSETS); - await installPrebuiltRules(es, supertest); - - const bulkResponse = await performBulkEditOnPrebuiltRules({ - type: BulkActionEditTypeEnum.set_schedule, - value: { interval: '1m', lookback: '1m' }, - }); - - expect(bulkResponse.attributes.results.updated).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - rule_id: QUERY_PREBUILT_RULE_ID, - interval: '1m', - from: 'now-120s', - to: 'now', - }), - expect.objectContaining({ - rule_id: SAVED_QUERY_PREBUILT_RULE_ID, - interval: '1m', - from: 'now-120s', - to: 'now', - }), - expect.objectContaining({ - rule_id: EQL_PREBUILT_RULE_ID, - interval: '1m', - from: 'now-120s', - to: 'now', - }), - ]) - ); + describe('when base version is missing', () => { + testCustomizationViaBulkEditing({ hasBaseVersion: false }); }); }); }; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_with_base_version.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_with_base_version.ts new file mode 100644 index 0000000000000..dbbd6a8a40ed0 --- /dev/null +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_with_base_version.ts @@ -0,0 +1,665 @@ +/* + * 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 type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getPrebuiltRuleMockOfType } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import { deleteAllRules } from '../../../../../../config/services/detections_response'; +import type { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + installPrebuiltRules, +} from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Detect prebuilt rule customization (base version exists)', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const testFieldCustomization = async ({ + fieldName, + customizedValue, + ruleType, + }: { + fieldName: string; + customizedValue: unknown; + // Rule type shouldn't be required to customize prebuilt rules. + // However prebuilt rules customization doesn't work as expected when rule type + // isn't provided for non custom query rule types. + ruleType?: RuleResponse['type']; + }) => { + const { body: nonCustomizedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: PREBUILT_RULE_ID }, + }) + .expect(200); + + // Assert the customization for "fieldName" works + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { rule_id: PREBUILT_RULE_ID, type: ruleType, [fieldName]: customizedValue }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + + // Assert that patching the "fieldName" to its original value reverts the customization + const { body: customizationRevertedResponse } = await securitySolutionApi + .updateRule({ + body: { ...nonCustomizedRule, id: undefined }, + }) + .expect(200); + + expect(customizationRevertedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: false, + }); + }; + + describe('common rule fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"name" field', () => + testFieldCustomization({ + fieldName: 'name', + customizedValue: 'some other name', + })); + + it('"description" field', () => + testFieldCustomization({ + fieldName: 'description', + customizedValue: 'some other description', + })); + + it('"note" field', () => + testFieldCustomization({ + fieldName: 'note', + customizedValue: '# some note markdown', + })); + + it('"severity" field', () => + testFieldCustomization({ + fieldName: 'severity', + customizedValue: 'medium', + })); + + it('"tags" field', () => + testFieldCustomization({ + fieldName: 'tags', + customizedValue: ['red fish', 'blue fish'], + })); + + it('"severity_mapping" field', () => + testFieldCustomization({ + fieldName: 'severity_mapping', + customizedValue: [ + { + field: 'event.severity', + operator: 'equals', + severity: 'low', + value: 'LOW', + }, + ], + })); + + it('"risk_score" field', () => + testFieldCustomization({ + fieldName: 'risk_score', + customizedValue: 72, + })); + + it('"risk_score_mapping" field', () => + testFieldCustomization({ + fieldName: 'risk_score_mapping', + customizedValue: [{ field: 'event.risk_score', operator: 'equals', value: '' }], + })); + + it('"references" field', () => + testFieldCustomization({ + fieldName: 'references', + customizedValue: ['http://test.test'], + })); + + it('"false_positives" field', () => + testFieldCustomization({ + fieldName: 'false_positives', + customizedValue: ['false positive example'], + })); + + it('"threat" field', () => + testFieldCustomization({ + fieldName: 'threat', + customizedValue: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0000', + name: 'test tactic', + reference: 'https://attack.mitre.org/tactics/TA0000/', + }, + }, + ], + })); + + it('"setup" field', () => + testFieldCustomization({ + fieldName: 'setup', + customizedValue: '# some setup markdown', + })); + + it('"related_integrations" field', () => + testFieldCustomization({ + fieldName: 'related_integrations', + customizedValue: [{ package: 'package-a', version: '^1.2.3' }], + })); + + it('"required_fields" field', () => + testFieldCustomization({ + fieldName: 'required_fields', + customizedValue: [{ name: '@timestamp', type: 'date' }], + })); + + it('"max_signals" field', () => + testFieldCustomization({ + fieldName: 'max_signals', + customizedValue: 42, + })); + + it('"investigation_fields" field', () => + testFieldCustomization({ + fieldName: 'investigation_fields', + customizedValue: { field_names: ['blob', 'boop'] }, + })); + + it('"rule_name_override" field', () => + testFieldCustomization({ + fieldName: 'rule_name_override', + customizedValue: 'override string', + })); + + it('"timestamp_override" field', () => + testFieldCustomization({ + fieldName: 'timestamp_override', + customizedValue: 'event.ingested', + })); + + it('"timeline_template" fields', async () => { + const { body: nonCustomizedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: PREBUILT_RULE_ID }, + }) + .expect(200); + + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { + rule_id: PREBUILT_RULE_ID, + timeline_id: '123', + timeline_title: 'timeline title', + }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + + const { body: customizationRevertedResponse } = await securitySolutionApi + .updateRule({ + body: { + ...nonCustomizedRule, + id: undefined, + }, + }) + .expect(200); + + expect(customizationRevertedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: false, + }); + }); + + it('"building_block_type" field', () => + testFieldCustomization({ + fieldName: 'building_block_type', + customizedValue: 'building block string', + })); + + describe('rule schedule', () => { + it('"interval" field', () => + testFieldCustomization({ + fieldName: 'interval', + customizedValue: '30m', + })); + + it('"from" field', () => + testFieldCustomization({ + fieldName: 'from', + customizedValue: 'now-10m', + })); + + it('"to" field', () => + testFieldCustomization({ + fieldName: 'to', + customizedValue: 'now-1m', + })); + }); + }); + + describe('custom query rule fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'event.action: *', + })); + + it('"language" field', () => + testFieldCustomization({ + fieldName: 'language', + customizedValue: 'lucene', + })); + + it('"filters" field', () => + testFieldCustomization({ + fieldName: 'filters', + customizedValue: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + })); + + it('"index" field', () => + testFieldCustomization({ + fieldName: 'index', + customizedValue: ['new-index-pattern-*'], + })); + + it('"data_view_id" field', async () => { + const { body: nonCustomizedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: PREBUILT_RULE_ID }, + }) + .expect(200); + + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { rule_id: PREBUILT_RULE_ID, data_view_id: 'new-data-view', index: [] }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + + const { body: customizationRevertedResponse } = await securitySolutionApi + .updateRule({ + body: { + ...nonCustomizedRule, + id: undefined, + }, + }) + .expect(200); + + expect(customizationRevertedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: false, + }); + }); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('saved query rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [SAVED_QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"saved_id" field', () => + testFieldCustomization({ + fieldName: 'saved_id', + customizedValue: 'customized-saved-query-id', + ruleType: 'saved_query', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('EQL rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [EQL_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'process where process.name == "some_process"', + })); + + it('"event_category_override" field', () => + testFieldCustomization({ + fieldName: 'event_category_override', + customizedValue: 'host.name', + })); + + it('"timestamp_field" field', () => + testFieldCustomization({ + fieldName: 'timestamp_field', + customizedValue: 'event.ingested', + })); + + it('"tiebreaker_field" field', () => + testFieldCustomization({ + fieldName: 'tiebreaker_field', + customizedValue: 'event.ingested', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('threat match rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [THREAT_MATCH_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"threat_index" field', () => + testFieldCustomization({ + fieldName: 'threat_index', + customizedValue: ['blue fish', 'red fish'], + ruleType: 'threat_match', + })); + + it('"threat_mapping" field', () => + testFieldCustomization({ + fieldName: 'threat_mapping', + customizedValue: [ + { + entries: [ + { + field: 'Endpoint.capabilities', + type: 'mapping', + value: 'Target.dll.pe.description', + }, + ], + }, + ], + ruleType: 'threat_match', + })); + + it('"threat_indicator_path" field', () => + testFieldCustomization({ + fieldName: 'threat_indicator_path', + customizedValue: 'C:over/there.exe', + ruleType: 'threat_match', + })); + + it('"threat_query" field', () => + testFieldCustomization({ + fieldName: 'threat_query', + customizedValue: 'event.action: *', + ruleType: 'threat_match', + })); + + it('"threat_language" field', () => + testFieldCustomization({ + fieldName: 'threat_language', + customizedValue: 'lucene', + ruleType: 'threat_match', + })); + + it('"threat_filters" field', () => + testFieldCustomization({ + fieldName: 'threat_filters', + customizedValue: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + ruleType: 'threat_match', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('threshold rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [THRESHOLD_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"threshold" field', () => + testFieldCustomization({ + fieldName: 'threshold', + customizedValue: { + field: ['Responses.process.pid'], + value: 100, + cardinality: [{ field: 'host.id', value: 2 }], + }, + ruleType: 'threshold', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('machine learning rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [MACHINE_LEARNING_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"machine_learning_job_id" field', () => + testFieldCustomization({ + fieldName: 'machine_learning_job_id', + customizedValue: '123', + ruleType: 'machine_learning', + })); + + it('"anomaly_threshold" field', () => + testFieldCustomization({ + fieldName: 'anomaly_threshold', + customizedValue: 20, + ruleType: 'machine_learning', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('new terms rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [NEW_TERMS_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"new_terms_fields" field', () => + testFieldCustomization({ + fieldName: 'new_terms_fields', + customizedValue: ['event.action'], + ruleType: 'new_terms', + })); + + it('"history_window_start" field', () => + testFieldCustomization({ + fieldName: 'history_window_start', + customizedValue: 'now-7d', + ruleType: 'new_terms', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('ES|QL rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ESQL_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'FROM sample_data | SORT @timestamp DESC | LIMIT 3', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + }); +}; + +const PREBUILT_RULE_ID = 'test-prebuilt-rule'; +const QUERY_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const SAVED_QUERY_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + type: 'saved_query', + saved_id: 'saved-query-id', + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const EQL_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('eql'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const THREAT_MATCH_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('threat_match'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const THRESHOLD_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('threshold'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const MACHINE_LEARNING_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('machine_learning'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const NEW_TERMS_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('new_terms'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const ESQL_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('esql'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_without_base_version.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_without_base_version.ts new file mode 100644 index 0000000000000..68d48e037b816 --- /dev/null +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/detect_customization_without_base_version.ts @@ -0,0 +1,615 @@ +/* + * 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 type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getPrebuiltRuleMockOfType } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; +import { deleteAllRules } from '../../../../../../config/services/detections_response'; +import type { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + installPrebuiltRules, +} from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Detect prebuilt rule customization (base version is missing)', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const testFieldCustomization = async ({ + fieldName, + customizedValue, + ruleType, + }: { + fieldName: string; + customizedValue: unknown; + // Rule type shouldn't be required to customize prebuilt rules. + // However prebuilt rules customization doesn't work as expected when rule type + // isn't provided for non custom query rule types. + ruleType?: RuleResponse['type']; + }) => { + // Assert the customization for "fieldName" works + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { rule_id: PREBUILT_RULE_ID, type: ruleType, [fieldName]: customizedValue }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + }; + + describe('common rule fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"name" field', () => + testFieldCustomization({ + fieldName: 'name', + customizedValue: 'some other name', + })); + + it('"description" field', () => + testFieldCustomization({ + fieldName: 'description', + customizedValue: 'some other description', + })); + + it('"note" field', () => + testFieldCustomization({ + fieldName: 'note', + customizedValue: '# some note markdown', + })); + + it('"severity" field', () => + testFieldCustomization({ + fieldName: 'severity', + customizedValue: 'medium', + })); + + it('"tags" field', () => + testFieldCustomization({ + fieldName: 'tags', + customizedValue: ['red fish', 'blue fish'], + })); + + it('"severity_mapping" field', () => + testFieldCustomization({ + fieldName: 'severity_mapping', + customizedValue: [ + { + field: 'event.severity', + operator: 'equals', + severity: 'low', + value: 'LOW', + }, + ], + })); + + it('"risk_score" field', () => + testFieldCustomization({ + fieldName: 'risk_score', + customizedValue: 72, + })); + + it('"risk_score_mapping" field', () => + testFieldCustomization({ + fieldName: 'risk_score_mapping', + customizedValue: [{ field: 'event.risk_score', operator: 'equals', value: '' }], + })); + + it('"references" field', () => + testFieldCustomization({ + fieldName: 'references', + customizedValue: ['http://test.test'], + })); + + it('"false_positives" field', () => + testFieldCustomization({ + fieldName: 'false_positives', + customizedValue: ['false positive example'], + })); + + it('"threat" field', () => + testFieldCustomization({ + fieldName: 'threat', + customizedValue: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0000', + name: 'test tactic', + reference: 'https://attack.mitre.org/tactics/TA0000/', + }, + }, + ], + })); + + it('"setup" field', () => + testFieldCustomization({ + fieldName: 'setup', + customizedValue: '# some setup markdown', + })); + + it('"related_integrations" field', () => + testFieldCustomization({ + fieldName: 'related_integrations', + customizedValue: [{ package: 'package-a', version: '^1.2.3' }], + })); + + it('"required_fields" field', () => + testFieldCustomization({ + fieldName: 'required_fields', + customizedValue: [{ name: '@timestamp', type: 'date' }], + })); + + it('"max_signals" field', () => + testFieldCustomization({ + fieldName: 'max_signals', + customizedValue: 42, + })); + + it('"investigation_fields" field', () => + testFieldCustomization({ + fieldName: 'investigation_fields', + customizedValue: { field_names: ['blob', 'boop'] }, + })); + + it('"rule_name_override" field', () => + testFieldCustomization({ + fieldName: 'rule_name_override', + customizedValue: 'override string', + })); + + it('"timestamp_override" field', () => + testFieldCustomization({ + fieldName: 'timestamp_override', + customizedValue: 'event.ingested', + })); + + it('"timeline_template" fields', async () => { + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { + rule_id: PREBUILT_RULE_ID, + timeline_id: '123', + timeline_title: 'timeline title', + }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + }); + + it('"building_block_type" field', () => + testFieldCustomization({ + fieldName: 'building_block_type', + customizedValue: 'building block string', + })); + + describe('rule schedule', () => { + it('"interval" field', () => + testFieldCustomization({ + fieldName: 'interval', + customizedValue: '30m', + })); + + it('"from" field', () => + testFieldCustomization({ + fieldName: 'from', + customizedValue: 'now-10m', + })); + + it('"to" field', () => + testFieldCustomization({ + fieldName: 'to', + customizedValue: 'now-1m', + })); + }); + }); + + describe('custom query rule fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'event.action: *', + })); + + it('"language" field', () => + testFieldCustomization({ + fieldName: 'language', + customizedValue: 'lucene', + })); + + it('"filters" field', () => + testFieldCustomization({ + fieldName: 'filters', + customizedValue: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + })); + + it('"index" field', () => + testFieldCustomization({ + fieldName: 'index', + customizedValue: ['new-index-pattern-*'], + })); + + it('"data_view_id" field', async () => { + const { body: customizedResponse } = await securitySolutionApi + .patchRule({ + body: { rule_id: PREBUILT_RULE_ID, data_view_id: 'new-data-view', index: [] }, + }) + .expect(200); + + expect(customizedResponse.rule_source).toMatchObject({ + type: 'external', + is_customized: true, + }); + }); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('saved query rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [SAVED_QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + }); + + it('"saved_id" field', () => + testFieldCustomization({ + fieldName: 'saved_id', + customizedValue: 'customized-saved-query-id', + ruleType: 'saved_query', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('EQL rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [EQL_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'process where process.name == "some_process"', + })); + + it('"event_category_override" field', () => + testFieldCustomization({ + fieldName: 'event_category_override', + customizedValue: 'host.name', + })); + + it('"timestamp_field" field', () => + testFieldCustomization({ + fieldName: 'timestamp_field', + customizedValue: 'event.ingested', + })); + + it('"tiebreaker_field" field', () => + testFieldCustomization({ + fieldName: 'tiebreaker_field', + customizedValue: 'event.ingested', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('threat match rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [THREAT_MATCH_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"threat_index" field', () => + testFieldCustomization({ + fieldName: 'threat_index', + customizedValue: ['blue fish', 'red fish'], + ruleType: 'threat_match', + })); + + it('"threat_mapping" field', () => + testFieldCustomization({ + fieldName: 'threat_mapping', + customizedValue: [ + { + entries: [ + { + field: 'Endpoint.capabilities', + type: 'mapping', + value: 'Target.dll.pe.description', + }, + ], + }, + ], + ruleType: 'threat_match', + })); + + it('"threat_indicator_path" field', () => + testFieldCustomization({ + fieldName: 'threat_indicator_path', + customizedValue: 'C:over/there.exe', + ruleType: 'threat_match', + })); + + it('"threat_query" field', () => + testFieldCustomization({ + fieldName: 'threat_query', + customizedValue: 'event.action: *', + ruleType: 'threat_match', + })); + + it('"threat_language" field', () => + testFieldCustomization({ + fieldName: 'threat_language', + customizedValue: 'lucene', + ruleType: 'threat_match', + })); + + it('"threat_filters" field', () => + testFieldCustomization({ + fieldName: 'threat_filters', + customizedValue: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + ruleType: 'threat_match', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('threshold rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [THRESHOLD_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"threshold" field', () => + testFieldCustomization({ + fieldName: 'threshold', + customizedValue: { + field: ['Responses.process.pid'], + value: 100, + cardinality: [{ field: 'host.id', value: 2 }], + }, + ruleType: 'threshold', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('machine learning rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [MACHINE_LEARNING_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"machine_learning_job_id" field', () => + testFieldCustomization({ + fieldName: 'machine_learning_job_id', + customizedValue: '123', + ruleType: 'machine_learning', + })); + + it('"anomaly_threshold" field', () => + testFieldCustomization({ + fieldName: 'anomaly_threshold', + customizedValue: 20, + ruleType: 'machine_learning', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('new terms rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [NEW_TERMS_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"new_terms_fields" field', () => + testFieldCustomization({ + fieldName: 'new_terms_fields', + customizedValue: ['event.action'], + ruleType: 'new_terms', + })); + + it('"history_window_start" field', () => + testFieldCustomization({ + fieldName: 'history_window_start', + customizedValue: 'now-7d', + ruleType: 'new_terms', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + + describe('ES|QL rule type fields', () => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [ESQL_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('"query" field', () => + testFieldCustomization({ + fieldName: 'query', + customizedValue: 'FROM sample_data | SORT @timestamp DESC | LIMIT 3', + })); + + it('"alert_suppression" field', () => + testFieldCustomization({ + fieldName: 'alert_suppression', + customizedValue: { + group_by: ['host.name'], + duration: { value: 5, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + })); + }); + }); +}; + +const PREBUILT_RULE_ID = 'test-prebuilt-rule'; +const QUERY_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const SAVED_QUERY_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + type: 'saved_query', + saved_id: 'saved-query-id', + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const EQL_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('eql'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const THREAT_MATCH_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('threat_match'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const THRESHOLD_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('threshold'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const MACHINE_LEARNING_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('machine_learning'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const NEW_TERMS_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('new_terms'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); +const ESQL_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + ...getPrebuiltRuleMockOfType('esql'), + rule_id: PREBUILT_RULE_ID, + version: 3, +}); diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/index.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/index.ts index 645e840fbf146..129f9ff7ce0fc 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/index.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/index.ts @@ -8,7 +8,8 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { - loadTestFile(require.resolve('./calculate_is_customized')); - loadTestFile(require.resolve('./customize_prebuilt_rules')); + loadTestFile(require.resolve('./detect_customization_with_base_version')); + loadTestFile(require.resolve('./detect_customization_without_base_version')); loadTestFile(require.resolve('./customize_via_bulk_editing')); + loadTestFile(require.resolve('./unaffected_fields')); }; diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/unaffected_fields.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/unaffected_fields.ts new file mode 100644 index 0000000000000..e008916864f3c --- /dev/null +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/customization/unaffected_fields.ts @@ -0,0 +1,209 @@ +/* + * 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 { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { deleteAllRules } from '../../../../../../config/services/detections_response'; +import type { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + createPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + getWebHookAction, + installPrebuiltRules, +} from '../../../../utils'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI Skip customization detection for unaffected prebuilt rule fields', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + const testUnaffectedFieldsNonCustomized = ({ hasBaseVersion }: { hasBaseVersion: boolean }) => { + beforeEach(async () => { + await createPrebuiltRuleAssetSavedObjects(es, [QUERY_PREBUILT_RULE_ASSET]); + await installPrebuiltRules(es, supertest); + + if (!hasBaseVersion) { + // Remove the prebuilt rule asset so that the base version is no longer available + await deleteAllPrebuiltRuleAssets(es, log); + } + }); + + describe('"is_customized" calculation is not affected by', () => { + const testFieldDoesNotAffectCustomizationState = async ({ + fieldName, + value, + }: { + fieldName: string; + value: unknown; + }) => { + const { body } = await securitySolutionApi + .patchRule({ + body: { rule_id: PREBUILT_RULE_ID, [fieldName]: value }, + }) + .expect(200); + + expect(body.rule_source).toMatchObject({ + type: 'external', + is_customized: false, + }); + }; + + it('"actions" field', async () => { + // create connector/action + const { body: hookAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getWebHookAction()) + .expect(200); + + await testFieldDoesNotAffectCustomizationState({ + fieldName: 'actions', + value: [ + { + group: 'default', + id: hookAction.id, + action_type_id: hookAction.actionTypeId, + params: {}, + }, + ], + }); + }); + + it('"exceptions_list" field', () => + testFieldDoesNotAffectCustomizationState({ + fieldName: 'exceptions_list', + value: [ + { + id: 'some_uuid', + list_id: 'list_id_single', + namespace_type: 'single', + type: 'detection', + }, + ], + })); + + it('"enabled" field', () => + testFieldDoesNotAffectCustomizationState({ + fieldName: 'enabled', + value: true, + })); + + it('"meta" field', () => + testFieldDoesNotAffectCustomizationState({ + fieldName: 'meta', + value: { + severity_override_field: 'field', + }, + })); + + it('leaves "is_customized" intact when bulk edit does not change the field value', async () => { + const { body: prebuiltRule } = await securitySolutionApi + .readRule({ + query: { rule_id: PREBUILT_RULE_ID }, + }) + .expect(200); + + expect(prebuiltRule.rule_source.is_customized).toEqual(false); + + const { body: bulkResult } = await securitySolutionApi + .performRulesBulkAction({ + query: {}, + body: { + ids: [prebuiltRule.id], + action: BulkActionTypeEnum.edit, + [BulkActionTypeEnum.edit]: [ + { + type: BulkActionEditTypeEnum.add_tags, + // This tag is already present on the rule, so the change will be skipped + value: [...prebuiltRule.tags], + }, + ], + }, + }) + .expect(200); + + expect(bulkResult.attributes.summary).toEqual({ + failed: 0, + skipped: 1, + succeeded: 0, + total: 1, + }); + + // Check that the rule has not been customized + const { body: unchangedPrebuiltRule } = await securitySolutionApi + .readRule({ + query: { rule_id: PREBUILT_RULE_ID }, + }) + .expect(200); + + expect(unchangedPrebuiltRule.rule_source.is_customized).toEqual(false); + }); + }); + + describe('cannot change non-customizable rule fields', () => { + it('"id" field', async () => { + await securitySolutionApi + .patchRule({ + body: { + rule_id: PREBUILT_RULE_ID, + id: 'new-id', + }, + }) + .expect(400); + }); + + it('"author" field', async () => { + await securitySolutionApi + .patchRule({ + body: { + rule_id: PREBUILT_RULE_ID, + author: ['new author'], + }, + }) + .expect(400); + }); + + it('"license" field', async () => { + await securitySolutionApi + .patchRule({ + body: { + rule_id: PREBUILT_RULE_ID, + license: 'custom-license', + }, + }) + .expect(400); + }); + }); + }; + + describe('when base version is available', () => { + testUnaffectedFieldsNonCustomized({ hasBaseVersion: true }); + }); + + describe('when base version is missing', () => { + testUnaffectedFieldsNonCustomized({ hasBaseVersion: false }); + }); + }); +}; + +const PREBUILT_RULE_ID = 'test-prebuilt-rule'; +const QUERY_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID, + version: 3, + tags: ['test-tag'], +}); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization.cy.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization.cy.ts index c3306212a31aa..bbe3b4f16d81b 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization.cy.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization.cy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { clickRuleUpdatesTab } from '../../../../../tasks/prebuilt_rules'; +import { setUpRuleUpgrades } from '../../../../../tasks/prebuilt_rules/setup_rule_upgrades'; import { clickUpdateScheduleMenuItem, openBulkEditAddIndexPatternsForm, @@ -37,7 +37,7 @@ import { MODIFIED_PREBUILT_RULE_PER_FIELD_BADGE, RULE_CUSTOMIZATIONS_DIFF_FLYOUT, } from '../../../../../screens/rule_details'; -import { goToRuleEditSettings } from '../../../../../tasks/rule_details'; +import { goToRuleEditSettings, visitRuleDetailsPage } from '../../../../../tasks/rule_details'; import { getIndexPatterns, getCustomQueryRuleParams } from '../../../../../objects/rule'; import { editFirstRule, @@ -51,7 +51,7 @@ import { filterByElasticRules, selectAllRules, } from '../../../../../tasks/alerts_detection_rules'; -import { MODIFIED_RULE_BADGE, RULE_NAME } from '../../../../../screens/alerts_detection_rules'; +import { MODIFIED_RULE_BADGE } from '../../../../../screens/alerts_detection_rules'; import { createRuleAssetSavedObject } from '../../../../../helpers/rules'; import { deleteAlertsAndRules, @@ -60,15 +60,17 @@ import { import { createAndInstallMockedPrebuiltRules, installMockPrebuiltRulesPackage, - installPrebuiltRuleAssets, } from '../../../../../tasks/api_calls/prebuilt_rules'; -import { createRule, patchRule } from '../../../../../tasks/api_calls/rules'; +import { createRule, patchRule, readRule } from '../../../../../tasks/api_calls/rules'; import { login } from '../../../../../tasks/login'; -import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; +import { + visitRulesManagementTable, + visitRulesUpgradeTable, +} from '../../../../../tasks/rules_management'; import { fillDescription, goToAboutStepTab } from '../../../../../tasks/create_new_rule'; -import { saveEditedRule } from '../../../../../tasks/edit_rule'; +import { saveEditedRule, visitRuleEditPage } from '../../../../../tasks/edit_rule'; describe( 'Detection rules, Prebuilt Rules Customization workflow', @@ -82,23 +84,29 @@ describe( }); const testTags = ['tag 1', 'tag 2']; - const PREBUILT_RULE = createRuleAssetSavedObject({ + const PREBUILT_RULE_ID = 'test-customization-prebuilt-rule'; + const PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ name: 'Non-customized prebuilt rule', - rule_id: 'rule_1', + rule_id: PREBUILT_RULE_ID, version: 1, index: getIndexPatterns(), tags: testTags, investigation_fields: { field_names: ['source.ip'] }, }); + const NEW_PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + name: 'New Non-customized prebuilt rule', + rule_id: PREBUILT_RULE_ID, + version: 2, + index: getIndexPatterns(), + tags: testTags, + investigation_fields: { field_names: ['source.ip'] }, + }); beforeEach(() => { deleteAlertsAndRules(); deletePrebuiltRulesAssets(); /* Create a new rule and install it */ - createAndInstallMockedPrebuiltRules([PREBUILT_RULE]); - - login(); - visitRulesManagementTable(); + createAndInstallMockedPrebuiltRules([PREBUILT_RULE_ASSET]); createRule( getCustomQueryRuleParams({ name: 'Custom rule', @@ -106,61 +114,83 @@ describe( tags: testTags, enabled: false, }) - ); - }); + ) + .then(({ body: createdRule }) => createdRule.id) + .as('customRuleId'); - it('user can navigate to rule editing page from the rule details page', function () { - cy.get(RULE_NAME).contains('Non-customized prebuilt rule').click(); + // Read and save just installed prebuilt rule's rule_id + readRule({ ruleId: PREBUILT_RULE_ID }) + .then(({ body: prebuiltRule }) => prebuiltRule.id) + .as('prebuiltRuleId'); - goToRuleEditSettings(); - cy.get(DEFINITION_EDIT_TAB).should('be.enabled'); - cy.get(ABOUT_EDIT_TAB).should('be.enabled'); - cy.get(SCHEDULE_EDIT_TAB).should('be.enabled'); - cy.get(ACTIONS_EDIT_TAB).should('be.enabled'); + login(); }); - it('user can edit a non-customized prebuilt rule from the rule edit page', function () { - const newDescriptionValue = 'New rule description'; - cy.get(RULE_NAME).contains('Non-customized prebuilt rule').click(); + describe('navigation to the rule editing page', () => { + it('navigates from the rule details page', () => { + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); + + goToRuleEditSettings(); + cy.get(DEFINITION_EDIT_TAB).should('be.enabled'); + cy.get(ABOUT_EDIT_TAB).should('be.enabled'); + cy.get(SCHEDULE_EDIT_TAB).should('be.enabled'); + cy.get(ACTIONS_EDIT_TAB).should('be.enabled'); + }); - goToRuleEditSettings(); - goToAboutStepTab(); - fillDescription(newDescriptionValue); - saveEditedRule(); + it('navigates from the rule management page', () => { + visitRulesManagementTable(); + + filterByElasticRules(); + editFirstRule(); - expectModifiedRuleBadgeToBeDisplayed(); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newDescriptionValue); + cy.get(DEFINITION_EDIT_TAB).should('be.enabled'); + cy.get(ABOUT_EDIT_TAB).should('be.enabled'); + cy.get(SCHEDULE_EDIT_TAB).should('be.enabled'); + cy.get(ACTIONS_EDIT_TAB).should('be.enabled'); + }); }); - it('user can edit a customized prebuilt rule from the rule edit page', function () { - const newDescriptionValue = 'New rule description'; - patchRule('rule_1', { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule - visitRulesManagementTable(); + describe('editing a single prebuilt rule on the rule edit page', () => { + it('edits a non-customized prebuilt rule', () => { + const newDescriptionValue = 'New rule description'; - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); - expectModifiedRuleBadgeToBeDisplayed(); // Expect modified badge to already be displayed + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleEditPage(prebuiltRuleId) + ); - goToRuleEditSettings(); - goToAboutStepTab(); - fillDescription(newDescriptionValue); - saveEditedRule(); + goToAboutStepTab(); + fillDescription(newDescriptionValue); + saveEditedRule(); - expectModifiedRuleBadgeToBeDisplayed(); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newDescriptionValue); - }); + expectModifiedRuleBadgeToBeDisplayed(); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newDescriptionValue); + }); + + it('edits a customized prebuilt rule', () => { + const newDescriptionValue = 'New rule description'; + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule - it('user can navigate to rule editing page from the rule management page', function () { - filterByElasticRules(); - editFirstRule(); + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleEditPage(prebuiltRuleId) + ); - cy.get(DEFINITION_EDIT_TAB).should('be.enabled'); - cy.get(ABOUT_EDIT_TAB).should('be.enabled'); - cy.get(SCHEDULE_EDIT_TAB).should('be.enabled'); - cy.get(ACTIONS_EDIT_TAB).should('be.enabled'); + goToAboutStepTab(); + fillDescription(newDescriptionValue); + saveEditedRule(); + + expectModifiedRuleBadgeToBeDisplayed(); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', newDescriptionValue); + }); }); - describe('user can bulk edit prebuilt rules from rules management page', () => { - it('add index patterns', () => { + describe('bulk editing prebuilt rules from the rules management page', () => { + beforeEach(() => { + visitRulesManagementTable(); + }); + + it('adds index patterns', () => { filterByElasticRules(); selectAllRules(); @@ -175,7 +205,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('delete index patterns', () => { + it('deletes index patterns', () => { filterByElasticRules(); selectAllRules(); @@ -190,7 +220,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('add tags', () => { + it('adds tags', () => { filterByElasticRules(); selectAllRules(); @@ -205,7 +235,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('delete tags', () => { + it('deletes tags', () => { filterByElasticRules(); selectAllRules(); @@ -220,7 +250,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('add custom highlighted fields', () => { + it('adds custom highlighted fields', () => { filterByElasticRules(); selectAllRules(); @@ -235,7 +265,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('delete custom highlighted fields', () => { + it('deletes custom highlighted fields', () => { filterByElasticRules(); selectAllRules(); @@ -250,7 +280,7 @@ describe( expectToContainModifiedBadge('Non-customized prebuilt rule'); }); - it('modify rule schedules', () => { + it('modifies rule schedules', () => { filterByElasticRules(); selectAllRules(); @@ -275,44 +305,50 @@ describe( describe('calculating the Modified badge', () => { describe('on the rule details page', () => { it('should open the rule diff flyout on click when rule is customized', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule - visitRulesManagementTable(); + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule + + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); expectModifiedRuleBadgeToBeDisplayed(); // Expect modified badge to be displayed cy.get(MODIFIED_PREBUILT_RULE_BADGE).click(); cy.get(RULE_CUSTOMIZATIONS_DIFF_FLYOUT).should('exist'); }); it('should not open the rule diff flyout on click when rule is customized but base version does not exist', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule deletePrebuiltRulesAssets(); - visitRulesManagementTable(); - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); + cy.get(MODIFIED_PREBUILT_RULE_BADGE_NO_BASE_VERSION).should('exist'); // Expect modified badge to be displayed cy.get(MODIFIED_PREBUILT_RULE_BADGE_NO_BASE_VERSION).click(); cy.get(RULE_CUSTOMIZATIONS_DIFF_FLYOUT).should('not.exist'); }); it("should not be displayed when rule isn't customized", function () { - visitRulesManagementTable(); + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); - cy.get(RULE_NAME).contains('Non-customized prebuilt rule').click(); expectModifiedRuleBadgeToNotBeDisplayed(); // Expect modified badge to not be displayed }); it('should not be displayed when rule is not prebuilt', function () { - visitRulesManagementTable(); + cy.get('@customRuleId').then((customRuleId) => + visitRuleDetailsPage(customRuleId) + ); - cy.get(RULE_NAME).contains('Custom rule').click(); expectModifiedRuleBadgeToNotBeDisplayed(); // Expect modified badge to not be displayed }); }); describe('on the rule management table', () => { it('should be displayed in row when prebuilt rule is customized', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule visitRulesManagementTable(); filterByElasticRules(); @@ -336,30 +372,30 @@ describe( describe('on the rule updates table', () => { it('should be displayed when prebuilt rule is customized', function () { - // Create a new version of the rule to trigger the rule update logic - installPrebuiltRuleAssets([ - { - ...PREBUILT_RULE, - 'security-rule': { ...PREBUILT_RULE['security-rule'], version: 2 }, - }, - ]); - patchRule('rule_1', { name: 'Customized prebuilt rule' }); // We want to make this a customized prebuilt rule - visitRulesManagementTable(); - clickRuleUpdatesTab(); + setUpRuleUpgrades({ + currentRuleAssets: [PREBUILT_RULE_ASSET], + rulePatches: [ + { + rule_id: PREBUILT_RULE_ID, + name: 'Customized prebuilt rule', + }, + ], + newRuleAssets: [NEW_PREBUILT_RULE_ASSET], + }); + + visitRulesUpgradeTable(); cy.get(MODIFIED_RULE_BADGE).should('exist'); }); it("should not be displayed when prebuilt rule isn't customized", function () { - // Create a new version of the rule to trigger the rule update logic - installPrebuiltRuleAssets([ - { - ...PREBUILT_RULE, - 'security-rule': { ...PREBUILT_RULE['security-rule'], version: 2 }, - }, - ]); - visitRulesManagementTable(); - clickRuleUpdatesTab(); + setUpRuleUpgrades({ + currentRuleAssets: [PREBUILT_RULE_ASSET], + rulePatches: [], + newRuleAssets: [NEW_PREBUILT_RULE_ASSET], + }); + + visitRulesUpgradeTable(); cy.get(MODIFIED_RULE_BADGE).should('not.exist'); }); @@ -368,29 +404,35 @@ describe( describe('calculating the per-field modified badge', () => { it('should appear next to fields that have been customized', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule - visitRulesManagementTable(); + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule + + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); expectModifiedRulePerFieldBadgeToBeDisplayed('tags'); // Customized fields should have a badge present expectModifiedRulePerFieldBadgeToNotBeDisplayed('max_signals'); // Non-customized fields should not have a badge present }); it('should open the rule customizations diff flyout on click', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule - visitRulesManagementTable(); + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule + + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); cy.get(MODIFIED_PREBUILT_RULE_PER_FIELD_BADGE('tags')).click(); cy.get(RULE_CUSTOMIZATIONS_DIFF_FLYOUT).should('exist'); }); it('should not be displayed when the rule base version does not exist', function () { - patchRule('rule_1', { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule + patchRule(PREBUILT_RULE_ID, { name: 'Customized prebuilt rule', tags: ['test'] }); // We want to make this a customized prebuilt rule deletePrebuiltRulesAssets(); - visitRulesManagementTable(); - cy.get(RULE_NAME).contains('Customized prebuilt rule').click(); + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleDetailsPage(prebuiltRuleId) + ); + expectModifiedRulePerFieldBadgeToNotBeDisplayed('tags'); }); }); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization_basic_license.cy.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization_basic_license.cy.ts new file mode 100644 index 0000000000000..2989959538134 --- /dev/null +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/customization/rule_customization_basic_license.cy.ts @@ -0,0 +1,194 @@ +/* + * 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 { createRule, readRule } from '../../../../../tasks/api_calls/rules'; +import { createRuleAssetSavedObject } from '../../../../../helpers/rules'; +import { filterByElasticRules, selectAllRules } from '../../../../../tasks/alerts_detection_rules'; +import { + deleteAlertsAndRules, + deletePrebuiltRulesAssets, +} from '../../../../../tasks/api_calls/common'; +import { + createAndInstallMockedPrebuiltRules, + installMockPrebuiltRulesPackage, +} from '../../../../../tasks/api_calls/prebuilt_rules'; +import { login } from '../../../../../tasks/login'; +import { getIndexPatterns, getCustomQueryRuleParams } from '../../../../../objects/rule'; +import { visitRulesManagementTable } from '../../../../../tasks/rules_management'; +import { downgradeLicenseToBasic } from '../../../../../tasks/license'; +import { visitRuleEditPage } from '../../../../../tasks/edit_rule'; +import { + ABOUT_EDIT_TAB, + DEFINITION_EDIT_TAB, + SCHEDULE_EDIT_TAB, +} from '../../../../../screens/create_new_rule'; +import { + clickBulkAddIndexPatternsMenuItem, + clickBulkAddInvestigationFieldsMenuItem, + clickBulkAddTagsMenuItem, + clickBulkDeleteIndexPatternsMenuItem, + clickBulkDeleteInvestigationFieldsMenuItem, + clickBulkDeleteTagsMenuItem, + clickBulkEditRuleScheduleMenuItem, +} from '../../../../../tasks/rules_bulk_actions'; +import { + RULES_BULK_ACTION_CONFIRMATION_MODAL, + RULES_BULK_ACTION_REJECT_MODAL, +} from '../../../../../screens/rules_bulk_actions'; + +describe( + 'Detection rules, Prebuilt Rules Customization workflow (Basic License)', + { tags: ['@ess'] }, + () => { + before(() => { + installMockPrebuiltRulesPackage(); + }); + + const testTags = ['tag 1', 'tag 2']; + const PREBUILT_RULE_ID = 'test-customization-prebuilt-rule'; + const PREBUILT_RULE_ASSET = createRuleAssetSavedObject({ + name: 'Non-customized prebuilt rule', + rule_id: PREBUILT_RULE_ID, + version: 1, + index: getIndexPatterns(), + tags: testTags, + investigation_fields: { field_names: ['source.ip'] }, + }); + + beforeEach(() => { + deleteAlertsAndRules(); + deletePrebuiltRulesAssets(); + /* Create a new rule and install it */ + createAndInstallMockedPrebuiltRules([PREBUILT_RULE_ASSET]); + createRule( + getCustomQueryRuleParams({ + rule_id: 'custom-rule', + name: 'Custom rule', + enabled: false, + }) + ); + + // Read and save just installed prebuilt rule's rule_id + readRule({ ruleId: PREBUILT_RULE_ID }) + .then(({ body: prebuiltRule }) => prebuiltRule.id) + .as('prebuiltRuleId'); + + login(); + downgradeLicenseToBasic(); + }); + + describe('under an insufficient license', () => { + it('fails to customize prebuilt rules under an insufficient license from the rule edit page', () => { + cy.get('@prebuiltRuleId').then((prebuiltRuleId) => + visitRuleEditPage(prebuiltRuleId) + ); + + cy.get(SCHEDULE_EDIT_TAB).should('be.disabled'); + cy.get(SCHEDULE_EDIT_TAB).realHover(); + cy.contains('Without the Enterprise subscription'); + + cy.get(DEFINITION_EDIT_TAB).should('be.disabled'); + cy.get(DEFINITION_EDIT_TAB).realHover(); + cy.contains('Without the Enterprise subscription'); + + cy.get(ABOUT_EDIT_TAB).should('be.disabled'); + cy.get(ABOUT_EDIT_TAB).realHover(); + cy.contains('Without the Enterprise subscription'); + }); + + describe('bulk editing', () => { + beforeEach(() => { + visitRulesManagementTable(); + }); + + it('fails to add index patterns', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkAddIndexPatternsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to delete index patterns', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkDeleteIndexPatternsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to add tags', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkAddTagsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to delete tags', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkDeleteTagsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to add custom highlighted fields', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkAddInvestigationFieldsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to delete custom highlighted fields', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkDeleteInvestigationFieldsMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to modify the rule schedule', () => { + filterByElasticRules(); + selectAllRules(); + + clickBulkEditRuleScheduleMenuItem(); + + cy.contains(RULES_BULK_ACTION_REJECT_MODAL, 'rule cannot be edited').should('be.visible'); + cy.get(RULES_BULK_ACTION_REJECT_MODAL).contains('Enterprise subscription is required'); + }); + + it('fails to bulk edit bulk prebuilt rules in a mixture of prebuilt and custom rules', () => { + selectAllRules(); + + clickBulkAddTagsMenuItem(); + + cy.contains( + RULES_BULK_ACTION_CONFIRMATION_MODAL, + 'This action can only be applied to 1 rule' + ).should('be.visible'); + cy.get(RULES_BULK_ACTION_CONFIRMATION_MODAL).contains( + 'Enterprise subscription is required' + ); + }); + }); + }); + } +); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/upgrade/upgrade_without_preview_basic_license.cy.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/upgrade/upgrade_without_preview_basic_license.cy.ts index c73f097b03cce..e8d034a829117 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/upgrade/upgrade_without_preview_basic_license.cy.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/upgrade/upgrade_without_preview_basic_license.cy.ts @@ -30,87 +30,94 @@ import { } from '../../../../../tasks/rules_management'; import { downgradeLicenseToBasic } from '../../../../../tasks/license'; -describe('Detection rules, Prebuilt Rules Upgrade Without Preview', { tags: ['@ess'] }, () => { - before(() => { - installMockPrebuiltRulesPackage(); - }); - - beforeEach(() => { - resetRulesTableState(); - deletePrebuiltRulesAssets(); - deleteAlertsAndRules(); - login(); - downgradeLicenseToBasic(); - }); +describe( + 'Detection rules, Prebuilt Rules Upgrade Without Preview (Basic License)', + { tags: ['@ess'] }, + () => { + before(() => { + installMockPrebuiltRulesPackage(); + }); - const PREBUILT_RULE_ID_A = 'test-prebuilt-rule-a'; - const PREBUILT_RULE_ID_B = 'test-prebuilt-rule-b'; - const PREBUILT_RULE_ASSET_A = createRuleAssetSavedObject({ - rule_id: PREBUILT_RULE_ID_A, - version: 1, - name: 'Prebuilt rule A', - description: 'Non-customized prebuilt rule A', - tags: ['tag-a', 'tab-b'], - }); - const NEW_PREBUILT_RULE_ASSET_A = createRuleAssetSavedObject({ - rule_id: PREBUILT_RULE_ID_A, - version: 2, - name: 'New Prebuilt rule A', - description: 'New Non-customized prebuilt rule A', - tags: ['tag-c', 'tab-d'], - }); - const PREBUILT_RULE_ASSET_B = createRuleAssetSavedObject({ - rule_id: PREBUILT_RULE_ID_B, - version: 1, - name: 'Prebuilt rule B', - description: 'Non-customized prebuilt rule B', - tags: ['tag-a', 'tab-b'], - }); - const NEW_PREBUILT_RULE_ASSET_B = createRuleAssetSavedObject({ - rule_id: PREBUILT_RULE_ID_B, - version: 2, - name: 'New Prebuilt rule B', - description: 'New Non-customized prebuilt rule B', - tags: ['tag-c', 'tab-d'], - }); + beforeEach(() => { + resetRulesTableState(); + deletePrebuiltRulesAssets(); + deleteAlertsAndRules(); + login(); + downgradeLicenseToBasic(); + }); - it('upgrades customized prebuilt rules with conflicts to the target versions', () => { - setUpRuleUpgrades({ - currentRuleAssets: [PREBUILT_RULE_ASSET_A, PREBUILT_RULE_ASSET_B], - rulePatches: [ - { rule_id: PREBUILT_RULE_ID_A, tags: ['custom-tag-a'] }, - { rule_id: PREBUILT_RULE_ID_B, tags: ['custom-tag-b'] }, - ], - newRuleAssets: [NEW_PREBUILT_RULE_ASSET_A, NEW_PREBUILT_RULE_ASSET_B], + const PREBUILT_RULE_ID_A = 'test-prebuilt-rule-a'; + const PREBUILT_RULE_ID_B = 'test-prebuilt-rule-b'; + const PREBUILT_RULE_ASSET_A = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID_A, + version: 1, + name: 'Prebuilt rule A', + description: 'Non-customized prebuilt rule A', + tags: ['tag-a', 'tab-b'], + }); + const NEW_PREBUILT_RULE_ASSET_A = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID_A, + version: 2, + name: 'New Prebuilt rule A', + description: 'New Non-customized prebuilt rule A', + tags: ['tag-c', 'tab-d'], + }); + const PREBUILT_RULE_ASSET_B = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID_B, + version: 1, + name: 'Prebuilt rule B', + description: 'Non-customized prebuilt rule B', + tags: ['tag-a', 'tab-b'], + }); + const NEW_PREBUILT_RULE_ASSET_B = createRuleAssetSavedObject({ + rule_id: PREBUILT_RULE_ID_B, + version: 2, + name: 'New Prebuilt rule B', + description: 'New Non-customized prebuilt rule B', + tags: ['tag-c', 'tab-d'], }); - visitRulesUpgradeTable(); - cy.get(UPGRADE_ALL_RULES_BUTTON).click(); + it('upgrades customized prebuilt rules with conflicts to the target versions', () => { + setUpRuleUpgrades({ + currentRuleAssets: [PREBUILT_RULE_ASSET_A, PREBUILT_RULE_ASSET_B], + rulePatches: [ + { rule_id: PREBUILT_RULE_ID_A, tags: ['custom-tag-a'] }, + { rule_id: PREBUILT_RULE_ID_B, tags: ['custom-tag-b'] }, + ], + newRuleAssets: [NEW_PREBUILT_RULE_ASSET_A, NEW_PREBUILT_RULE_ASSET_B], + }); + visitRulesUpgradeTable(); - assertRuleUpgradeSuccessToastShown([NEW_PREBUILT_RULE_ASSET_A, NEW_PREBUILT_RULE_ASSET_B]); - assertRulesNotPresentInRuleUpdatesTable([NEW_PREBUILT_RULE_ASSET_A, NEW_PREBUILT_RULE_ASSET_B]); + cy.get(UPGRADE_ALL_RULES_BUTTON).click(); - visitRulesManagementTable(); - expectRulesInTable(RULES_MANAGEMENT_TABLE, [ - NEW_PREBUILT_RULE_ASSET_A['security-rule'].name, - NEW_PREBUILT_RULE_ASSET_B['security-rule'].name, - ]); + assertRuleUpgradeSuccessToastShown([NEW_PREBUILT_RULE_ASSET_A, NEW_PREBUILT_RULE_ASSET_B]); + assertRulesNotPresentInRuleUpdatesTable([ + NEW_PREBUILT_RULE_ASSET_A, + NEW_PREBUILT_RULE_ASSET_B, + ]); - goToRuleDetailsOf(NEW_PREBUILT_RULE_ASSET_A['security-rule'].name); + visitRulesManagementTable(); + expectRulesInTable(RULES_MANAGEMENT_TABLE, [ + NEW_PREBUILT_RULE_ASSET_A['security-rule'].name, + NEW_PREBUILT_RULE_ASSET_B['security-rule'].name, + ]); - // Assert that tags got upgraded to the target value - cy.get(TAGS_PROPERTY_VALUE_ITEM).then((items) => { - const tags = items.map((_, item) => item.textContent).toArray(); - cy.wrap(tags).should('deep.equal', NEW_PREBUILT_RULE_ASSET_A['security-rule'].tags); - }); + goToRuleDetailsOf(NEW_PREBUILT_RULE_ASSET_A['security-rule'].name); + + // Assert that tags got upgraded to the target value + cy.get(TAGS_PROPERTY_VALUE_ITEM).then((items) => { + const tags = items.map((_, item) => item.textContent).toArray(); + cy.wrap(tags).should('deep.equal', NEW_PREBUILT_RULE_ASSET_A['security-rule'].tags); + }); - visitRulesManagementTable(); - goToRuleDetailsOf(NEW_PREBUILT_RULE_ASSET_B['security-rule'].name); + visitRulesManagementTable(); + goToRuleDetailsOf(NEW_PREBUILT_RULE_ASSET_B['security-rule'].name); - // Assert that tags got upgraded to the target value - cy.get(TAGS_PROPERTY_VALUE_ITEM).then((items) => { - const tags = items.map((_, item) => item.textContent).toArray(); - cy.wrap(tags).should('deep.equal', NEW_PREBUILT_RULE_ASSET_B['security-rule'].tags); + // Assert that tags got upgraded to the target value + cy.get(TAGS_PROPERTY_VALUE_ITEM).then((items) => { + const tags = items.map((_, item) => item.textContent).toArray(); + cy.wrap(tags).should('deep.equal', NEW_PREBUILT_RULE_ASSET_B['security-rule'].tags); + }); }); - }); -}); + } +); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts index 6cdd71c3577c5..05c49f3cda3b5 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules.cy.ts @@ -50,7 +50,7 @@ import { typeIndexPatterns, waitForBulkEditActionToFinish, submitBulkEditForm, - clickAddIndexPatternsMenuItem, + clickBulkAddIndexPatternsMenuItem, checkMachineLearningRulesCannotBeModified, checkEsqlRulesCannotBeModified, openBulkEditAddTagsForm, @@ -353,7 +353,7 @@ describe( const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded]; selectAllRules(); - clickAddIndexPatternsMenuItem(); + clickBulkAddIndexPatternsMenuItem(); // confirm editing all rules, that are not Machine Learning checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited); @@ -374,7 +374,7 @@ describe( it('Index pattern action applied to all rules, including machine learning: user cancels action', () => { selectAllRules(); - clickAddIndexPatternsMenuItem(); + clickBulkAddIndexPatternsMenuItem(); // confirm editing all rules, that are not Machine Learning checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited); @@ -777,7 +777,7 @@ describe('Detection rules, bulk edit, ES|QL rule type', { tags: ['@ess'] }, () = { tags: ['@ess'] }, () => { selectAllRules(); - clickAddIndexPatternsMenuItem(); + clickBulkAddIndexPatternsMenuItem(); // confirm editing all rules, that are not Machine Learning checkEsqlRulesCannotBeModified(1); diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/rules_bulk_actions.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/rules_bulk_actions.ts index e6fc5cf7d8a24..ff5faa8d36475 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/rules_bulk_actions.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/screens/rules_bulk_actions.ts @@ -10,6 +10,11 @@ export const RULES_BULK_EDIT_FORM_TITLE = '[data-test-subj="rulesBulkEditFormTit export const RULES_BULK_EDIT_FORM_CONFIRM_BTN = '[data-test-subj="rulesBulkEditFormSaveBtn"]'; +export const RULES_BULK_ACTION_CONFIRMATION_MODAL = + '[data-test-subj="bulkActionConfirmationModal"]'; + +export const RULES_BULK_ACTION_REJECT_MODAL = '[data-test-subj="bulkActionRejectModal"]'; + // INDEX PATTERNS export const INDEX_PATTERNS_RULE_BULK_MENU_ITEM = '[data-test-subj="indexPatternsBulkEditRule"]'; diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts index ae0261edc1d0b..dc8e5fcfeb1ab 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/api_calls/rules.ts @@ -26,6 +26,30 @@ export const findAllRules = () => { }); }; +type ReadRuleParams = + | { + id: string; + ruleId?: never; + } + | { + id?: never; + ruleId: string; + }; + +export const readRule = ({ + id, + ruleId, +}: ReadRuleParams): Cypress.Chainable> => { + return cy.currentSpace().then((spaceId) => + rootRequest({ + method: 'GET', + url: spaceId ? getSpaceUrl(spaceId, DETECTION_ENGINE_RULES_URL) : DETECTION_ENGINE_RULES_URL, + qs: { ...(id ? { id } : {}), ...(ruleId ? { rule_id: ruleId } : {}) }, + failOnStatusCode: true, + }) + ); +}; + export const createRule = ( rule: RuleCreateProps ): Cypress.Chainable> => { diff --git a/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts b/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts index 40161115d4f1a..cb6057839ff8c 100644 --- a/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts +++ b/x-pack/solutions/security/test/security_solution_cypress/cypress/tasks/rules_bulk_actions.ts @@ -147,21 +147,25 @@ const clickIndexPatternsMenuItem = () => { cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('not.exist'); }; -export const clickAddIndexPatternsMenuItem = () => { +export const clickBulkAddIndexPatternsMenuItem = () => { clickIndexPatternsMenuItem(); cy.get(ADD_INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); }; +export const clickBulkDeleteIndexPatternsMenuItem = () => { + cy.get(BULK_ACTIONS_BTN).click(); + cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); + cy.get(DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); +}; + export const openBulkEditAddIndexPatternsForm = () => { - clickAddIndexPatternsMenuItem(); + clickBulkAddIndexPatternsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add index patterns'); }; export const openBulkEditDeleteIndexPatternsForm = () => { - cy.get(BULK_ACTIONS_BTN).click(); - cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); - cy.get(DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); + clickBulkDeleteIndexPatternsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Delete index patterns'); }; @@ -197,20 +201,24 @@ const clickTagsMenuItem = () => { cy.get(TAGS_RULE_BULK_MENU_ITEM).click(); }; -export const clickAddTagsMenuItem = () => { +export const clickBulkAddTagsMenuItem = () => { clickTagsMenuItem(); cy.get(ADD_TAGS_RULE_BULK_MENU_ITEM).click(); }; +export const clickBulkDeleteTagsMenuItem = () => { + clickTagsMenuItem(); + cy.get(DELETE_TAGS_RULE_BULK_MENU_ITEM).click(); +}; + export const openBulkEditAddTagsForm = () => { - clickAddTagsMenuItem(); + clickBulkAddTagsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add tags'); }; export const openBulkEditDeleteTagsForm = () => { - clickTagsMenuItem(); - cy.get(DELETE_TAGS_RULE_BULK_MENU_ITEM).click(); + clickBulkDeleteTagsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Delete tags'); }; @@ -254,20 +262,24 @@ const clickInvestigationFieldsMenuItem = () => { cy.get(INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click(); }; -export const clickAddInvestigationFieldsMenuItem = () => { +export const clickBulkAddInvestigationFieldsMenuItem = () => { clickInvestigationFieldsMenuItem(); cy.get(ADD_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click(); }; +export const clickBulkDeleteInvestigationFieldsMenuItem = () => { + clickInvestigationFieldsMenuItem(); + cy.get(DELETE_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click(); +}; + export const openBulkEditAddInvestigationFieldsForm = () => { - clickAddInvestigationFieldsMenuItem(); + clickBulkAddInvestigationFieldsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add custom highlighted fields'); }; export const openBulkEditDeleteInvestigationFieldsForm = () => { - clickInvestigationFieldsMenuItem(); - cy.get(DELETE_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click(); + clickBulkDeleteInvestigationFieldsMenuItem(); cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Delete custom highlighted fields'); }; @@ -315,9 +327,13 @@ export const clickDeleteAlertSuppressionMenuItem = () => { }; // EDIT-SCHEDULE -export const clickUpdateScheduleMenuItem = () => { +export const clickBulkEditRuleScheduleMenuItem = () => { cy.get(BULK_ACTIONS_BTN).click(); cy.get(UPDATE_SCHEDULE_MENU_ITEM).click(); +}; + +export const clickUpdateScheduleMenuItem = () => { + clickBulkEditRuleScheduleMenuItem(); cy.get(UPDATE_SCHEDULE_MENU_ITEM).should('not.exist'); };