diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index 80536451b6f3c..b49e04f566c4b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -47,6 +47,7 @@ import { kqlQueryDiffAlgorithm, eqlQueryDiffAlgorithm, esqlQueryDiffAlgorithm, + ruleTypeDiffAlgorithm, } from './algorithms'; const BASE_TYPE_ERROR = `Base version can't be of different rule type`; @@ -212,7 +213,7 @@ const calculateCustomQueryFieldsDiff = ( }; const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, kql_query: kqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, @@ -225,7 +226,7 @@ const calculateSavedQueryFieldsDiff = ( }; const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, kql_query: kqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, @@ -238,7 +239,7 @@ const calculateEqlFieldsDiff = ( }; const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, eql_query: eqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, event_category_override: singleLineStringDiffAlgorithm, @@ -254,7 +255,7 @@ const calculateEsqlFieldsDiff = ( }; const esqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, esql_query: esqlQueryDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, }; @@ -266,7 +267,7 @@ const calculateThreatMatchFieldsDiff = ( }; const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, kql_query: kqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, threat_query: kqlQueryDiffAlgorithm, @@ -284,7 +285,7 @@ const calculateThresholdFieldsDiff = ( }; const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, kql_query: kqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, threshold: simpleDiffAlgorithm, @@ -299,7 +300,7 @@ const calculateMachineLearningFieldsDiff = ( const machineLearningFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, machine_learning_job_id: simpleDiffAlgorithm, anomaly_threshold: numberDiffAlgorithm, alert_suppression: simpleDiffAlgorithm, @@ -312,7 +313,7 @@ const calculateNewTermsFieldsDiff = ( }; const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, kql_query: kqlQueryDiffAlgorithm, data_source: dataSourceDiffAlgorithm, new_terms_fields: scalarArrayDiffAlgorithm, @@ -336,5 +337,5 @@ const allFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { ...thresholdFieldsDiffAlgorithms, ...machineLearningFieldsDiffAlgorithms, ...newTermsFieldsDiffAlgorithms, - type: simpleDiffAlgorithm, + type: ruleTypeDiffAlgorithm, }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts index e850963058af7..d42c00d3ab115 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.rule_type_fields.ts @@ -6,10 +6,12 @@ */ import expect from 'expect'; import { + RuleUpdateProps, ThreeWayDiffConflict, ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { getPrebuiltRuleMock } from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/mocks'; import { FtrProviderContext } from '../../../../../../ftr_provider_context'; import { deleteAllTimelines, @@ -20,6 +22,7 @@ import { reviewPrebuiltRulesToUpgrade, patchRule, createHistoricalPrebuiltRuleAssetSavedObjects, + updateRule, } from '../../../../utils'; import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; @@ -59,7 +62,7 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligible for update // but type field is NOT returned const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); + expect(reviewResponse.rules[0].diff.fields.type).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(0); @@ -78,10 +81,15 @@ export default ({ getService }: FtrProviderContext): void => { await installPrebuiltRules(es, supertest); // Customize a type field on the installed rule - await patchRule(supertest, log, { + await updateRule(supertest, { + ...getPrebuiltRuleMock(), rule_id: 'rule-1', - type: 'eql', - }); + type: 'saved_query', + query: undefined, + language: undefined, + filters: undefined, + saved_id: 'saved-query-id', + } as RuleUpdateProps); // Increment the version of the installed rule, do NOT update the related type field, and create the new rule assets const updatedRuleAssetSavedObjects = [ @@ -96,9 +104,9 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade review prebuilt rules endpoint and check that type diff field // is returned but field does not have an update, and the merge outcome is "Target" const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ + expect(reviewResponse.rules[0].diff.fields.type).toEqual({ base_version: 'query', - current_version: 'eql', + current_version: 'saved_query', target_version: 'query', merged_version: 'query', diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, @@ -129,18 +137,19 @@ export default ({ getService }: FtrProviderContext): void => { createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2, - type: 'eql', + type: 'saved_query', + saved_id: 'even-newer-saved-query-id', }), ]; await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ + expect(reviewResponse.rules[0].diff.fields.type).toEqual({ base_version: 'query', current_version: 'query', - target_version: 'eql', - merged_version: 'eql', + target_version: 'saved_query', + merged_version: 'saved_query', diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, merge_outcome: ThreeWayMergeOutcome.Target, conflict: ThreeWayDiffConflict.NON_SOLVABLE, @@ -148,7 +157,7 @@ export default ({ getService }: FtrProviderContext): void => { has_base_version: true, }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(3); // version and query fields also have updates expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); @@ -165,28 +174,34 @@ export default ({ getService }: FtrProviderContext): void => { await installPrebuiltRules(es, supertest); // Customize a type field on the installed rule - await patchRule(supertest, log, { + await updateRule(supertest, { + ...getPrebuiltRuleMock(), rule_id: 'rule-1', - type: 'eql', - }); + type: 'saved_query', + query: undefined, + language: undefined, + filters: undefined, + saved_id: 'saved-query-id', + } as RuleUpdateProps); // Increment the version of the installed rule, update a type field, and create the new rule assets const updatedRuleAssetSavedObjects = [ createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2, - type: 'eql', + type: 'saved_query', + saved_id: 'saved-query-id', }), ]; await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ + expect(reviewResponse.rules[0].diff.fields.type).toEqual({ base_version: 'query', - current_version: 'eql', - target_version: 'eql', - merged_version: 'eql', + current_version: 'saved_query', + target_version: 'saved_query', + merged_version: 'saved_query', diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, merge_outcome: ThreeWayMergeOutcome.Target, conflict: ThreeWayDiffConflict.NON_SOLVABLE, @@ -211,10 +226,15 @@ export default ({ getService }: FtrProviderContext): void => { await installPrebuiltRules(es, supertest); // Customize a type field on the installed rule - await patchRule(supertest, log, { + await updateRule(supertest, { + ...getPrebuiltRuleMock(), rule_id: 'rule-1', - type: 'eql', - }); + type: 'saved_query', + query: undefined, + language: undefined, + filters: undefined, + saved_id: 'saved-query-id', + } as RuleUpdateProps); // Increment the version of the installed rule, update a type field, and create the new rule assets const updatedRuleAssetSavedObjects = [ @@ -222,6 +242,7 @@ export default ({ getService }: FtrProviderContext): void => { rule_id: 'rule-1', version: 2, type: 'esql', + language: 'esql', }), ]; await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); @@ -229,9 +250,9 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update // and type field update has NON_SOLVABLE conflict, and merged version is TARGET const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ + expect(reviewResponse.rules[0].diff.fields.type).toEqual({ base_version: 'query', - current_version: 'eql', + current_version: 'saved_query', target_version: 'esql', merged_version: 'esql', diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, @@ -241,9 +262,9 @@ export default ({ getService }: FtrProviderContext): void => { has_base_version: true, }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); - expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(4); // version + type + kql_query all considered updates + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // type + kql_query both considered conflicts + expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(2); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); expect(reviewResponse.stats.num_rules_with_conflicts).toBe(1); @@ -274,7 +295,7 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update // but does NOT contain type field const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toBeUndefined(); + expect(reviewResponse.rules[0].diff.fields.type).toBeUndefined(); expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(1); expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(1); // version is considered a conflict @@ -306,7 +327,8 @@ export default ({ getService }: FtrProviderContext): void => { createRuleAssetSavedObject({ rule_id: 'rule-1', version: 2, - type: 'eql', + type: 'saved_query', + saved_id: 'saved-query-id', }), ]; await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); @@ -314,10 +336,10 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update // and type field update does not have a conflict const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); - expect(reviewResponse.rules[0].diff.fields.name).toEqual({ + expect(reviewResponse.rules[0].diff.fields.type).toEqual({ current_version: 'query', - target_version: 'eql', - merged_version: 'eql', + target_version: 'saved_query', + merged_version: 'saved_query', diff_outcome: ThreeWayDiffOutcome.MissingBaseCanUpdate, merge_outcome: ThreeWayMergeOutcome.Target, conflict: ThreeWayDiffConflict.NON_SOLVABLE, @@ -325,8 +347,8 @@ export default ({ getService }: FtrProviderContext): void => { has_base_version: false, }); - expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(2); - expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(2); // type + version are both considered conflicts + expect(reviewResponse.rules[0].diff.num_fields_with_updates).toBe(3); + expect(reviewResponse.rules[0].diff.num_fields_with_conflicts).toBe(3); // type + version + query are all considered conflicts expect(reviewResponse.rules[0].diff.num_fields_with_non_solvable_conflicts).toBe(1); expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1);