From e2eaf0c7d2e23a82de4985d3c22fc172197d4273 Mon Sep 17 00:00:00 2001 From: Dmitrii Shevchenko Date: Wed, 4 Dec 2024 13:06:34 +0100 Subject: [PATCH] [8.16] [Security Solution] Skip isCustomized calculation when the feature flag is off (#201825) (#202751) # Backport This will backport the following commits from `main` to `8.16`: - [[Security Solution] Skip isCustomized calculation when the feature flag is off (#201825)](https://github.com/elastic/kibana/pull/201825) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) \n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n\n\nCo-authored-by: Dmitrii Shevchenko "}},{"branch":"8.x","label":"v8.18.0","labelRegex":"^v8.18.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/202697","number":202697,"state":"MERGED","mergeCommit":{"sha":"8c9181aa48796a8467e38ad1431238ebaa78de7e","message":"[8.x] [Security Solution] Skip isCustomized calculation when the feature flag is off (#201825) (#202697)\n\n# Backport\n\nThis will backport the following commits from `main` to `8.x`:\n- [[Security Solution] Skip isCustomized calculation when the feature\nflag is off (#201825)](https://github.com/elastic/kibana/pull/201825)\n\n\n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n\n\nCo-authored-by: Dmitrii Shevchenko "}},{"branch":"8.16","label":"v8.16.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --- .../ftr_security_serverless_configs.yml | 3 +- .buildkite/ftr_security_stateful_configs.yml | 3 +- .../logic/bulk_actions/bulk_edit_rules.ts | 14 +- ...on_rules_client.create_custom_rule.test.ts | 1 + ..._rules_client.create_prebuilt_rule.test.ts | 1 + ...detection_rules_client.delete_rule.test.ts | 1 + ...detection_rules_client.import_rule.test.ts | 1 + .../detection_rules_client.patch_rule.test.ts | 1 + .../detection_rules_client.ts | 6 + ...detection_rules_client.update_rule.test.ts | 1 + ...rules_client.upgrade_prebuilt_rule.test.ts | 1 + .../mergers/apply_rule_patch.test.ts | 22 ++ .../mergers/apply_rule_patch.ts | 3 + .../mergers/apply_rule_update.ts | 3 + .../rule_source/calculate_is_customized.ts | 20 +- .../rule_source/calculate_rule_source.test.ts | 25 ++ .../rule_source/calculate_rule_source.ts | 8 +- .../methods/import_rule.ts | 3 + .../methods/patch_rule.ts | 3 + .../methods/update_rule.ts | 3 + .../methods/upgrade_prebuilt_rule.ts | 3 + .../server/request_context_factory.ts | 1 + .../configs/ess.config.ts | 25 ++ .../configs/serverless.config.ts | 17 ++ .../customization_disabled/index.ts | 14 ++ .../is_customized_calculation.ts | 218 ++++++++++++++++++ .../configs/ess.config.ts | 2 +- .../configs/serverless.config.ts | 2 +- .../customization_enabled/import_rules.ts | 185 +++++++++++++++ .../index.ts | 2 +- .../is_customized_calculation.ts | 0 .../rules_export.ts | 0 .../patch_rules.ts | 28 +-- 33 files changed, 586 insertions(+), 34 deletions(-) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless.config.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/index.ts create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/is_customized_calculation.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/{trial_license_complete_tier => customization_enabled}/configs/ess.config.ts (91%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/{trial_license_complete_tier => customization_enabled}/configs/serverless.config.ts (84%) create mode 100644 x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/import_rules.ts rename x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/{trial_license_complete_tier => customization_enabled}/index.ts (84%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/{trial_license_complete_tier => customization_enabled}/is_customized_calculation.ts (100%) rename x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/{trial_license_complete_tier => customization_enabled}/rules_export.ts (100%) diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index 1efb4a684db05..b7e2434e7ea2d 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -60,7 +60,8 @@ disabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/configs/serverless.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/serverless.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/serverless.config.ts diff --git a/.buildkite/ftr_security_stateful_configs.yml b/.buildkite/ftr_security_stateful_configs.yml index dbe529596102e..1dd0cb5d64185 100644 --- a/.buildkite/ftr_security_stateful_configs.yml +++ b/.buildkite/ftr_security_stateful_configs.yml @@ -48,7 +48,8 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/large_prebuilt_rules_package/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/update_prebuilt_rules_package/trial_license_complete_tier/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/ess.config.ts + - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/trial_license_complete_tier/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_delete/basic_license_essentials_tier/configs/ess.config.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts index bee44a3600f97..b9c97c2412b1a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts @@ -92,14 +92,20 @@ export const bulkEditRules = async ({ params: modifiedParams, }; const ruleResponse = convertAlertingRuleToRuleResponse(updatedRule); + let isCustomized = false; + if (ruleResponse.immutable === true) { + isCustomized = calculateIsCustomized({ + baseRule: baseVersionsMap.get(ruleResponse.rule_id), + nextRule: ruleResponse, + isRuleCustomizationEnabled: experimentalFeatures.prebuiltRulesCustomizationEnabled, + }); + } + const ruleSource = ruleResponse.immutable === true ? { type: 'external' as const, - isCustomized: calculateIsCustomized( - baseVersionsMap.get(ruleResponse.rule_id), - ruleResponse - ), + isCustomized, } : { type: 'internal' as const, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts index af3f85f3e1345..b07f95f74d2f9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts @@ -51,6 +51,7 @@ describe('DetectionRulesClient.createCustomRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts index 4caeb4bd9b940..6e1e75855b6a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts @@ -44,6 +44,7 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.delete_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.delete_rule.test.ts index ea5d1453753c3..a4afe5abadb6b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.delete_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.delete_rule.test.ts @@ -30,6 +30,7 @@ describe('DetectionRulesClient.deleteRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.import_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.import_rule.test.ts index e3b922fa831a6..9337fe553db24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.import_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.import_rule.test.ts @@ -55,6 +55,7 @@ describe('DetectionRulesClient.importRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts index 448df6b581a3b..18fe3c54da7e0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.patch_rule.test.ts @@ -50,6 +50,7 @@ describe('DetectionRulesClient.patchRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts index 4753df0ffe411..040d2156f5f79 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts @@ -35,6 +35,7 @@ interface DetectionRulesClientParams { rulesClient: RulesClient; savedObjectsClient: SavedObjectsClientContract; mlAuthz: MlAuthz; + isRuleCustomizationEnabled: boolean; } export const createDetectionRulesClient = ({ @@ -42,6 +43,7 @@ export const createDetectionRulesClient = ({ rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled, }: DetectionRulesClientParams): IDetectionRulesClient => { const prebuiltRuleAssetClient = createPrebuiltRuleAssetsClient(savedObjectsClient); @@ -86,6 +88,7 @@ export const createDetectionRulesClient = ({ prebuiltRuleAssetClient, mlAuthz, ruleUpdate, + isRuleCustomizationEnabled, }); }); }, @@ -98,6 +101,7 @@ export const createDetectionRulesClient = ({ prebuiltRuleAssetClient, mlAuthz, rulePatch, + isRuleCustomizationEnabled, }); }); }, @@ -116,6 +120,7 @@ export const createDetectionRulesClient = ({ ruleAsset, mlAuthz, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }); }); }, @@ -128,6 +133,7 @@ export const createDetectionRulesClient = ({ importRulePayload: args, mlAuthz, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }); }); }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts index cbd0fb1fe3680..64c01ab395529 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.update_rule.test.ts @@ -50,6 +50,7 @@ describe('DetectionRulesClient.updateRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.upgrade_prebuilt_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.upgrade_prebuilt_rule.test.ts index acdb7b9653930..ed186abe5a7c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.upgrade_prebuilt_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.upgrade_prebuilt_rule.test.ts @@ -48,6 +48,7 @@ describe('DetectionRulesClient.upgradePrebuiltRule', () => { rulesClient, mlAuthz, savedObjectsClient, + isRuleCustomizationEnabled: true, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.test.ts index abf90c3f4dfc4..0d780c07aac19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.test.ts @@ -37,6 +37,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -65,6 +66,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -94,6 +96,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError( 'event_category_override: Expected string, received number, tiebreaker_field: Expected string, received number, timestamp_field: Expected string, received number' @@ -119,6 +122,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError('alert_suppression.group_by: Expected array, received string'); }); @@ -134,6 +138,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -154,6 +159,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError( 'threat_query: Expected string, received number, threat_indicator_path: Expected string, received number' @@ -170,6 +176,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -190,6 +197,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError( "index.0: Expected string, received number, language: Invalid enum value. Expected 'kuery' | 'lucene', received 'non-language'" @@ -206,6 +214,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -226,6 +235,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError( "index.0: Expected string, received number, language: Invalid enum value. Expected 'kuery' | 'lucene', received 'non-language'" @@ -244,6 +254,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -268,6 +279,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError('threshold.value: Expected number, received string'); }); @@ -285,6 +297,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -308,6 +321,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -330,6 +344,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -354,6 +369,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -376,6 +392,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -394,6 +411,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError('anomaly_threshold: Expected number, received string'); }); @@ -410,6 +428,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( @@ -432,6 +451,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ @@ -450,6 +470,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }) ).rejects.toThrowError('new_terms_fields: Expected array, received string'); }); @@ -472,6 +493,7 @@ describe('applyRulePatch', () => { rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled: true, }); expect(patchedRule).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts index 9f5b167322491..aaaab474c2fbe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_patch.ts @@ -51,6 +51,7 @@ interface ApplyRulePatchProps { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; existingRule: RuleResponse; rulePatch: PatchRuleRequestBody; + isRuleCustomizationEnabled: boolean; } // eslint-disable-next-line complexity @@ -58,6 +59,7 @@ export const applyRulePatch = async ({ rulePatch, existingRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }: ApplyRulePatchProps): Promise => { const typeSpecificParams = patchTypeSpecificParams(rulePatch, existingRule); @@ -122,6 +124,7 @@ export const applyRulePatch = async ({ nextRule.rule_source = await calculateRuleSource({ rule: nextRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }); return nextRule; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_update.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_update.ts index b911e66a1fc45..850924d3cd04d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_update.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/apply_rule_update.ts @@ -17,12 +17,14 @@ interface ApplyRuleUpdateProps { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; existingRule: RuleResponse; ruleUpdate: RuleUpdateProps; + isRuleCustomizationEnabled: boolean; } export const applyRuleUpdate = async ({ prebuiltRuleAssetClient, existingRule, ruleUpdate, + isRuleCustomizationEnabled, }: ApplyRuleUpdateProps): Promise => { const nextRule: RuleResponse = { ...applyRuleDefaults(ruleUpdate), @@ -46,6 +48,7 @@ export const applyRuleUpdate = async ({ nextRule.rule_source = await calculateRuleSource({ rule: nextRule, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }); return nextRule; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts index 92c7d941dd7f1..a94506486cc53 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts @@ -12,10 +12,22 @@ import { calculateRuleFieldsDiff } from '../../../../../prebuilt_rules/logic/dif import { convertRuleToDiffable } from '../../../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../converters/convert_prebuilt_rule_asset_to_rule_response'; -export function calculateIsCustomized( - baseRule: PrebuiltRuleAsset | undefined, - nextRule: RuleResponse -) { +interface CalculateIsCustomizedArgs { + baseRule: PrebuiltRuleAsset | undefined; + nextRule: RuleResponse; + isRuleCustomizationEnabled: boolean; +} + +export function calculateIsCustomized({ + baseRule, + nextRule, + isRuleCustomizationEnabled, +}: CalculateIsCustomizedArgs) { + if (!isRuleCustomizationEnabled) { + // We don't want to accidentally mark rules as customized when customization is disabled. + return false; + } + if (baseRule == null) { // If the base version is missing, we consider the rule to be customized return true; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.test.ts index e44c69d2705d5..d0b9f722458a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.test.ts @@ -43,6 +43,7 @@ describe('calculateRuleSource', () => { const result = await calculateRuleSource({ prebuiltRuleAssetClient, rule, + isRuleCustomizationEnabled: true, }); expect(result).toEqual({ type: 'internal', @@ -59,6 +60,7 @@ describe('calculateRuleSource', () => { const result = await calculateRuleSource({ prebuiltRuleAssetClient, rule, + isRuleCustomizationEnabled: true, }); expect(result).toEqual( expect.objectContaining({ @@ -79,6 +81,7 @@ describe('calculateRuleSource', () => { const result = await calculateRuleSource({ prebuiltRuleAssetClient, rule, + isRuleCustomizationEnabled: true, }); expect(result).toEqual( expect.objectContaining({ @@ -101,6 +104,28 @@ describe('calculateRuleSource', () => { const result = await calculateRuleSource({ prebuiltRuleAssetClient, rule, + isRuleCustomizationEnabled: true, + }); + expect(result).toEqual( + expect.objectContaining({ + type: 'external', + is_customized: false, + }) + ); + }); + + it('returns is_customized false when the rule is customized but customization is disabled', async () => { + const rule = getSampleRule(); + rule.immutable = true; + rule.name = 'Updated name'; + + const baseRule = getSampleRuleAsset(); + prebuiltRuleAssetClient.fetchAssetsByVersion.mockResolvedValueOnce([baseRule]); + + const result = await calculateRuleSource({ + prebuiltRuleAssetClient, + rule, + isRuleCustomizationEnabled: false, }); expect(result).toEqual( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.ts index 742cd20544a60..6c3d2419159a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_rule_source.ts @@ -16,11 +16,13 @@ import { calculateIsCustomized } from './calculate_is_customized'; interface CalculateRuleSourceProps { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; rule: RuleResponse; + isRuleCustomizationEnabled: boolean; } export async function calculateRuleSource({ prebuiltRuleAssetClient, rule, + isRuleCustomizationEnabled, }: CalculateRuleSourceProps): Promise { if (rule.immutable) { // This is a prebuilt rule and, despite the name, they are not immutable. So @@ -33,7 +35,11 @@ export async function calculateRuleSource({ ]); const baseRule: PrebuiltRuleAsset | undefined = prebuiltRulesResponse.at(0); - const isCustomized = calculateIsCustomized(baseRule, rule); + const isCustomized = calculateIsCustomized({ + baseRule, + nextRule: rule, + isRuleCustomizationEnabled, + }); return { type: 'external', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/import_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/import_rule.ts index 4066cb00849a3..4211110cebe4e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/import_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/import_rule.ts @@ -26,6 +26,7 @@ interface ImportRuleOptions { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; importRulePayload: ImportRuleArgs; mlAuthz: MlAuthz; + isRuleCustomizationEnabled: boolean; } export const importRule = async ({ @@ -34,6 +35,7 @@ export const importRule = async ({ importRulePayload, prebuiltRuleAssetClient, mlAuthz, + isRuleCustomizationEnabled, }: ImportRuleOptions): Promise => { const { ruleToImport, overwriteRules, allowMissingConnectorSecrets } = importRulePayload; @@ -57,6 +59,7 @@ export const importRule = async ({ prebuiltRuleAssetClient, existingRule, ruleUpdate: ruleToImport, + isRuleCustomizationEnabled, }); const updatedRule = await rulesClient.update({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts index 113576e8d02e2..0b22ea39eaef4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/patch_rule.ts @@ -28,6 +28,7 @@ interface PatchRuleOptions { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; rulePatch: RulePatchProps; mlAuthz: MlAuthz; + isRuleCustomizationEnabled: boolean; } export const patchRule = async ({ @@ -36,6 +37,7 @@ export const patchRule = async ({ prebuiltRuleAssetClient, rulePatch, mlAuthz, + isRuleCustomizationEnabled, }: PatchRuleOptions): Promise => { const { rule_id: ruleId, id } = rulePatch; @@ -58,6 +60,7 @@ export const patchRule = async ({ prebuiltRuleAssetClient, existingRule, rulePatch, + isRuleCustomizationEnabled, }); const patchedInternalRule = await rulesClient.update({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts index 8fd7f7a89dcb7..3d465d44fdc1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/update_rule.ts @@ -27,6 +27,7 @@ interface UpdateRuleArguments { prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; ruleUpdate: RuleUpdateProps; mlAuthz: MlAuthz; + isRuleCustomizationEnabled: boolean; } export const updateRule = async ({ @@ -35,6 +36,7 @@ export const updateRule = async ({ prebuiltRuleAssetClient, ruleUpdate, mlAuthz, + isRuleCustomizationEnabled, }: UpdateRuleArguments): Promise => { const { rule_id: ruleId, id } = ruleUpdate; @@ -57,6 +59,7 @@ export const updateRule = async ({ prebuiltRuleAssetClient, existingRule, ruleUpdate, + isRuleCustomizationEnabled, }); const updatedRule = await rulesClient.update({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts index 64486bed14304..dff7c8a333ca7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/upgrade_prebuilt_rule.ts @@ -25,12 +25,14 @@ export const upgradePrebuiltRule = async ({ ruleAsset, mlAuthz, prebuiltRuleAssetClient, + isRuleCustomizationEnabled, }: { actionsClient: ActionsClient; rulesClient: RulesClient; ruleAsset: PrebuiltRuleAsset; mlAuthz: MlAuthz; prebuiltRuleAssetClient: IPrebuiltRuleAssetsClient; + isRuleCustomizationEnabled: boolean; }): Promise => { await validateMlAuth(mlAuthz, ruleAsset.type); @@ -73,6 +75,7 @@ export const upgradePrebuiltRule = async ({ prebuiltRuleAssetClient, existingRule, ruleUpdate: ruleAsset, + isRuleCustomizationEnabled, }); const updatedInternalRule = await rulesClient.update({ diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts index aebf0fb6dfd8f..a13e7b6300a3a 100644 --- a/x-pack/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/plugins/security_solution/server/request_context_factory.ts @@ -139,6 +139,7 @@ export class RequestContextFactory implements IRequestContextFactory { rulesClient: startPlugins.alerting.getRulesClientWithRequest(request), savedObjectsClient: coreContext.savedObjects.client, mlAuthz, + isRuleCustomizationEnabled: config.experimentalFeatures.prebuiltRulesCustomizationEnabled, }); }), diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess.config.ts new file mode 100644 index 0000000000000..2e1b278c9247f --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/ess.config.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile( + require.resolve('../../../../../../../config/ess/config.base.trial') + ); + + const testConfig = { + ...functionalConfig.getAll(), + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Prebuilt Rule Customization Disabled Integration Tests - ESS Env', + }, + }; + + return testConfig; +} diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless.config.ts new file mode 100644 index 0000000000000..462a971d8dda7 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/configs/serverless.config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../../../../../../config/serverless/config.base'; + +export default createTestConfig({ + testFiles: [require.resolve('..')], + junit: { + reportName: + 'Rules Management - Prebuilt Rule Customization Disabled Integration Tests - Serverless Env', + }, + kbnTestServerArgs: [], +}); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/index.ts new file mode 100644 index 0000000000000..0d78605426dbf --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Rules Management - Prebuilt Rules - Prebuilt Rule Customization Disabled', function () { + loadTestFile(require.resolve('./is_customized_calculation')); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/is_customized_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/is_customized_calculation.ts new file mode 100644 index 0000000000000..91d904fbc87ac --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_disabled/is_customized_calculation.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from 'expect'; +import { + BulkActionEditTypeEnum, + BulkActionTypeEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +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 is_customized calculation with disabled customization', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + it('should set is_customized to "false" on prebuilt rule PATCH', 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 } = await securitySolutionApi + .patchRule({ + body: { + rule_id: 'test-rule-id', + name: 'some other rule name', + }, + }) + .expect(200); + + // Check that the rule name has been updated and the rule is still not customized + expect(body).toEqual( + expect.objectContaining({ + name: 'some other rule name', + }) + ); + expect(body.rule_source.is_customized).toEqual(false); + }); + + it('should set is_customized to "false" on prebuilt rule UPDATE', 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 } = await securitySolutionApi + .updateRule({ + body: { + ...prebuiltRule, + id: undefined, // id together with rule_id is not allowed + name: 'some other rule name', + }, + }) + .expect(200); + + // Check that the rule name has been updated and the rule is still not customized + expect(body).toEqual( + expect.objectContaining({ + name: 'some other rule name', + }) + ); + expect(body.rule_source.is_customized).toEqual(false); + }); + + 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: {} }) + .attach('file', ruleBuffer, 'rules.ndjson') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + + expect(body).toMatchObject({ + rules_count: 1, + success: false, + success_count: 0, + errors: [ + { + error: { + message: expect.stringContaining('immutable: Invalid literal value, expected false'), + }, + rule_id: '(unknown id)', + }, + ], + }); + + // Check that the rule has not been customized + const { body: importedRule } = await securitySolutionApi.readRule({ + query: { rule_id: prebuiltRule.rule_id }, + }); + expect(importedRule.rule_source.is_customized).toEqual(false); + }); + + 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/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/ess.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/ess.config.ts similarity index 91% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/ess.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/ess.config.ts index eee14323c9b98..ff46ebb70d7c8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/ess.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/ess.config.ts @@ -17,7 +17,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('..')], junit: { reportName: - 'Rules Management - Prebuilt Rule Customization Integration Tests - ESS Env - Trial License', + 'Rules Management - Prebuilt Rule Customization Enabled Integration Tests - ESS Env', }, }; testConfig.kbnTestServer.serverArgs = testConfig.kbnTestServer.serverArgs.map((arg: string) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/serverless.config.ts similarity index 84% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/serverless.config.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/serverless.config.ts index 79a23c85d2279..8c1d777665667 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/configs/serverless.config.ts @@ -11,7 +11,7 @@ export default createTestConfig({ testFiles: [require.resolve('..')], junit: { reportName: - 'Rules Management - Prebuilt Rule Customization Integration Tests - Serverless Env - Complete Tier', + 'Rules Management - Prebuilt Rule Customization Enabled Integration Tests - Serverless Env', }, kbnTestServerArgs: [ `--xpack.securitySolution.enableExperimental=${JSON.stringify([ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/import_rules.ts new file mode 100644 index 0000000000000..934ee6460a5e2 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/import_rules.ts @@ -0,0 +1,185 @@ +/* + * 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 { + SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS, + combineArrayToNdJson, + createHistoricalPrebuiltRuleAssetSavedObjects, + deleteAllPrebuiltRuleAssets, + fetchRule, + getCustomQueryRuleParams, + getInstalledRules, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + const securitySolutionApi = getService('securitySolutionApi'); + + const importRules = async (rules: unknown[]) => { + const buffer = Buffer.from(combineArrayToNdJson(rules)); + + return securitySolutionApi + .importRules({ query: {} }) + .attach('file', buffer, 'rules.ndjson') + .expect('Content-Type', 'application/json; charset=utf-8') + .expect(200); + }; + + const prebuiltRules = SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS.map( + (prebuiltRule) => prebuiltRule['security-rule'] + ); + const prebuiltRuleIds = [...new Set(prebuiltRules.map((rule) => rule.rule_id))]; + + describe('@ess @serverless @skipInServerlessMKI import_rules', () => { + before(async () => { + await deleteAllPrebuiltRuleAssets(es, log); + await createHistoricalPrebuiltRuleAssetSavedObjects( + es, + SAMPLE_PREBUILT_RULES_WITH_HISTORICAL_VERSIONS + ); + }); + + after(async () => { + await deleteAllPrebuiltRuleAssets(es, log); + await deleteAllRules(supertest, log); + }); + + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + describe('calculation of rule customization fields', () => { + it('defaults a versionless custom rule to "version: 1"', async () => { + const rule = getCustomQueryRuleParams({ rule_id: 'custom-rule', version: undefined }); + const { body } = await importRules([rule]); + + expect(body).toMatchObject({ + rules_count: 1, + success: true, + success_count: 1, + errors: [], + }); + + const importedRule = await fetchRule(supertest, { ruleId: rule.rule_id! }); + expect(importedRule).toMatchObject({ + rule_id: rule.rule_id, + version: 1, + rule_source: { type: 'internal' }, + immutable: false, + }); + }); + + it('preserves a custom rule with a specified version', async () => { + const rule = getCustomQueryRuleParams({ rule_id: 'custom-rule', version: 23 }); + const { body } = await importRules([rule]); + + expect(body).toMatchObject({ + rules_count: 1, + success: true, + success_count: 1, + errors: [], + }); + + const importedRule = await fetchRule(supertest, { ruleId: rule.rule_id! }); + expect(importedRule).toMatchObject({ + rule_id: rule.rule_id, + version: 23, + rule_source: { type: 'internal' }, + immutable: false, + }); + }); + + it('rejects a versionless prebuilt rule', async () => { + const rule = getCustomQueryRuleParams({ rule_id: prebuiltRuleIds[0], version: undefined }); + const { body } = await importRules([rule]); + + expect(body.errors).toHaveLength(1); + expect(body.errors[0]).toMatchObject({ + error: { + message: `Prebuilt rules must specify a "version" to be imported. [rule_id: ${prebuiltRuleIds[0]}]`, + status_code: 400, + }, + }); + }); + + it('respects the version of a prebuilt rule', async () => { + const rule = getCustomQueryRuleParams({ rule_id: prebuiltRuleIds[1], version: 9999 }); + const { body } = await importRules([rule]); + + expect(body).toMatchObject({ + rules_count: 1, + success: true, + success_count: 1, + errors: [], + }); + + const importedRule = await fetchRule(supertest, { ruleId: rule.rule_id! }); + expect(importedRule).toMatchObject({ + rule_id: rule.rule_id, + version: 9999, + rule_source: { type: 'external', is_customized: true }, + immutable: true, + }); + }); + + it('imports a combination of prebuilt and custom rules', async () => { + const rules = [ + getCustomQueryRuleParams({ rule_id: 'custom-rule', version: 23 }), + getCustomQueryRuleParams({ rule_id: prebuiltRuleIds[0], version: 1234 }), + getCustomQueryRuleParams({ rule_id: 'custom-rule-2', version: undefined }), + prebuiltRules[3], + ]; + const { body } = await importRules(rules); + + expect(body).toMatchObject({ + rules_count: 4, + success: true, + success_count: 4, + errors: [], + }); + + const { data: importedRules } = await getInstalledRules(supertest); + + expect(importedRules).toHaveLength(4); + expect(importedRules).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + rule_id: 'custom-rule', + version: 23, + rule_source: { type: 'internal' }, + immutable: false, + }), + expect.objectContaining({ + rule_id: prebuiltRuleIds[0], + version: 1234, + rule_source: { type: 'external', is_customized: true }, + immutable: true, + }), + expect.objectContaining({ + rule_id: 'custom-rule-2', + version: 1, + rule_source: { type: 'internal' }, + immutable: false, + }), + expect.objectContaining({ + rule_id: prebuiltRules[3].rule_id, + version: prebuiltRules[3].version, + rule_source: { type: 'external', is_customized: false }, + immutable: true, + }), + ]) + ); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/index.ts similarity index 84% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/index.ts index 4324ce4602d72..763a31c5e2d70 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/index.ts @@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../../../../ftr_provider_context'; export default ({ loadTestFile }: FtrProviderContext): void => { - describe('Rules Management - Prebuilt Rules - Update Prebuilt Rules Package', function () { + describe('Rules Management - Prebuilt Rules - Prebuilt Rule Customization Enabled', function () { loadTestFile(require.resolve('./is_customized_calculation')); loadTestFile(require.resolve('./rules_export')); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/is_customized_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/is_customized_calculation.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/is_customized_calculation.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/rules_export.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/trial_license_complete_tier/rules_export.ts rename to x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/prebuilt_rule_customization/customization_enabled/rules_export.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts index 378a1f7bb0187..9ed45b81cc1e4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts @@ -7,25 +7,21 @@ import expect from 'expect'; +import { createRule, deleteAllRules } from '../../../../../../common/utils/security_solution'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { + createHistoricalPrebuiltRuleAssetSavedObjects, + createRuleAssetSavedObject, + deleteAllPrebuiltRuleAssets, + getCustomQueryRuleParams, getSimpleRule, getSimpleRuleOutput, - getCustomQueryRuleParams, + getSimpleRuleOutputWithoutRuleId, + installPrebuiltRules, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, - getSimpleRuleOutputWithoutRuleId, updateUsername, - createHistoricalPrebuiltRuleAssetSavedObjects, - installPrebuiltRules, - createRuleAssetSavedObject, } from '../../../utils'; -import { - createAlertsIndex, - deleteAllRules, - createRule, - deleteAllAlerts, -} from '../../../../../../common/utils/security_solution'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); @@ -37,12 +33,8 @@ export default ({ getService }: FtrProviderContext) => { describe('@ess @serverless @serverlessQA patch_rules', () => { describe('patch rules', () => { beforeEach(async () => { - await createAlertsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); + await deleteAllPrebuiltRuleAssets(es, log); }); it('should patch a single rule property of name using a rule_id', async () => { @@ -261,10 +253,6 @@ export default ({ getService }: FtrProviderContext) => { }); describe('max signals', () => { - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - it('does NOT patch a rule when max_signals is less than 1', async () => { await securitySolutionApi.createRule({ body: getCustomQueryRuleParams({ rule_id: 'rule-1', max_signals: 100 }),