diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_route.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_route.ts index 164ab43db9648..0bc4c5d2d37c0 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_route.ts @@ -7,7 +7,7 @@ import { z } from '@kbn/zod'; import type { RuleResponse } from '../../model/rule_schema/rule_schemas.gen'; -import type { PartialRuleDiff } from '../model'; +import type { PartialThreeWayRuleDiff } from '../model'; export type GetPrebuiltRuleBaseVersionRequest = z.infer; export const GetPrebuiltRuleBaseVersionRequest = z.object({ @@ -22,5 +22,5 @@ export interface GetPrebuiltRuleBaseVersionResponseBody { current_version: RuleResponse; /** The resulting diff between the base and current versions of the rule */ - diff: PartialRuleDiff; + diff: PartialThreeWayRuleDiff; } diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts index 4d83778ded7eb..183e5b7447474 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/index.ts @@ -16,8 +16,8 @@ export * from './urls'; export * from './model/aggregated_prebuilt_rules_error'; export * from './model/diff/diffable_rule/diffable_field_types'; export * from './model/diff/diffable_rule/diffable_rule'; -export type * from './model/diff/rule_diff/fields_diff'; -export type * from './model/diff/rule_diff/rule_diff'; +export type * from './model/diff/three_way_diff/three_way_fields_diff'; +export type * from './model/diff/three_way_diff/three_way_rule_diff'; export * from './model/diff/three_way_diff/three_way_diff_outcome'; export * from './model/diff/three_way_diff/three_way_diff'; export * from './model/diff/three_way_diff/three_way_diff_conflict'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/rule_diff.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/rule_diff.ts deleted file mode 100644 index 8825413b4ceda..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/rule_diff.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - DiffableAllFields, - DiffableCommonFields, - DiffableCustomQueryFields, - DiffableEqlFields, - DiffableEsqlFields, - DiffableMachineLearningFields, - DiffableNewTermsFields, - DiffableSavedQueryFields, - DiffableThreatMatchFields, - DiffableThresholdFields, -} from '../diffable_rule/diffable_rule'; - -import type { FieldsDiff } from './fields_diff'; - -export type AllFieldsDiff = FieldsDiff; -export type CommonFieldsDiff = FieldsDiff; -export type CustomQueryFieldsDiff = FieldsDiff; -export type SavedQueryFieldsDiff = FieldsDiff; -export type EqlFieldsDiff = FieldsDiff; -export type EsqlFieldsDiff = FieldsDiff; -export type ThreatMatchFieldsDiff = FieldsDiff; -export type ThresholdFieldsDiff = FieldsDiff; -export type MachineLearningFieldsDiff = FieldsDiff; -export type NewTermsFieldsDiff = FieldsDiff; - -/** - * It's an object which keys are the same as keys of DiffableRule, but values are - * three-way diffs calculated for their values. - * - * @example - * { - * name: ThreeWayDiff; - * tags: ThreeWayDiff; - * // etc - * } - */ -export type RuleFieldsDiff = CommonFieldsDiff & - ( - | CustomQueryFieldsDiff - | SavedQueryFieldsDiff - | EqlFieldsDiff - | EsqlFieldsDiff - | ThreatMatchFieldsDiff - | ThresholdFieldsDiff - | MachineLearningFieldsDiff - | NewTermsFieldsDiff - ); - -interface BaseRuleDiff { - num_fields_with_updates: number; - num_fields_with_conflicts: number; - num_fields_with_non_solvable_conflicts: number; -} -/** - * Full rule diff contains diffs for all the top-level rule fields. - * Even if there's no change at all to a given field, its diff will be included in this object. - * This diff can be useful for internal server-side calculations or debugging. - * Note that this is a pretty large object so returning it from the API might be undesirable. - */ -export interface FullRuleDiff extends BaseRuleDiff { - fields: RuleFieldsDiff; -} - -/** - * Partial rule diff contains diffs only for those rule fields that have some changes to them. - * This diff can be useful for returning info from REST API endpoints because its size is tolerable. - */ -export interface PartialRuleDiff extends BaseRuleDiff { - fields: Partial; -} - -export type RuleFieldsDiffWithDataSource = - | CustomQueryFieldsDiff - | SavedQueryFieldsDiff - | EqlFieldsDiff - | ThreatMatchFieldsDiff - | ThresholdFieldsDiff - | NewTermsFieldsDiff; - -export type RuleFieldsDiffWithKqlQuery = - | CustomQueryFieldsDiff - | SavedQueryFieldsDiff - | ThreatMatchFieldsDiff - | ThresholdFieldsDiff - | NewTermsFieldsDiff; - -export type RuleFieldsDiffWithEqlQuery = EqlFieldsDiff; - -export type RuleFieldsDiffWithEsqlQuery = EsqlFieldsDiff; - -export type RuleFieldsDiffWithThreatQuery = ThreatMatchFieldsDiff; - -export type RuleFieldsDiffWithThreshold = ThresholdFieldsDiff; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts index 87ec80d1ac292..ef3e76a67200f 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff_outcome.ts @@ -42,14 +42,14 @@ export const determineDiffOutcome = ( currentVersion: TValue, targetVersion: TValue ): ThreeWayDiffOutcome => { - const baseEqlCurrent = isEqual(baseVersion, currentVersion); - const baseEqlTarget = isEqual(baseVersion, targetVersion); - const currentEqlTarget = isEqual(currentVersion, targetVersion); + const baseEqualsCurrent = isEqual(baseVersion, currentVersion); + const baseEqualsTarget = isEqual(baseVersion, targetVersion); + const currentEqualsTarget = isEqual(currentVersion, targetVersion); return getThreeWayDiffOutcome({ - baseEqlCurrent, - baseEqlTarget, - currentEqlTarget, + baseEqualsCurrent, + baseEqualsTarget, + currentEqualsTarget, hasBaseVersion: baseVersion !== MissingVersion, }); }; @@ -65,14 +65,13 @@ export const determineOrderAgnosticDiffOutcome = ( const baseSet = baseVersion === MissingVersion ? MissingVersion : new Set(baseVersion); const currentSet = new Set(currentVersion); const targetSet = new Set(targetVersion); - const baseEqlCurrent = isEqual(baseSet, currentSet); - const baseEqlTarget = isEqual(baseSet, targetSet); - const currentEqlTarget = isEqual(currentSet, targetSet); - + const baseEqualsCurrent = isEqual(baseSet, currentSet); + const baseEqualsTarget = isEqual(baseSet, targetSet); + const currentEqualsTarget = isEqual(currentSet, targetSet); return getThreeWayDiffOutcome({ - baseEqlCurrent, - baseEqlTarget, - currentEqlTarget, + baseEqualsCurrent, + baseEqualsTarget, + currentEqualsTarget, hasBaseVersion: baseVersion !== MissingVersion, }); }; @@ -105,16 +104,16 @@ export const determineDiffOutcomeForDataSource = ( }; interface DetermineDiffOutcomeProps { - baseEqlCurrent: boolean; - baseEqlTarget: boolean; - currentEqlTarget: boolean; + baseEqualsCurrent: boolean; + baseEqualsTarget: boolean; + currentEqualsTarget: boolean; hasBaseVersion: boolean; } const getThreeWayDiffOutcome = ({ - baseEqlCurrent, - baseEqlTarget, - currentEqlTarget, + baseEqualsCurrent, + baseEqualsTarget, + currentEqualsTarget, hasBaseVersion, }: DetermineDiffOutcomeProps): ThreeWayDiffOutcome => { if (!hasBaseVersion) { @@ -123,22 +122,22 @@ const getThreeWayDiffOutcome = ({ * version comparison is not possible. We assume that the rule is * customized and the value can be updated if there's an update. */ - return currentEqlTarget + return currentEqualsTarget ? ThreeWayDiffOutcome.MissingBaseNoUpdate : ThreeWayDiffOutcome.MissingBaseCanUpdate; } - if (baseEqlCurrent) { - return currentEqlTarget + if (baseEqualsCurrent) { + return currentEqualsTarget ? ThreeWayDiffOutcome.StockValueNoUpdate : ThreeWayDiffOutcome.StockValueCanUpdate; } - if (baseEqlTarget) { + if (baseEqualsTarget) { return ThreeWayDiffOutcome.CustomizedValueNoUpdate; } - return currentEqlTarget + return currentEqualsTarget ? ThreeWayDiffOutcome.CustomizedValueSameUpdate : ThreeWayDiffOutcome.CustomizedValueCanUpdate; }; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_fields_diff.ts similarity index 66% rename from x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff.ts rename to x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_fields_diff.ts index 9637faebe9c9c..fba3a31df6c92 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_fields_diff.ts @@ -5,12 +5,12 @@ * 2.0. */ -import type { ThreeWayDiff, ThreeWayDiffAlgorithm } from '../three_way_diff/three_way_diff'; +import type { ThreeWayDiff, ThreeWayDiffAlgorithm } from './three_way_diff'; -export type FieldsDiff = Required<{ +export type ThreeWayFieldsDiff = Required<{ [Field in keyof TObject]: ThreeWayDiff; }>; -export type FieldsDiffAlgorithmsFor = Required<{ +export type ThreeWayFieldsDiffAlgorithmsFor = Required<{ [Field in keyof TObject]: ThreeWayDiffAlgorithm; }>; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_rule_diff.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_rule_diff.ts new file mode 100644 index 0000000000000..e16e1d083efa8 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_rule_diff.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + DiffableAllFields, + DiffableCommonFields, + DiffableCustomQueryFields, + DiffableEqlFields, + DiffableEsqlFields, + DiffableMachineLearningFields, + DiffableNewTermsFields, + DiffableSavedQueryFields, + DiffableThreatMatchFields, + DiffableThresholdFields, +} from '../diffable_rule/diffable_rule'; + +import type { ThreeWayFieldsDiff } from './three_way_fields_diff'; + +export type AllThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type CommonThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type CustomQueryThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type SavedQueryThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type EqlThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type EsqlThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type ThreatMatchThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type ThresholdThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type MachineLearningThreeWayFieldsDiff = ThreeWayFieldsDiff; +export type NewTermsThreeWayFieldsDiff = ThreeWayFieldsDiff; + +/** + * It's an object which keys are the same as keys of DiffableRule, but values are + * three-way diffs calculated for their values. + * + * @example + * { + * name: ThreeWayDiff; + * tags: ThreeWayDiff; + * // etc + * } + */ +export type ThreeWayRuleFieldsDiff = CommonThreeWayFieldsDiff & + ( + | CustomQueryThreeWayFieldsDiff + | SavedQueryThreeWayFieldsDiff + | EqlThreeWayFieldsDiff + | EsqlThreeWayFieldsDiff + | ThreatMatchThreeWayFieldsDiff + | ThresholdThreeWayFieldsDiff + | MachineLearningThreeWayFieldsDiff + | NewTermsThreeWayFieldsDiff + ); + +interface ThreeWayBaseRuleDiff { + num_fields_with_updates: number; + num_fields_with_conflicts: number; + num_fields_with_non_solvable_conflicts: number; +} +/** + * Full rule diff contains diffs for all the top-level rule fields. + * Even if there's no change at all to a given field, its diff will be included in this object. + * This diff can be useful for internal server-side calculations or debugging. + * Note that this is a pretty large object so returning it from the API might be undesirable. + */ +export interface FullThreeWayRuleDiff extends ThreeWayBaseRuleDiff { + fields: ThreeWayRuleFieldsDiff; +} + +/** + * Partial rule diff contains diffs only for those rule fields that have some changes to them. + * This diff can be useful for returning info from REST API endpoints because its size is tolerable. + */ +export interface PartialThreeWayRuleDiff extends ThreeWayBaseRuleDiff { + fields: Partial; +} + +export type RuleFieldsDiffWithDataSource = + | CustomQueryThreeWayFieldsDiff + | SavedQueryThreeWayFieldsDiff + | EqlThreeWayFieldsDiff + | ThreatMatchThreeWayFieldsDiff + | ThresholdThreeWayFieldsDiff + | NewTermsThreeWayFieldsDiff; + +export type RuleFieldsDiffWithKqlQuery = + | CustomQueryThreeWayFieldsDiff + | SavedQueryThreeWayFieldsDiff + | ThreatMatchThreeWayFieldsDiff + | ThresholdThreeWayFieldsDiff + | NewTermsThreeWayFieldsDiff; + +export type RuleFieldsDiffWithEqlQuery = EqlThreeWayFieldsDiff; + +export type RuleFieldsDiffWithEsqlQuery = EsqlThreeWayFieldsDiff; + +export type RuleFieldsDiffWithThreatQuery = ThreatMatchThreeWayFieldsDiff; + +export type RuleFieldsDiffWithThreshold = ThresholdThreeWayFieldsDiff; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_diff_outcome.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_diff_outcome.ts new file mode 100644 index 0000000000000..7e5f3ff779e9e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_diff_outcome.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Used for the structures that contain the two-way diff algorithms + * + * These types will throw an error when a field is missing from the comparator groups, + * alerting us to a field that won't be compared during the two-way diff calculation. + */ +export type TwoWayFieldsDiffAlgorithmsFor = Required<{ + [Field in keyof TObject]: TwoWayDiffAlgorithm; +}>; + +/** + * Type for the two way diff algorithm comparison itself + * + * All of these algorithms take in two field values and return if the values are equal + * to one another. + */ +export type TwoWayDiffAlgorithm = (a_value: TValue, b_value: TValue) => boolean; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_fields_diff.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_fields_diff.ts new file mode 100644 index 0000000000000..fd3be0d262e2c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_fields_diff.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/** + * The entire two-way fields diff return object + * + * Contains every field compared by the two-way diff algorithms and their values. + */ +export type TwoWayFieldsDiff = Required<{ + [Field in keyof TObject]: TwoWayDiff; +}>; + +/** + * The result of a two-way field diff comparison + * + * We use this to determine whether the two fields are equal to one another + * and easily filter out fields that are different. It also contains the field + * values compared for reference. + */ +export interface TwoWayDiff { + is_equal: boolean; + value_a: unknown; + value_b: unknown; +} diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_rule_diff.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_rule_diff.ts new file mode 100644 index 0000000000000..3024b50470d9f --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_rule_diff.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + BaseResponseProps, + EqlRuleResponseFields, + EsqlRuleResponseFields, + MachineLearningRuleResponseFields, + NewTermsRuleResponseFields, + QueryRuleResponseFields, + SavedQueryRuleResponseFields, + ThreatMatchRuleResponseFields, + ThresholdRuleResponseFields, +} from '../../../../model/rule_schema'; +import type { TwoWayFieldsDiff } from './two_way_fields_diff'; + +// Omit non-diffable fields from BaseResponseProps +export type TwoWayRuleDiffCommonFields = Omit< + BaseResponseProps, + | 'actions' + | 'exceptions_list' + | 'enabled' + | 'author' + | 'license' + | 'response_actions' + | 'throttle' + | 'outcome' + | 'alias_purpose' + | 'alias_target_id' + | 'output_index' + | 'meta' + | 'namespace' +>; + +export type TwoWayRuleDiffCustomQueryFields = QueryRuleResponseFields; +export type TwoWayRuleDiffSavedQueryFields = SavedQueryRuleResponseFields; +export type TwoWayRuleDiffEqlFields = EqlRuleResponseFields; +export type TwoWayRuleDiffEsqlFields = EsqlRuleResponseFields; +export type TwoWayRuleDiffThresholdFields = ThresholdRuleResponseFields; +export type TwoWayRuleDiffMachineLearningFields = MachineLearningRuleResponseFields; +export type TwoWayRuleDiffNewTermsFields = NewTermsRuleResponseFields; + +// Omit non-diffable fields from ThreatMatchRuleResponseFields +export type TwoWayRuleDiffThreatMatchFields = Omit< + ThreatMatchRuleResponseFields, + 'concurrent_searches' | 'items_per_search' +>; + +export type TwoWayRuleDiffFieldsByTypeUnion = + | (TwoWayRuleDiffCustomQueryFields & { type: 'query' }) + | (TwoWayRuleDiffSavedQueryFields & { type: 'saved_query' }) + | (TwoWayRuleDiffEqlFields & { type: 'eql' }) + | (TwoWayRuleDiffEsqlFields & { type: 'esql' }) + | (TwoWayRuleDiffThreatMatchFields & { type: 'threat_match' }) + | (TwoWayRuleDiffThresholdFields & { type: 'threshold' }) + | (TwoWayRuleDiffMachineLearningFields & { type: 'machine_learning' }) + | (TwoWayRuleDiffNewTermsFields & { type: 'new_terms' }); + +export type TwoWayDiffRule = TwoWayRuleDiffCommonFields & TwoWayRuleDiffFieldsByTypeUnion; + +export type TwoWayRuleFieldsDiff = TwoWayFieldsDiff; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/index.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/index.ts index 29259f7cfa5e4..69c043978953d 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/model/index.ts @@ -8,8 +8,8 @@ export * from './aggregated_prebuilt_rules_error'; export * from './diff/diffable_rule/diffable_field_types'; export * from './diff/diffable_rule/diffable_rule'; -export type * from './diff/rule_diff/fields_diff'; -export type * from './diff/rule_diff/rule_diff'; +export type * from './diff/three_way_diff/three_way_fields_diff'; +export type * from './diff/three_way_diff/three_way_rule_diff'; export * from './diff/three_way_diff/three_way_diff_outcome'; export * from './diff/three_way_diff/three_way_diff'; export * from './diff/three_way_diff/three_way_diff_conflict'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts index 40c92733851cb..7ff42893f0358 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route.ts @@ -7,7 +7,7 @@ import { z } from '@kbn/zod'; import { SortOrder, type RuleObjectId, type RuleSignatureId, type RuleTagArray } from '../../model'; -import type { PartialRuleDiff } from '../model'; +import type { PartialThreeWayRuleDiff } from '../model'; import type { RuleResponse, RuleVersion } from '../../model/rule_schema'; import { FindRulesSortField } from '../../rule_management'; import { ReviewPrebuiltRuleUpgradeFilter } from '../common/review_prebuilt_rules_upgrade_filter'; @@ -87,7 +87,7 @@ export interface RuleUpgradeInfoForReview { version: RuleVersion; current_rule: RuleResponse; target_rule: RuleResponse; - diff: PartialRuleDiff; + diff: PartialThreeWayRuleDiff; revision: number; has_base_version: boolean; } diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts index bba1908994748..baf91ce9fae4e 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable.ts @@ -6,8 +6,6 @@ */ import type { RequiredOptional } from '@kbn/zod-helpers'; -import { requiredOptional } from '@kbn/zod-helpers'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; import { assertUnreachable } from '../../../utility_types'; import type { EqlRule, @@ -40,27 +38,28 @@ import type { DiffableThreatMatchFields, DiffableThresholdFields, } from '../../../api/detection_engine/prebuilt_rules'; -import { addEcsToRequiredFields } from '../../rule_management/utils'; -import { extractBuildingBlockObject } from './extract_building_block_object'; +import { extractBuildingBlockObject } from './extractors/extract_building_block_object'; import { extractInlineKqlQuery, extractRuleEqlQuery, extractRuleEsqlQuery, extractRuleKqlQuery, -} from './extract_rule_data_query'; -import { extractRuleDataSource } from './extract_rule_data_source'; -import { extractRuleNameOverrideObject } from './extract_rule_name_override_object'; -import { extractRuleSchedule } from './extract_rule_schedule'; -import { extractTimelineTemplateReference } from './extract_timeline_template_reference'; -import { extractTimestampOverrideObject } from './extract_timestamp_override_object'; -import { extractThreatArray } from './extract_threat_array'; -import { normalizeRuleThreshold } from './normalize_rule_threshold'; +} from './extractors/extract_rule_data_query'; +import { extractRuleDataSource } from './extractors/extract_rule_data_source'; +import { extractRuleNameOverrideObject } from './extractors/extract_rule_name_override_object'; +import { extractRuleSchedule } from './extractors/extract_rule_schedule'; +import { extractTimelineTemplateReference } from './extractors/extract_timeline_template_reference'; +import { extractTimestampOverrideObject } from './extractors/extract_timestamp_override_object'; +import { normalizeRuleThreshold } from './normalizers/normalize_rule_threshold'; +import { normalizeRuleResponse } from './normalize_rule_response'; /** - * Normalizes a given rule to the form which is suitable for passing to the diff algorithm. + * Structures and groups together similar fields for diffing purposes. * Read more in the JSDoc description of DiffableRule. */ -export const convertRuleToDiffable = (rule: RuleResponse): DiffableRule => { +export const convertRuleToDiffable = (ruleResponse: RuleResponse): DiffableRule => { + const rule = normalizeRuleResponse(ruleResponse); + const commonFields = extractDiffableCommonFields(rule); switch (rule.type) { @@ -119,26 +118,26 @@ const extractDiffableCommonFields = ( version: rule.version, // Main domain fields - name: rule.name?.trim(), - tags: rule.tags ?? [], + name: rule.name, + tags: rule.tags, description: rule.description, severity: rule.severity, - severity_mapping: rule.severity_mapping ?? [], + severity_mapping: rule.severity_mapping, risk_score: rule.risk_score, - risk_score_mapping: rule.risk_score_mapping?.map((mapping) => requiredOptional(mapping)) ?? [], + risk_score_mapping: rule.risk_score_mapping, // About -> Advanced settings - references: rule.references ?? [], - false_positives: rule.false_positives ?? [], - threat: extractThreatArray(rule), + references: rule.references, + false_positives: rule.false_positives, + threat: rule.threat, note: rule.note ?? '', setup: rule.setup ?? '', - related_integrations: rule.related_integrations ?? [], - required_fields: addEcsToRequiredFields(rule.required_fields), + related_integrations: rule.related_integrations, + required_fields: rule.required_fields, // Other domain fields rule_schedule: extractRuleSchedule(rule), - max_signals: rule.max_signals ?? DEFAULT_MAX_SIGNALS, + max_signals: rule.max_signals, // --------------------- OPTIONAL FIELDS investigation_fields: rule.investigation_fields, diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.test.ts deleted file mode 100644 index 637caba9cc278..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.test.ts +++ /dev/null @@ -1,187 +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 { FilterStateStore, type Filter } from '@kbn/es-query'; -import { KqlQueryType } from '../../../api/detection_engine'; -import { - extractRuleEqlQuery, - extractRuleEsqlQuery, - extractRuleKqlQuery, -} from './extract_rule_data_query'; - -const mockFilter: Filter = { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'test', - params: { - query: 'value', - }, - }, - query: { - term: { - field: 'value', - }, - }, - $state: { - store: FilterStateStore.APP_STATE, - }, -}; - -describe('extract rule data queries', () => { - describe('extractRuleKqlQuery', () => { - it('extracts a trimmed version of the query field for inline query types', () => { - const extractedKqlQuery = extractRuleKqlQuery('\nevent.kind:alert\n', 'kuery', [], undefined); - - expect(extractedKqlQuery).toEqual({ - type: KqlQueryType.inline_query, - query: 'event.kind:alert', - language: 'kuery', - filters: [], - }); - }); - - describe('filters normalization', () => { - it('normalizes filters[].query when all fields present', () => { - const extractedKqlQuery = extractRuleKqlQuery( - 'some:query', - 'kuery', - [mockFilter], - undefined - ); - - expect(extractedKqlQuery).toMatchObject({ - filters: [ - { - query: { - term: { - field: 'value', - }, - }, - }, - ], - }); - }); - - it('normalizes filters[].query when query object is missing', () => { - const extractedKqlQuery = extractRuleKqlQuery( - 'some:query', - 'kuery', - [{ ...mockFilter, query: undefined }], - undefined - ); - - expect(extractedKqlQuery).not.toMatchObject({ - filters: [ - { - query: expect.anything(), - }, - ], - }); - }); - - it.each([ - { - caseName: 'when all fields present', - filter: mockFilter, - expectedFilterMeta: { - negate: false, - disabled: false, - }, - }, - { - caseName: 'when disabled field is missing', - filter: { ...mockFilter, meta: { ...mockFilter.meta, disabled: undefined } }, - expectedFilterMeta: { - negate: false, - disabled: false, - }, - }, - { - caseName: 'when negate field is missing', - filter: { ...mockFilter, meta: { ...mockFilter.meta, negate: undefined } }, - expectedFilterMeta: { - disabled: false, - }, - }, - { - caseName: 'when query object is missing', - filter: { ...mockFilter, query: undefined }, - expectedFilterMeta: { - negate: false, - disabled: false, - }, - }, - ])('normalizes filters[].meta $caseName', ({ filter, expectedFilterMeta }) => { - const extractedKqlQuery = extractRuleKqlQuery('some:query', 'kuery', [filter], undefined); - - expect(extractedKqlQuery).toMatchObject({ - filters: [ - { - meta: expectedFilterMeta, - }, - ], - }); - }); - - it('normalizes filters[].meta when query object is missing', () => { - const extractedKqlQuery = extractRuleKqlQuery( - 'some:query', - 'kuery', - [{ ...mockFilter, query: undefined }], - undefined - ); - - expect(extractedKqlQuery).toMatchObject({ - filters: [ - { - meta: { - negate: false, - disabled: false, - }, - }, - ], - }); - }); - }); - }); - - describe('extractRuleEqlQuery', () => { - it('extracts a trimmed version of the query field', () => { - const extractedEqlQuery = extractRuleEqlQuery({ - query: '\n\nquery where true\n\n', - language: 'eql', - filters: [], - eventCategoryOverride: undefined, - timestampField: undefined, - tiebreakerField: undefined, - }); - - expect(extractedEqlQuery).toEqual({ - query: 'query where true', - language: 'eql', - filters: [], - event_category_override: undefined, - timestamp_field: undefined, - tiebreaker_field: undefined, - }); - }); - }); - - describe('extractRuleEsqlQuery', () => { - it('extracts a trimmed version of the query field', () => { - const extractedEsqlQuery = extractRuleEsqlQuery('\nFROM * where true\t\n', 'esql'); - - expect(extractedEsqlQuery).toEqual({ - query: 'FROM * where true', - language: 'esql', - }); - }); - }); -}); diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.ts deleted file mode 100644 index 25a4fe81c3e3d..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { TimeDuration } from '@kbn/securitysolution-utils/time_duration'; -import { normalizeDateMath } from '@kbn/securitysolution-utils/date_math'; -import type { RuleSchedule } from '../../../api/detection_engine/model/rule_schema/rule_schedule'; -import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema'; - -export const extractRuleSchedule = (rule: RuleResponse): RuleSchedule => { - const interval = TimeDuration.parse(rule.interval) ?? new TimeDuration(5, 'm'); - const from = rule.from ?? 'now-6m'; - const to = rule.to ?? 'now'; - - return { - interval: interval.toString(), - from: normalizeDateMath(from), - to: normalizeDateMath(to), - }; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_building_block_object.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_building_block_object.ts similarity index 72% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_building_block_object.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_building_block_object.ts index 18d6ebfb54ce4..fc3e5caf266b4 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_building_block_object.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_building_block_object.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema'; -import type { BuildingBlockObject } from '../../../api/detection_engine/prebuilt_rules'; +import type { RuleResponse } from '../../../../api/detection_engine/model/rule_schema'; +import type { BuildingBlockObject } from '../../../../api/detection_engine/prebuilt_rules'; export const extractBuildingBlockObject = (rule: RuleResponse): BuildingBlockObject | undefined => { if (rule.building_block_type == null) { diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.test.ts new file mode 100644 index 0000000000000..9c87c062a9256 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { KqlQueryType } from '../../../../api/detection_engine'; +import { + extractRuleEqlQuery, + extractRuleEsqlQuery, + extractRuleKqlQuery, +} from './extract_rule_data_query'; + +describe('extract rule data queries', () => { + describe('extractRuleKqlQuery', () => { + it('extracts the query field for inline query types', () => { + const extractedKqlQuery = extractRuleKqlQuery('event.kind:alert', 'kuery', [], undefined); + + expect(extractedKqlQuery).toEqual({ + type: KqlQueryType.inline_query, + query: 'event.kind:alert', + language: 'kuery', + filters: [], + }); + }); + }); + + describe('extractRuleEqlQuery', () => { + it('extracts the EQL query field', () => { + const extractedEqlQuery = extractRuleEqlQuery({ + query: 'query where true', + language: 'eql', + filters: [], + eventCategoryOverride: undefined, + timestampField: undefined, + tiebreakerField: undefined, + }); + + expect(extractedEqlQuery).toEqual({ + query: 'query where true', + language: 'eql', + filters: [], + event_category_override: undefined, + timestamp_field: undefined, + tiebreaker_field: undefined, + }); + }); + }); + + describe('extractRuleEsqlQuery', () => { + it('extracts the ESQL query field', () => { + const extractedEsqlQuery = extractRuleEsqlQuery('FROM * where true', 'esql'); + + expect(extractedEsqlQuery).toEqual({ + query: 'FROM * where true', + language: 'esql', + }); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.ts similarity index 59% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.ts index cfdae81b9abbe..7f447e3644e4a 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_query.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Filter } from '@kbn/es-query'; import type { EqlQueryLanguage, EsqlQueryLanguage, @@ -15,14 +14,14 @@ import type { RuleQuery, TiebreakerField, TimestampField, -} from '../../../api/detection_engine/model/rule_schema'; +} from '../../../../api/detection_engine/model/rule_schema'; import type { InlineKqlQuery, RuleEqlQuery, RuleEsqlQuery, RuleKqlQuery, -} from '../../../api/detection_engine/prebuilt_rules'; -import { KqlQueryType } from '../../../api/detection_engine/prebuilt_rules'; +} from '../../../../api/detection_engine/prebuilt_rules'; +import { KqlQueryType } from '../../../../api/detection_engine/prebuilt_rules'; export const extractRuleKqlQuery = ( query: RuleQuery | undefined, @@ -47,9 +46,9 @@ export const extractInlineKqlQuery = ( ): InlineKqlQuery => { return { type: KqlQueryType.inline_query, - query: query?.trim() ?? '', + query: query ?? '', language: language ?? 'kuery', - filters: normalizeFilterArray(filters), + filters: filters ?? [], }; }; @@ -64,9 +63,9 @@ interface ExtractRuleEqlQueryParams { export const extractRuleEqlQuery = (params: ExtractRuleEqlQueryParams): RuleEqlQuery => { return { - query: params.query?.trim(), + query: params.query, language: params.language, - filters: normalizeFilterArray(params.filters), + filters: params.filters ?? [], event_category_override: params.eventCategoryOverride, timestamp_field: params.timestampField, tiebreaker_field: params.tiebreakerField, @@ -78,33 +77,7 @@ export const extractRuleEsqlQuery = ( language: EsqlQueryLanguage ): RuleEsqlQuery => { return { - query: query?.trim(), + query, language, }; }; - -/** - * Normalizes filter properties to only include ones relevant to the query itself - * Relevant issues: - * - https://github.com/elastic/kibana/issues/202966 - * - https://github.com/elastic/kibana/issues/206527 - */ -const normalizeFilterArray = (filters: RuleFilterArray | undefined): RuleFilterArray => { - if (!filters?.length) { - return []; - } - return (filters as Filter[]).map((filter) => ({ - query: filter.query, - meta: filter.meta - ? { - negate: filter.meta.negate, - disabled: filter.meta.disabled !== undefined ? filter.meta.disabled : false, - params: filter.meta.params, - relation: 'relation' in filter.meta ? filter.meta?.relation : undefined, - type: filter.meta.type ?? 'custom', - alias: filter.meta.alias ?? undefined, - key: filter.meta.key ?? undefined, - } - : undefined, - })); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_source.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_source.ts similarity index 76% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_source.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_source.ts index 08407770339e3..e634246d25eed 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_data_source.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_data_source.ts @@ -8,9 +8,9 @@ import type { DataViewId, IndexPatternArray, -} from '../../../api/detection_engine/model/rule_schema'; -import type { RuleDataSource } from '../../../api/detection_engine/prebuilt_rules'; -import { DataSourceType } from '../../../api/detection_engine/prebuilt_rules'; +} from '../../../../api/detection_engine/model/rule_schema'; +import type { RuleDataSource } from '../../../../api/detection_engine/prebuilt_rules'; +import { DataSourceType } from '../../../../api/detection_engine/prebuilt_rules'; export const extractRuleDataSource = ( indexPatterns: IndexPatternArray | undefined, diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_name_override_object.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_name_override_object.ts similarity index 72% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_name_override_object.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_name_override_object.ts index 55119608264f9..68d9db8fc4af2 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_name_override_object.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_name_override_object.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema'; -import type { RuleNameOverrideObject } from '../../../api/detection_engine/prebuilt_rules'; +import type { RuleResponse } from '../../../../api/detection_engine/model/rule_schema'; +import type { RuleNameOverrideObject } from '../../../../api/detection_engine/prebuilt_rules'; export const extractRuleNameOverrideObject = ( rule: RuleResponse diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.test.ts similarity index 70% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.test.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.test.ts index 7781041efedc7..f3c55c5a81691 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_rule_schedule.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RuleResponse } from '../../../api/detection_engine'; +import type { RuleResponse } from '../../../../api/detection_engine'; import { extractRuleSchedule } from './extract_rule_schedule'; describe('extractRuleSchedule', () => { @@ -18,10 +18,4 @@ describe('extractRuleSchedule', () => { expect(ruleSchedule).toEqual({ interval: '5m', from: 'now-6m', to: 'now' }); }); - - it('returns default values', () => { - const ruleSchedule = extractRuleSchedule({} as RuleResponse); - - expect(ruleSchedule).toEqual({ interval: '5m', from: 'now-6m', to: 'now' }); - }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.ts new file mode 100644 index 0000000000000..0c0de95b5a952 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_rule_schedule.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 type { RuleSchedule } from '../../../../api/detection_engine/model/rule_schema/rule_schedule'; +import type { RuleResponse } from '../../../../api/detection_engine/model/rule_schema'; + +export const extractRuleSchedule = (rule: RuleResponse): RuleSchedule => { + return { + interval: rule.interval, + from: rule.from, + to: rule.to, + }; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timeline_template_reference.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timeline_template_reference.ts similarity index 74% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timeline_template_reference.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timeline_template_reference.ts index 7dcce30061659..1592a9bd8ab4b 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timeline_template_reference.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timeline_template_reference.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema'; -import type { TimelineTemplateReference } from '../../../api/detection_engine/prebuilt_rules'; +import type { RuleResponse } from '../../../../api/detection_engine/model/rule_schema'; +import type { TimelineTemplateReference } from '../../../../api/detection_engine/prebuilt_rules'; export const extractTimelineTemplateReference = ( rule: RuleResponse diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timestamp_override_object.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timestamp_override_object.ts similarity index 75% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timestamp_override_object.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timestamp_override_object.ts index 40f218fe430b7..06c4a61ac61b9 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_timestamp_override_object.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extractors/extract_timestamp_override_object.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { RuleResponse } from '../../../api/detection_engine/model/rule_schema'; -import type { TimestampOverrideObject } from '../../../api/detection_engine/prebuilt_rules'; +import type { RuleResponse } from '../../../../api/detection_engine/model/rule_schema'; +import type { TimestampOverrideObject } from '../../../../api/detection_engine/prebuilt_rules'; export const extractTimestampOverrideObject = ( rule: RuleResponse diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_response.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_response.ts new file mode 100644 index 0000000000000..9a05b1b70e6c3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_response.ts @@ -0,0 +1,184 @@ +/* + * 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 { requiredOptional } from '@kbn/zod-helpers'; +import { TimeDuration } from '@kbn/securitysolution-utils/time_duration'; +import { normalizeDateMath } from '@kbn/securitysolution-utils/date_math'; +import { DEFAULT_MAX_SIGNALS } from '../../../constants'; +import { assertUnreachable } from '../../../utility_types'; +import type { + RuleResponse, + SharedResponseProps, + TypeSpecificResponse, +} from '../../../api/detection_engine/model/rule_schema'; +import { addEcsToRequiredFields } from '../../rule_management/utils'; +import { + normalizeThreatArray, + normalizeRuleThreshold, + normalizeFilterArray, + normalizeQueryField, +} from './normalizers'; + +export const normalizeRuleResponse = (rule: RuleResponse): RuleResponse => { + const typeSpecificFields = normalizeTypeSpecificFields(rule); + const commonFields = normalizeCommonResponseFields(rule); + + // Needed to correctly narrow typescript types, we add in the correctly typed alert suppression field in the type-specific normalization + const { alert_suppression: unUsedField, ...existingRule } = rule; + + return { + ...existingRule, + ...commonFields, + ...typeSpecificFields, + }; +}; + +const normalizeCommonResponseFields = (rule: RuleResponse): SharedResponseProps => { + return { + name: rule.name?.trim(), + description: rule.description, + risk_score: rule.risk_score, + severity: rule.severity, + version: rule.version, + enabled: rule.enabled, + exceptions_list: rule.exceptions_list, + actions: rule.actions, + author: rule.author, + tags: rule.tags ?? [], + severity_mapping: rule.severity_mapping ?? [], + risk_score_mapping: rule.risk_score_mapping?.map((mapping) => requiredOptional(mapping)) ?? [], + references: rule.references ?? [], + false_positives: rule.false_positives ?? [], + threat: normalizeThreatArray(rule.threat), + note: rule.note ?? '', + setup: rule.setup ?? '', + related_integrations: rule.related_integrations ?? [], + required_fields: addEcsToRequiredFields(rule.required_fields), + interval: (TimeDuration.parse(rule.interval) ?? new TimeDuration(5, 'm')).toString(), + from: normalizeDateMath(rule.from ?? 'now-6m'), + to: normalizeDateMath(rule.to ?? 'now'), + max_signals: rule.max_signals ?? DEFAULT_MAX_SIGNALS, + timestamp_override_fallback_disabled: rule.timestamp_override_fallback_disabled ?? false, + id: rule.id, + rule_id: rule.rule_id, + rule_source: rule.rule_source, + immutable: rule.immutable, + created_at: rule.created_at, + created_by: rule.created_by, + updated_at: rule.updated_at, + updated_by: rule.updated_by, + revision: rule.revision, + }; +}; + +const normalizeTypeSpecificFields = (rule: RuleResponse): TypeSpecificResponse => { + switch (rule.type) { + case 'query': { + return { + type: rule.type, + language: rule.language ?? 'kuery', + index: rule.index, + data_view_id: rule.data_view_id, + query: normalizeQueryField(rule.query), + filters: normalizeFilterArray(rule.filters), + saved_id: rule.saved_id, + alert_suppression: rule.alert_suppression, + }; + } + case 'saved_query': { + return { + type: rule.type, + language: rule.language ?? 'kuery', + index: rule.index, + query: normalizeQueryField(rule.query), + filters: normalizeFilterArray(rule.filters), + saved_id: rule.saved_id, + data_view_id: rule.data_view_id, + alert_suppression: rule.alert_suppression, + }; + } + case 'eql': { + return { + type: rule.type, + language: rule.language, + index: rule.index, + data_view_id: rule.data_view_id, + query: normalizeQueryField(rule.query), + filters: normalizeFilterArray(rule.filters), + timestamp_field: rule.timestamp_field, + event_category_override: rule.event_category_override, + tiebreaker_field: rule.tiebreaker_field, + alert_suppression: rule.alert_suppression, + }; + } + case 'esql': { + return { + type: rule.type, + language: rule.language, + query: normalizeQueryField(rule.query), + alert_suppression: rule.alert_suppression, + }; + } + case 'threat_match': { + return { + type: rule.type, + language: rule.language ?? 'kuery', + index: rule.index, + data_view_id: rule.data_view_id, + query: normalizeQueryField(rule.query), + filters: normalizeFilterArray(rule.filters), + saved_id: rule.saved_id, + threat_filters: rule.threat_filters, + threat_query: normalizeQueryField(rule.threat_query), + threat_mapping: rule.threat_mapping, + threat_language: rule.threat_language, + threat_index: rule.threat_index, + threat_indicator_path: rule.threat_indicator_path, + concurrent_searches: rule.concurrent_searches, + items_per_search: rule.items_per_search, + alert_suppression: rule.alert_suppression, + }; + } + case 'threshold': { + return { + type: rule.type, + language: rule.language ?? 'kuery', + index: rule.index, + data_view_id: rule.data_view_id, + query: normalizeQueryField(rule.query), + filters: normalizeFilterArray(rule.filters), + saved_id: rule.saved_id, + threshold: normalizeRuleThreshold(rule.threshold), + alert_suppression: rule.alert_suppression, + }; + } + case 'machine_learning': { + return { + type: rule.type, + anomaly_threshold: rule.anomaly_threshold, + machine_learning_job_id: rule.machine_learning_job_id, + alert_suppression: rule.alert_suppression, + }; + } + case 'new_terms': { + return { + type: rule.type, + query: normalizeQueryField(rule.query), + new_terms_fields: rule.new_terms_fields, + history_window_start: rule.history_window_start, + index: rule.index, + filters: normalizeFilterArray(rule.filters), + language: rule.language ?? 'kuery', + data_view_id: rule.data_view_id, + alert_suppression: rule.alert_suppression, + }; + } + default: { + return assertUnreachable(rule); + } + } +}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/index.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/index.ts new file mode 100644 index 0000000000000..78147a2c0f490 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './normalize_filter_array'; +export * from './normalize_query_field'; +export * from './normalize_rule_threshold'; +export * from './normalize_threat_array'; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.test.ts new file mode 100644 index 0000000000000..97c7248daaa07 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.test.ts @@ -0,0 +1,142 @@ +/* + * 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 { FilterStateStore, type Filter } from '@kbn/es-query'; +import { normalizeFilterArray } from './normalize_filter_array'; + +const mockFilter: Filter = { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + $state: { + store: FilterStateStore.APP_STATE, + }, +}; + +describe('normalizeFilterArray', () => { + it('returns an empty array when filters field is undefined', () => { + const normalizedFilter = normalizeFilterArray(undefined); + + expect(normalizedFilter).toEqual([]); + }); + + it('returns an empty array when filters field is empty array', () => { + const normalizedFilter = normalizeFilterArray([]); + + expect(normalizedFilter).toEqual([]); + }); + + it('omits the meta field when not present in the filter object', () => { + const normalizedFilter = normalizeFilterArray([{ ...mockFilter, meta: undefined }]); + + expect(normalizedFilter).toEqual([ + { + query: { + term: { + field: 'value', + }, + }, + }, + ]); + }); + + it('normalizes filters[].query when all fields present', () => { + const normalizedFilter = normalizeFilterArray([mockFilter]); + + expect(normalizedFilter).toMatchObject([ + { + query: { + term: { + field: 'value', + }, + }, + }, + ]); + }); + + it('normalizes filters[].query when query object is missing', () => { + const normalizedFilter = normalizeFilterArray([ + { ...mockFilter, query: undefined }, + ]) as Filter[]; + + expect(normalizedFilter[0].query).toBeUndefined(); + }); + + it.each([ + { + caseName: 'when all fields present', + filter: mockFilter, + expectedFilterMeta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + }, + { + caseName: 'when disabled field is missing', + filter: { ...mockFilter, meta: { ...mockFilter.meta, disabled: undefined } }, + expectedFilterMeta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + }, + { + caseName: 'when negate field is missing', + filter: { ...mockFilter, meta: { ...mockFilter.meta, negate: undefined } }, + expectedFilterMeta: { + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + }, + { + caseName: 'when query object is missing', + filter: { ...mockFilter, query: undefined }, + expectedFilterMeta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + }, + ])('normalizes filters[].meta $caseName', ({ filter, expectedFilterMeta }) => { + const normalizedFilter = normalizeFilterArray([filter]); + + expect(normalizedFilter).toEqual([ + expect.objectContaining({ + meta: expectedFilterMeta, + }), + ]); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.ts new file mode 100644 index 0000000000000..1edc4cce1ce53 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_filter_array.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Filter } from '@kbn/es-query'; +import type { RuleFilterArray } from '../../../../api/detection_engine/model/rule_schema'; + +export const normalizeFilterArray = (filters: RuleFilterArray | undefined): RuleFilterArray => { + if (!filters?.length) { + return []; + } + return (filters as Filter[]).map((filter) => ({ + query: filter.query, + meta: filter.meta + ? { + negate: filter.meta.negate, + disabled: filter.meta.disabled ?? false, + params: filter.meta.params, + relation: 'relation' in filter.meta ? filter.meta?.relation : undefined, + type: filter.meta.type ?? 'custom', + alias: filter.meta.alias ?? undefined, + key: filter.meta.key ?? undefined, + } + : undefined, + })); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.test.ts new file mode 100644 index 0000000000000..038e1900220d3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { normalizeQueryField } from './normalize_query_field'; + +describe('normalizeQueryField', () => { + it('trims new lines from the query field', () => { + const normalizedQueryField = normalizeQueryField('\nquery where true\n\n'); + + expect(normalizedQueryField).toEqual('query where true'); + }); + + it('trims tabs from the query field', () => { + const normalizedQueryField = normalizeQueryField('\tquery where true\t'); + + expect(normalizedQueryField).toEqual('query where true'); + }); + + it('trims whitespace from the query field', () => { + const normalizedQueryField = normalizeQueryField(' query where true '); + + expect(normalizedQueryField).toEqual('query where true'); + }); + + it('defaults to empty string when query is undefined', () => { + const normalizedQueryField = normalizeQueryField(undefined); + + expect(normalizedQueryField).toEqual(''); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.ts new file mode 100644 index 0000000000000..c0c18c3abb4f8 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_query_field.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleQuery } from '../../../../api/detection_engine/model/rule_schema'; + +export const normalizeQueryField = (query: RuleQuery | undefined): RuleQuery => { + return query?.trim() ?? ''; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_threshold.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_rule_threshold.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_threshold.test.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_rule_threshold.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_threshold.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_rule_threshold.ts similarity index 86% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_threshold.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_rule_threshold.ts index 9592ea141f46c..9eeecaf91e00f 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalize_rule_threshold.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_rule_threshold.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Threshold } from '../../../api/detection_engine/model/rule_schema'; +import type { Threshold } from '../../../../api/detection_engine/model/rule_schema'; export const normalizeRuleThreshold = (threshold: Threshold): Threshold => { const cardinality = diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.test.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.test.ts similarity index 56% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.test.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.test.ts index 8e226b8492729..a9e460386a6b3 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.test.ts @@ -5,39 +5,42 @@ * 2.0. */ -import { getRulesSchemaMock } from '../../../api/detection_engine/model/rule_schema/mocks'; -import { getThreatMock } from '../../schemas/types/threat.mock'; -import { extractThreatArray } from './extract_threat_array'; +import { getThreatMock } from '../../../schemas/types/threat.mock'; +import { normalizeThreatArray } from './normalize_threat_array'; const mockThreat = getThreatMock()[0]; -describe('extractThreatArray', () => { +describe('normalizeThreatArray', () => { + it('returns an empty array if threat field is undefined', () => { + const normalizedThreatArray = normalizeThreatArray(undefined); + + expect(normalizedThreatArray).toEqual([]); + }); + it('normalizes url ending backslashes', () => { - const mockRule = { - ...getRulesSchemaMock(), - threat: [ - { - ...mockThreat, - tactic: { - ...mockThreat.tactic, - reference: 'https://attack.mitre.org/tactics/TA0000', - }, - technique: [ - { - ...mockThreat.technique![0], - reference: 'https://attack.mitre.org/techniques/T0000/', - subtechnique: [ - { - ...mockThreat.technique![0].subtechnique![0], - reference: 'https://attack.mitre.org/techniques/T0000/000', - }, - ], - }, - ], + const mockThreatArray = [ + { + ...mockThreat, + tactic: { + ...mockThreat.tactic, + reference: 'https://attack.mitre.org/tactics/TA0000', }, - ], - }; - const normalizedThreatArray = extractThreatArray(mockRule); + technique: [ + { + ...mockThreat.technique![0], + reference: 'https://attack.mitre.org/techniques/T0000/', + subtechnique: [ + { + ...mockThreat.technique![0].subtechnique![0], + reference: 'https://attack.mitre.org/techniques/T0000/000', + }, + ], + }, + ], + }, + ]; + + const normalizedThreatArray = normalizeThreatArray(mockThreatArray); expect(normalizedThreatArray).toEqual([ { @@ -66,20 +69,18 @@ describe('extractThreatArray', () => { }); it('normalizes url ending backslashes with query strings', () => { - const mockRule = { - ...getRulesSchemaMock(), - threat: [ - { - ...mockThreat, - tactic: { - ...mockThreat.tactic, - reference: 'https://attack.mitre.org/tactics/TA0000?query=test', - }, - technique: [], + const mockThreatArray = [ + { + ...mockThreat, + tactic: { + ...mockThreat.tactic, + reference: 'https://attack.mitre.org/tactics/TA0000?query=test', }, - ], - }; - const normalizedThreatArray = extractThreatArray(mockRule); + technique: [], + }, + ]; + + const normalizedThreatArray = normalizeThreatArray(mockThreatArray); expect(normalizedThreatArray).toEqual([ { @@ -94,8 +95,8 @@ describe('extractThreatArray', () => { }); it('trims empty technique fields from threat object', () => { - const mockRule = { ...getRulesSchemaMock(), threat: [{ ...mockThreat, technique: [] }] }; - const normalizedThreatArray = extractThreatArray(mockRule); + const mockThreatArray = [{ ...mockThreat, technique: [] }]; + const normalizedThreatArray = normalizeThreatArray(mockThreatArray); expect(normalizedThreatArray).toEqual([ { @@ -110,11 +111,10 @@ describe('extractThreatArray', () => { }); it('trims empty subtechnique fields from threat object', () => { - const mockRule = { - ...getRulesSchemaMock(), - threat: [{ ...mockThreat, technique: [{ ...mockThreat.technique![0], subtechnique: [] }] }], - }; - const normalizedThreatArray = extractThreatArray(mockRule); + const mockThreatArray = [ + { ...mockThreat, technique: [{ ...mockThreat.technique![0], subtechnique: [] }] }, + ]; + const normalizedThreatArray = normalizeThreatArray(mockThreatArray); expect(normalizedThreatArray).toEqual([ { diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.ts similarity index 86% rename from x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.ts rename to x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.ts index 52883248d34a7..515b2c5e9c7f6 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/extract_threat_array.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/prebuilt_rules/diff/normalizers/normalize_threat_array.ts @@ -6,14 +6,13 @@ */ import type { - RuleResponse, ThreatArray, ThreatSubtechnique, ThreatTechnique, -} from '../../../api/detection_engine/model/rule_schema'; +} from '../../../../api/detection_engine/model/rule_schema'; -export const extractThreatArray = (rule: RuleResponse): ThreatArray => - rule.threat?.map((threat) => { +export const normalizeThreatArray = (threatArray: ThreatArray | undefined): ThreatArray => + (threatArray ?? []).map((threat) => { if (threat.technique && threat.technique.length) { return { ...threat, @@ -21,12 +20,13 @@ export const extractThreatArray = (rule: RuleResponse): ThreatArray => technique: trimTechniqueArray(threat.technique), }; } + // If `technique` is an empty array, remove the field from the `threat` object return { ...threat, tactic: { ...threat.tactic, reference: normalizeThreatReference(threat.tactic.reference) }, technique: undefined, - }; // If `technique` is an empty array, remove the field from the `threat` object - }) ?? []; + }; + }); const trimTechniqueArray = (techniqueArray: ThreatTechnique[]): ThreatTechnique[] => { return techniqueArray.map((technique) => ({ diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts index d96169a7315a7..d5cc7b4f10c94 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts @@ -9,7 +9,7 @@ import { camelCase, isPlainObject, startCase } from 'lodash'; import type { Filter } from '@kbn/es-query'; import type { DiffableAllFields, - RuleFieldsDiff, + ThreeWayRuleFieldsDiff, ThreeWayDiff, } from '../../../../../common/api/detection_engine'; import { DataSourceType, ThreeWayDiffOutcome } from '../../../../../common/api/detection_engine'; @@ -54,8 +54,8 @@ export const getSectionedFieldDiffs = (fields: FieldsGroupDiff[]) => { * current per-field rule diff flyout */ export const filterUnsupportedDiffOutcomes = ( - fields: Partial -): Partial => + fields: Partial +): Partial => Object.fromEntries( Object.entries(fields).filter(([key, value]) => { const diff = value as ThreeWayDiff; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_field_diffs_for_grouped_fields.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_field_diffs_for_grouped_fields.ts index 7dffd3a593b15..d87143c340100 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_field_diffs_for_grouped_fields.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_field_diffs_for_grouped_fields.ts @@ -12,7 +12,7 @@ import type { } from '../../../../../../common/api/detection_engine/model/rule_schema/rule_schedule'; import { toSimpleRuleSchedule } from '../../../../../../common/api/detection_engine/model/rule_schema/to_simple_rule_schedule'; import type { - AllFieldsDiff, + AllThreeWayFieldsDiff, RuleFieldsDiffWithDataSource, RuleFieldsDiffWithEqlQuery, RuleFieldsDiffWithEsqlQuery, @@ -241,7 +241,7 @@ export const getFieldDiffsForEsqlQuery = ( }; export const getFieldDiffsForThreatQuery = ( - threatQuery: AllFieldsDiff['threat_query'] + threatQuery: AllThreeWayFieldsDiff['threat_query'] ): FieldDiff[] => { const currentQuery = sortAndStringifyJson(threatQuery.current_version?.query); const targetQuery = sortAndStringifyJson(threatQuery.target_version?.query); @@ -355,7 +355,7 @@ const getFieldDiffsForSimpleRuleSchedule = ( }; export const getFieldDiffsForRuleNameOverride = ( - ruleNameOverrideThreeWayDiff: AllFieldsDiff['rule_name_override'] + ruleNameOverrideThreeWayDiff: AllThreeWayFieldsDiff['rule_name_override'] ): FieldDiff[] => { const currentFieldName = sortAndStringifyJson( ruleNameOverrideThreeWayDiff.current_version?.field_name @@ -377,7 +377,7 @@ export const getFieldDiffsForRuleNameOverride = ( }; export const getFieldDiffsForTimestampOverride = ( - timestampOverrideThreeWayDiff: AllFieldsDiff['timestamp_override'] + timestampOverrideThreeWayDiff: AllThreeWayFieldsDiff['timestamp_override'] ): FieldDiff[] => { const currentFieldName = sortAndStringifyJson( timestampOverrideThreeWayDiff.current_version?.field_name @@ -415,7 +415,7 @@ export const getFieldDiffsForTimestampOverride = ( }; export const getFieldDiffsForTimelineTemplate = ( - timelineTemplateThreeWayDiff: AllFieldsDiff['timeline_template'] + timelineTemplateThreeWayDiff: AllThreeWayFieldsDiff['timeline_template'] ): FieldDiff[] => { const currentTimelineId = sortAndStringifyJson( timelineTemplateThreeWayDiff.current_version?.timeline_id @@ -453,7 +453,7 @@ export const getFieldDiffsForTimelineTemplate = ( }; export const getFieldDiffsForBuildingBlock = ( - buildingBlockThreeWayDiff: AllFieldsDiff['building_block'] + buildingBlockThreeWayDiff: AllThreeWayFieldsDiff['building_block'] ): FieldDiff[] => { const currentType = sortAndStringifyJson(buildingBlockThreeWayDiff.current_version?.type); const targetType = sortAndStringifyJson(buildingBlockThreeWayDiff.target_version?.type); @@ -471,7 +471,7 @@ export const getFieldDiffsForBuildingBlock = ( }; export const getFieldDiffsForThreshold = ( - thresholdThreeWayDiff: AllFieldsDiff['threshold'] + thresholdThreeWayDiff: AllThreeWayFieldsDiff['threshold'] ): FieldDiff[] => { const currentField = sortAndStringifyJson(thresholdThreeWayDiff.current_version?.field); const targetField = sortAndStringifyJson(thresholdThreeWayDiff.target_version?.field); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_formatted_field_diff.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_formatted_field_diff.ts index 8f150efbf6677..311c5c8c3b51b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_formatted_field_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_diff/get_formatted_field_diff.ts @@ -6,8 +6,8 @@ */ import type { - AllFieldsDiff, - RuleFieldsDiff, + AllThreeWayFieldsDiff, + ThreeWayRuleFieldsDiff, RuleFieldsDiffWithDataSource, RuleFieldsDiffWithKqlQuery, RuleFieldsDiffWithEqlQuery, @@ -32,8 +32,8 @@ import { } from './get_field_diffs_for_grouped_fields'; export const getFormattedFieldDiffGroups = ( - fieldName: keyof AllFieldsDiff, - fields: Partial + fieldName: keyof AllThreeWayFieldsDiff, + fields: Partial ): FormattedFieldDiff => { /** * Field types that contain groupings of rule fields must be formatted differently to compare and render @@ -73,13 +73,15 @@ export const getFormattedFieldDiffGroups = ( fieldDiffs: getFieldDiffsForThreatQuery(threatQueryThreeWayDiff), }; case 'rule_schedule': - const ruleScheduleThreeWayDiff = fields[fieldName] as AllFieldsDiff['rule_schedule']; + const ruleScheduleThreeWayDiff = fields[fieldName] as AllThreeWayFieldsDiff['rule_schedule']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForRuleSchedule(ruleScheduleThreeWayDiff), }; case 'rule_name_override': - const ruleNameOverrideThreeWayDiff = fields[fieldName] as AllFieldsDiff['rule_name_override']; + const ruleNameOverrideThreeWayDiff = fields[ + fieldName + ] as AllThreeWayFieldsDiff['rule_name_override']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForRuleNameOverride(ruleNameOverrideThreeWayDiff), @@ -87,19 +89,23 @@ export const getFormattedFieldDiffGroups = ( case 'timestamp_override': const timestampOverrideThreeWayDiff = fields[ fieldName - ] as AllFieldsDiff['timestamp_override']; + ] as AllThreeWayFieldsDiff['timestamp_override']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForTimestampOverride(timestampOverrideThreeWayDiff), }; case 'timeline_template': - const timelineTemplateThreeWayDiff = fields[fieldName] as AllFieldsDiff['timeline_template']; + const timelineTemplateThreeWayDiff = fields[ + fieldName + ] as AllThreeWayFieldsDiff['timeline_template']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForTimelineTemplate(timelineTemplateThreeWayDiff), }; case 'building_block': - const buildingBlockThreeWayDiff = fields[fieldName] as AllFieldsDiff['building_block']; + const buildingBlockThreeWayDiff = fields[ + fieldName + ] as AllThreeWayFieldsDiff['building_block']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForBuildingBlock(buildingBlockThreeWayDiff), @@ -107,13 +113,13 @@ export const getFormattedFieldDiffGroups = ( case 'threshold': const thresholdThreeWayDiff = (fields as RuleFieldsDiffWithThreshold)[ fieldName - ] as AllFieldsDiff['threshold']; + ] as AllThreeWayFieldsDiff['threshold']; return { shouldShowSubtitles: true, fieldDiffs: getFieldDiffsForThreshold(thresholdThreeWayDiff), }; default: - const fieldThreeWayDiff = (fields as AllFieldsDiff)[fieldName]; + const fieldThreeWayDiff = (fields as AllThreeWayFieldsDiff)[fieldName]; const currentVersionField = sortAndStringifyJson(fieldThreeWayDiff.current_version); const targetVersionField = sortAndStringifyJson(fieldThreeWayDiff.target_version); return { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx index 34139f4af1de3..1b404d4c00d7b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.test.tsx @@ -11,7 +11,7 @@ import { ThreeWayDiffOutcome, ThreeWayMergeOutcome, } from '../../../../../common/api/detection_engine'; -import type { PartialRuleDiff } from '../../../../../common/api/detection_engine'; +import type { PartialThreeWayRuleDiff } from '../../../../../common/api/detection_engine'; import { TestProviders } from '../../../../common/mock'; import { render } from '@testing-library/react'; import React from 'react'; @@ -25,7 +25,7 @@ const ruleFieldsDiffBaseFieldsMock = { merge_outcome: ThreeWayMergeOutcome.Target, }; -const ruleFieldsDiffMock: PartialRuleDiff = { +const ruleFieldsDiffMock: PartialThreeWayRuleDiff = { fields: { version: { ...ruleFieldsDiffBaseFieldsMock, @@ -40,7 +40,7 @@ const ruleFieldsDiffMock: PartialRuleDiff = { num_fields_with_non_solvable_conflicts: 0, }; -const renderPerFieldRuleDiffTab = (ruleDiff: PartialRuleDiff) => { +const renderPerFieldRuleDiffTab = (ruleDiff: PartialThreeWayRuleDiff) => { return render( { describe('PerFieldRuleDiffTab', () => { test('Field groupings should be rendered together in the same accordion panel', () => { - const mockData: PartialRuleDiff = { + const mockData: PartialThreeWayRuleDiff = { ...ruleFieldsDiffMock, fields: { kql_query: { @@ -99,7 +99,7 @@ describe('PerFieldRuleDiffTab', () => { describe('Undefined values are displayed with empty diffs', () => { test('Displays only an updated field value when changed from an empty value', () => { - const mockData: PartialRuleDiff = { + const mockData: PartialThreeWayRuleDiff = { ...ruleFieldsDiffMock, fields: { name: { @@ -119,7 +119,7 @@ describe('PerFieldRuleDiffTab', () => { }); test('Displays only an outdated field value when incoming update is an empty value', () => { - const mockData: PartialRuleDiff = { + const mockData: PartialThreeWayRuleDiff = { ...ruleFieldsDiffMock, fields: { name: { @@ -140,7 +140,7 @@ describe('PerFieldRuleDiffTab', () => { }); test('Field diff components have the same grouping and order as in rule details overview', () => { - const mockData: PartialRuleDiff = { + const mockData: PartialThreeWayRuleDiff = { ...ruleFieldsDiffMock, fields: { setup: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx index 487bf0c53ccd2..d509254b10539 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx @@ -6,7 +6,10 @@ */ import React, { useMemo } from 'react'; -import type { PartialRuleDiff, RuleFieldsDiff } from '../../../../../common/api/detection_engine'; +import type { + PartialThreeWayRuleDiff, + ThreeWayRuleFieldsDiff, +} from '../../../../../common/api/detection_engine'; import { getFormattedFieldDiffGroups } from './per_field_diff/get_formatted_field_diff'; import { UPGRADE_FIELD_ORDER } from './constants'; import type { RuleDiffHeaderBarProps } from './diff_components'; @@ -16,7 +19,7 @@ import type { FieldsGroupDiff, DiffLayout } from '../../model/rule_details/rule_ import * as i18n from './translations'; interface PerFieldRuleDiffTabProps extends RuleDiffHeaderBarProps { - ruleDiff: PartialRuleDiff; + ruleDiff: PartialThreeWayRuleDiff; header?: React.ReactNode; diffLayout?: DiffLayout; } @@ -35,7 +38,7 @@ export const PerFieldRuleDiffTab = ({ // Filter out diff outcomes that we don't support displaying in the per-field diff flyout const filteredFieldDiffs = filterUnsupportedDiffOutcomes(ruleDiff.fields); for (const field of Object.keys(filteredFieldDiffs)) { - const typedField = field as keyof RuleFieldsDiff; + const typedField = field as keyof ThreeWayRuleFieldsDiff; const formattedDiffs = getFormattedFieldDiffGroups(typedField, filteredFieldDiffs); fields.push({ formattedDiffs, fieldsGroupName: typedField }); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout.tsx index cc66755a96089..0a51927beea51 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout.tsx @@ -8,7 +8,10 @@ import React, { memo, useCallback, useMemo, useRef, useEffect, useState } from 'react'; import { EuiButton, EuiCallOut, EuiSpacer, EuiToolTip } from '@elastic/eui'; import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; -import type { PartialRuleDiff, RuleResponse } from '../../../../../../common/api/detection_engine'; +import type { + PartialThreeWayRuleDiff, + RuleResponse, +} from '../../../../../../common/api/detection_engine'; import { PerFieldRuleDiffTab } from '../per_field_rule_diff_tab'; import { RuleDetailsFlyout, TabContentPadding } from '../rule_details_flyout'; import * as ruleDetailsI18n from '../translations'; @@ -30,7 +33,7 @@ interface PrebuiltRuleConcurrencyControl { interface RuleCustomizationsFlyoutProps { currentRule: RuleResponse; baseRule: RuleResponse; - diff: PartialRuleDiff; + diff: PartialThreeWayRuleDiff; closeFlyout: () => void; isReverting: boolean; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout_subheader.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout_subheader.tsx index d4afc0b863152..afcac7e780aa7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout_subheader.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_customizations_diff/rule_customizations_flyout_subheader.tsx @@ -8,13 +8,16 @@ import React from 'react'; import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedDate } from '../../../../../common/components/formatted_date'; -import type { PartialRuleDiff, RuleResponse } from '../../../../../../common/api/detection_engine'; +import type { + PartialThreeWayRuleDiff, + RuleResponse, +} from '../../../../../../common/api/detection_engine'; import * as i18n from './translations'; import { convertFieldToDisplayName } from '../helpers'; interface RuleCustomizationsFlyoutSubheaderProps { currentRule: RuleResponse; - diff: PartialRuleDiff; + diff: PartialThreeWayRuleDiff; isOutdated: boolean; } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade/field_upgrade_context.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade/field_upgrade_context.tsx index 43b61315dc972..8c47246441058 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade/field_upgrade_context.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/rule_upgrade/field_upgrade_context.tsx @@ -13,7 +13,7 @@ import { assertUnreachable } from '../../../../../../../common/utility_types'; import { ThreeWayDiffOutcome, type DiffableRule, - type FieldsDiff, + type ThreeWayFieldsDiff, type ThreeWayDiff, } from '../../../../../../../common/api/detection_engine'; import { invariant } from '../../../../../../../common/utils/invariant'; @@ -208,7 +208,7 @@ function calcFinalDiffableRule(ruleUpgradeState: RuleUpgradeState): DiffableRule * Assembles a `DiffableRule` from rule fields diff `merged_version`s. */ function convertRuleFieldsDiffToDiffable( - ruleFieldsDiff: FieldsDiff> + ruleFieldsDiff: ThreeWayFieldsDiff> ): Partial { const mergeVersionRule: Record = {}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/prebuilt_rule_upgrade/fields.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/prebuilt_rule_upgrade/fields.ts index f3b98ba3d0dd9..dab7fb9eb531e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/prebuilt_rule_upgrade/fields.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/prebuilt_rule_upgrade/fields.ts @@ -15,12 +15,15 @@ import { type DiffableSavedQueryFields, type DiffableThreatMatchFields, type DiffableThresholdFields, - type RuleFieldsDiff, + type ThreeWayRuleFieldsDiff, } from '../../../../../common/api/detection_engine'; export type NonUpgradeableDiffableFields = (typeof NON_UPGRADEABLE_DIFFABLE_FIELDS)[number]; -export type UpgradeableDiffableFields = Exclude; +export type UpgradeableDiffableFields = Exclude< + keyof ThreeWayRuleFieldsDiff, + NonUpgradeableDiffableFields +>; export type UpgradeableCommonFields = Exclude< keyof DiffableCommonFields, diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/rule_details/rule_field_diff.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/rule_details/rule_field_diff.ts index f60125457b9ea..c64b5357ce2d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/rule_details/rule_field_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/model/rule_details/rule_field_diff.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AllFieldsDiff } from '../../../../../common/api/detection_engine'; +import type { AllThreeWayFieldsDiff } from '../../../../../common/api/detection_engine'; export interface FieldDiff { currentVersion: string; @@ -19,7 +19,7 @@ export interface FormattedFieldDiff { export interface FieldsGroupDiff { formattedDiffs: FormattedFieldDiff; - fieldsGroupName: keyof AllFieldsDiff; + fieldsGroupName: keyof AllThreeWayFieldsDiff; } export enum DiffLayout { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_flyout_subheader.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_flyout_subheader.tsx index e4868fff6913d..73c17e4140882 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_flyout_subheader.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_flyout_subheader.tsx @@ -8,7 +8,7 @@ import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { convertFieldToDisplayName } from '../../../../rule_management/components/rule_details/helpers'; -import type { FieldsDiff } from '../../../../../../common/api/detection_engine'; +import type { ThreeWayFieldsDiff } from '../../../../../../common/api/detection_engine'; import { FormattedDate } from '../../../../../common/components/formatted_date'; import { SeverityBadge } from '../../../../../common/components/severity_badge'; import { ModifiedBadge } from '../../../../rule_management/components/rule_details/three_way_diff/badges/modified_badge'; @@ -52,7 +52,7 @@ export const UpgradeFlyoutSubHeader = memo(function UpgradeFlyoutSubHeader({ ); - const fieldsDiff: FieldsDiff> = ruleUpgradeState.diff.fields; + const fieldsDiff: ThreeWayFieldsDiff> = ruleUpgradeState.diff.fields; const fieldsNamesWithUpdates = Object.keys(ruleUpgradeState.fieldsUpgradeState).filter( (fieldName) => fieldsDiff[fieldName].has_update ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts index 663a39a80de5d..6017992df21c9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_prebuilt_rules_upgrade_state.ts @@ -15,7 +15,7 @@ import type { } from '../../../../rule_management/model/prebuilt_rule_upgrade'; import { FieldUpgradeStateEnum } from '../../../../rule_management/model/prebuilt_rule_upgrade'; import { - type FieldsDiff, + type ThreeWayFieldsDiff, type DiffableAllFields, type RuleUpgradeInfoForReview, ThreeWayDiffConflict, @@ -165,7 +165,7 @@ const NON_UPGRADEABLE_DIFFABLE_FIELDS_SET: Readonly> = new Set( ); function calcFieldsState( - fieldsDiff: FieldsDiff>, + fieldsDiff: ThreeWayFieldsDiff>, ruleResolvedConflicts: RuleResolvedConflicts ): FieldsUpgradeState { const fieldsState: FieldsUpgradeState = {}; @@ -215,7 +215,7 @@ function calcFieldsState( } function getWorstConflictLevelAmongFields( - fieldsDiff: FieldsDiff> + fieldsDiff: ThreeWayFieldsDiff> ): ThreeWayDiffConflict { let mostSevereFieldConflict = ThreeWayDiffConflict.NONE; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_handler.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_handler.ts index 2e2d281b2f1d2..e8698f31898b8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_handler.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rule_base_version/get_prebuilt_rule_base_version_handler.ts @@ -13,9 +13,9 @@ import { ThreeWayDiffOutcome, type GetPrebuiltRuleBaseVersionRequest, type GetPrebuiltRuleBaseVersionResponseBody, - type PartialRuleDiff, + type PartialThreeWayRuleDiff, type ThreeWayDiff, - type FullRuleDiff, + type FullThreeWayRuleDiff, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { SecuritySolutionRequestHandlerContext } from '../../../../../types'; import { buildSiemResponse } from '../../../routes/utils'; @@ -87,10 +87,10 @@ const formatDiffResponse = ({ baseRule, currentRule, }: { - ruleDiff: FullRuleDiff; + ruleDiff: FullThreeWayRuleDiff; baseRule: PrebuiltRuleAsset; currentRule: RuleResponse; -}): { diff: PartialRuleDiff; baseVersion: RuleResponse; currentVersion: RuleResponse } => { +}): { diff: PartialThreeWayRuleDiff; baseVersion: RuleResponse; currentVersion: RuleResponse } => { const baseVersion: RuleResponse = { ...convertPrebuiltRuleAssetToRuleResponse(baseRule), id: currentRule.id, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts index f8578411449b6..74efee7ba19d8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -11,7 +11,7 @@ import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { type PerformRuleUpgradeRequestBody, type PickVersionValues, - type AllFieldsDiff, + type AllThreeWayFieldsDiff, MissingVersion, } from '../../../../../../common/api/detection_engine'; import type { UpgradeConflictResolution } from '../../../../../../common/api/detection_engine/prebuilt_rules'; @@ -20,7 +20,7 @@ import { convertRuleToDiffable } from '../../../../../../common/detection_engine import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; import { assertPickVersionIsTarget } from './assert_pick_version_is_target'; import { FIELD_NAMES_BY_RULE_TYPE_MAP } from './create_props_to_rule_type_map'; -import { calculateRuleFieldsDiff } from '../../logic/diff/calculation/calculate_rule_fields_diff'; +import { calculateThreeWayRuleFieldsDiff } from '../../logic/diff/calculation/calculate_three_way_rule_fields_diff'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; import { getValueForField } from './get_value_for_field'; @@ -67,7 +67,7 @@ export const createModifiedPrebuiltRuleAssets = ({ const isCustomized = isRuleCustomized(current); - const calculatedRuleDiff = calculateRuleFieldsDiff( + const calculatedRuleDiff = calculateThreeWayRuleFieldsDiff( { base_version: upgradeableRule.base ? convertRuleToDiffable( @@ -80,7 +80,7 @@ export const createModifiedPrebuiltRuleAssets = ({ ), }, isCustomized - ) as AllFieldsDiff; + ) as AllThreeWayFieldsDiff; if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { const fieldsWithConflicts = Object.keys( @@ -135,7 +135,7 @@ interface CreateModifiedPrebuiltRuleAssetParams { fieldNames: Array; globalPickVersion: PickVersionValues; requestBody: PerformRuleUpgradeRequestBody; - calculatedRuleDiff: AllFieldsDiff; + calculatedRuleDiff: AllThreeWayFieldsDiff; } function createModifiedPrebuiltRuleAsset({ @@ -161,7 +161,7 @@ function createModifiedPrebuiltRuleAsset({ } const getFieldsDiffConflicts = ( - ruleFieldsDiff: Partial, + ruleFieldsDiff: Partial, onConflict?: UpgradeConflictResolution ) => pickBy(ruleFieldsDiff, (diff) => diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts index 4ed4a3032faa1..a33d3a077c643 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/diffable_rule_fields_mappings.ts @@ -12,7 +12,7 @@ import type { InlineKqlQuery, ThreeWayDiff, DiffableRuleTypes, - AllFieldsDiff, + AllThreeWayFieldsDiff, } from '../../../../../../common/api/detection_engine'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; @@ -79,7 +79,7 @@ interface MapRuleFieldToDiffableRuleFieldParams { export function mapRuleFieldToDiffableRuleField({ ruleType, fieldName, -}: MapRuleFieldToDiffableRuleFieldParams): keyof AllFieldsDiff { +}: MapRuleFieldToDiffableRuleFieldParams): keyof AllThreeWayFieldsDiff { // Handle query, filters and language fields based on rule type if (fieldName === 'query' || fieldName === 'language' || fieldName === 'filters') { switch (ruleType) { @@ -95,7 +95,7 @@ export function mapRuleFieldToDiffableRuleField({ } } - const diffableRuleFieldMap: Record = { + const diffableRuleFieldMap: Record = { building_block_type: 'building_block', saved_id: 'kql_query', event_category_override: 'eql_query', diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts index ffafbf916ad24..91affe9be2b6c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_for_field.ts @@ -8,7 +8,7 @@ import type { PickVersionValues, PerformRuleUpgradeRequestBody, - AllFieldsDiff, + AllThreeWayFieldsDiff, } from '../../../../../../common/api/detection_engine'; import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_asset'; import type { RuleTriad } from '../../model/rule_groups/get_rule_groups'; @@ -22,7 +22,7 @@ interface GetValueForFieldArgs { upgradeableRule: RuleTriad; globalPickVersion: PickVersionValues; requestBody: PerformRuleUpgradeRequestBody; - ruleFieldsDiff: AllFieldsDiff; + ruleFieldsDiff: AllThreeWayFieldsDiff; } export const getValueForField = ({ diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts index 8d82d74f50f90..a96221c285046 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_value_from_rule_version.ts @@ -7,7 +7,7 @@ import { type RuleFieldsToUpgrade, - type AllFieldsDiff, + type AllThreeWayFieldsDiff, type UpgradeConflictResolution, UpgradeConflictResolutionEnum, } from '../../../../../../common/api/detection_engine'; @@ -31,7 +31,7 @@ export const getValueFromMergedVersion = ({ fieldName: keyof PrebuiltRuleAsset; upgradeableRule: RuleTriad; fieldUpgradeSpecifier: NonNullable; - ruleFieldsDiff: AllFieldsDiff; + ruleFieldsDiff: AllThreeWayFieldsDiff; onConflict?: UpgradeConflictResolution; }) => { const ruleId = upgradeableRule.target.rule_id; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts index a5fd84f4b097a..f186853c7240f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculate_rule_diff.ts @@ -8,9 +8,9 @@ import { isRuleCustomized } from '../../../../../../common/detection_engine/rule_management/utils'; import type { DiffableRule, - FullRuleDiff, + FullThreeWayRuleDiff, ThreeWayDiff, - RuleFieldsDiff, + ThreeWayRuleFieldsDiff, } from '../../../../../../common/api/detection_engine/prebuilt_rules'; import { MissingVersion, @@ -22,7 +22,7 @@ import type { PrebuiltRuleAsset } from '../../model/rule_assets/prebuilt_rule_as import { convertRuleToDiffable } from '../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; -import { calculateRuleFieldsDiff } from './calculation/calculate_rule_fields_diff'; +import { calculateThreeWayRuleFieldsDiff } from './calculation/calculate_three_way_rule_fields_diff'; export interface RuleVersions { current?: RuleResponse; @@ -31,7 +31,7 @@ export interface RuleVersions { } export interface CalculateRuleDiffResult { - ruleDiff: FullRuleDiff; + ruleDiff: FullThreeWayRuleDiff; ruleVersions: { input: RuleVersions; output: { @@ -80,7 +80,7 @@ export const calculateRuleDiff = (args: RuleVersions): CalculateRuleDiffResult = ? convertRuleToDiffable(convertPrebuiltRuleAssetToRuleResponse(base)) : undefined; - const fieldsDiff = calculateRuleFieldsDiff( + const fieldsDiff = calculateThreeWayRuleFieldsDiff( { base_version: diffableBaseVersion || MissingVersion, current_version: diffableCurrentVersion, @@ -117,7 +117,7 @@ export const calculateRuleDiff = (args: RuleVersions): CalculateRuleDiffResult = }; }; -const getNumberOfFieldsByChangeType = (fieldsDiff: RuleFieldsDiff) => +const getNumberOfFieldsByChangeType = (fieldsDiff: ThreeWayRuleFieldsDiff) => Object.values>(fieldsDiff).reduce<{ numberFieldsWithUpdates: number; numberFieldsWithConflicts: number; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_diff_synchronization.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_diff_synchronization.test.ts new file mode 100644 index 0000000000000..097f787e963d0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_diff_synchronization.test.ts @@ -0,0 +1,1865 @@ +/* + * 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 { convertPrebuiltRuleAssetToRuleResponse } from '../../../../rule_management/logic/detection_rules_client/converters/convert_prebuilt_rule_asset_to_rule_response'; +import { getPrebuiltRuleMockOfType } from '../../../mocks'; +import { calculateRuleFieldsDiff } from './calculate_rule_fields_diff'; +import { calculateThreeWayRuleFieldsDiff } from './calculate_three_way_rule_fields_diff'; +import { convertRuleToDiffable } from '../../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable'; +import type { + RuleResponse, + ThreeWayRuleFieldsDiff, +} from '../../../../../../../common/api/detection_engine'; +import { + AlertSuppressionDurationUnitEnum, + AlertSuppressionMissingFieldsStrategyEnum, + MissingVersion, + SeverityEnum, +} from '../../../../../../../common/api/detection_engine'; +import type { TwoWayDiffRule } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_rule_diff'; + +const CUSTOM_QUERY_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('query'); +const CUSTOM_QUERY_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + CUSTOM_QUERY_PREBUILT_RULE_ASSET +); + +const SAVED_QUERY_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('saved_query'); +const SAVED_QUERY_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + SAVED_QUERY_PREBUILT_RULE_ASSET +); + +const EQL_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('eql'); +const EQL_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse(EQL_PREBUILT_RULE_ASSET); + +const ESQL_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('esql'); +const ESQL_PREBUILT_RULE_RESPONSE = + convertPrebuiltRuleAssetToRuleResponse(ESQL_PREBUILT_RULE_ASSET); + +const THREAT_MATCH_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('threat_match'); +const THREAT_MATCH_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + THREAT_MATCH_PREBUILT_RULE_ASSET +); + +const THRESHOLD_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('threshold'); +const THRESHOLD_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + THRESHOLD_PREBUILT_RULE_ASSET +); + +const MACHINE_LEARNING_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('machine_learning'); +const MACHINE_LEARNING_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + MACHINE_LEARNING_PREBUILT_RULE_ASSET +); + +const NEW_TERMS_PREBUILT_RULE_ASSET = getPrebuiltRuleMockOfType('new_terms'); +const NEW_TERMS_PREBUILT_RULE_RESPONSE = convertPrebuiltRuleAssetToRuleResponse( + NEW_TERMS_PREBUILT_RULE_ASSET +); + +/** + * This test suite's purpose is to ensure uniformity between the two different types of rule + * diff calculation we utilize in the prebuilt rule customization code. We have a two-way comparison + * (calculateRuleFieldsDiff) and a three-way comparison (calculateThreeWayRuleFieldsDiff) that share + * underlying comparison logic, but perform calculate their results from different rule structures. + * + * The two-way diff uses the `RuleResponse` type for its rule comparison schema while the three-way + * diff uses the `DiffableRule` schema. While the results for each of these diffing calculations have + * differences in return structure and schema, the determination for if a field is customized should + * be the same across both functions. + * + * In these tests, we test for every field we diff on in `RuleResponse` to determine if both our two- + * way diff and our three-way diff functions return the same result and don't diverge in logical + * comparison. We do this by explicitly defining a base rule field and a modified rule field, and then + * calculating diff objects with both diff methods and ensuring both comparisons give equatable results. + */ +describe('synchronizing 2-way and 3-way rule diff calculations', () => { + const testDiffCalculationEquality = ({ + fieldName, + diffableFieldName, + baseRule, + modifiedRule, + }: { + fieldName: string; + diffableFieldName: string; + baseRule: RuleResponse; + modifiedRule: RuleResponse; + }) => { + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: baseRule, + ruleB: modifiedRule, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(baseRule), + target_version: convertRuleToDiffable(modifiedRule), + }); + + expect(twoWayDiff[fieldName as keyof TwoWayDiffRule]?.is_equal).toEqual(false); + expect(threeWayDiff[diffableFieldName as keyof ThreeWayRuleFieldsDiff]?.has_update).toEqual( + true + ); + }; + + it('unmodified rule objects', () => { + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + ruleB: CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(CUSTOM_QUERY_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(CUSTOM_QUERY_PREBUILT_RULE_RESPONSE), + }); + + expect(Object.values(twoWayDiff).every((field) => field.is_equal === true)).toEqual(true); + expect(Object.values(threeWayDiff).every((diff) => diff.has_update === false)).toEqual(true); + }); + + describe('common fields', () => { + it('"name" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + name: 'base name', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + name: 'updated name', + }; + + testDiffCalculationEquality({ + fieldName: 'name', + diffableFieldName: 'name', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"version" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + version: 1, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + version: 2, + }; + + testDiffCalculationEquality({ + fieldName: 'version', + diffableFieldName: 'version', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"tags" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + tags: ['test one'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + tags: ['test one', 'test two'], + }; + + testDiffCalculationEquality({ + fieldName: 'tags', + diffableFieldName: 'tags', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"description" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + description: 'test description', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + description: 'updated test description', + }; + + testDiffCalculationEquality({ + fieldName: 'description', + diffableFieldName: 'description', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"severity" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + severity: SeverityEnum.low, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + severity: SeverityEnum.high, + }; + + testDiffCalculationEquality({ + fieldName: 'severity', + diffableFieldName: 'severity', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"severity_mapping" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + severity_mapping: [ + { + field: 'event.severity', + operator: 'equals' as const, + severity: SeverityEnum.low, + value: 'LOW', + }, + ], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + severity_mapping: [ + { + field: 'event.severity', + operator: 'equals' as const, + severity: SeverityEnum.high, + value: 'HIGH', + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'severity_mapping', + diffableFieldName: 'severity_mapping', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"risk_score" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + risk_score: 30, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + risk_score: 40, + }; + + testDiffCalculationEquality({ + fieldName: 'risk_score', + diffableFieldName: 'risk_score', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"risk_score_mapping" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + risk_score_mapping: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + risk_score_mapping: [ + { field: 'event.risk_score', operator: 'equals' as const, value: 'updated value' }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'risk_score_mapping', + diffableFieldName: 'risk_score_mapping', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"references" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + references: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + references: ['http://test.test'], + }; + + testDiffCalculationEquality({ + fieldName: 'references', + diffableFieldName: 'references', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"false_positives" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + false_positives: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + false_positives: ['test false positive'], + }; + + testDiffCalculationEquality({ + fieldName: 'false_positives', + diffableFieldName: 'false_positives', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + threat: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + threat: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0000', + name: 'test tactic', + reference: 'https://attack.mitre.org/tactics/TA0000/', + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'threat', + diffableFieldName: 'threat', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"note" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + note: '## base markdown', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + note: '## updated markdown', + }; + + testDiffCalculationEquality({ + fieldName: 'note', + diffableFieldName: 'note', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"setup" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + setup: '## base markdown', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + setup: '## updated markdown', + }; + + testDiffCalculationEquality({ + fieldName: 'setup', + diffableFieldName: 'setup', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"related_integrations" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + related_integrations: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + related_integrations: [{ package: 'package-test', version: '^1.2.3' }], + }; + + testDiffCalculationEquality({ + fieldName: 'related_integrations', + diffableFieldName: 'related_integrations', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"required_fields" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + required_fields: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + required_fields: [{ name: '@timestamp', type: 'date', ecs: true }], + }; + + testDiffCalculationEquality({ + fieldName: 'required_fields', + diffableFieldName: 'required_fields', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"max_signals" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + max_signals: 100, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + max_signals: 50, + }; + + testDiffCalculationEquality({ + fieldName: 'max_signals', + diffableFieldName: 'max_signals', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"investigation_fields" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + investigation_fields: { field_names: ['foo', 'bar'] }, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + investigation_fields: { field_names: ['blob', 'boop'] }, + }; + + testDiffCalculationEquality({ + fieldName: 'investigation_fields', + diffableFieldName: 'investigation_fields', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"rule_name_override" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + rule_name_override: 'field.name', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + rule_name_override: 'field.updated.name', + }; + + testDiffCalculationEquality({ + fieldName: 'rule_name_override', + diffableFieldName: 'rule_name_override', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"timestamp_override" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timestamp_override: 'field.name', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timestamp_override: 'field.updated.name', + }; + + testDiffCalculationEquality({ + fieldName: 'timestamp_override', + diffableFieldName: 'timestamp_override', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"timestamp_override_fallback_disabled" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + /** + * `timestamp_override` field has to be present in both fields + * because we only extract `timestamp_override_fallback_disabled` + * out in diffable rules if `timestamp_override` exists. Field value + * will be unchanged between the versions and doesn't affect diff output. + */ + timestamp_override: 'field.name', + timestamp_override_fallback_disabled: false, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timestamp_override: 'field.name', + timestamp_override_fallback_disabled: true, + }; + + testDiffCalculationEquality({ + fieldName: 'timestamp_override_fallback_disabled', + diffableFieldName: 'timestamp_override', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"timeline_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timeline_id: 'base-timeline-id', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timeline_id: 'updated-timeline-id', + }; + + testDiffCalculationEquality({ + fieldName: 'timeline_id', + diffableFieldName: 'timeline_template', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"timeline_title" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + /** + * `timeline_id` field has to be present in both fields + * because we only extract `timeline_title` out in diffable + * rules if `timeline_id` exists. Field value will be + * unchanged between the versions and doesn't affect diff output. + */ + timeline_id: 'timeline-id-123', + timeline_title: 'base timeline title', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + timeline_id: 'timeline-id-123', + timeline_title: 'updated timeline title', + }; + + testDiffCalculationEquality({ + fieldName: 'timeline_title', + diffableFieldName: 'timeline_template', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"building_block_type" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + building_block_type: 'base type', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + building_block_type: 'updated type', + }; + + testDiffCalculationEquality({ + fieldName: 'building_block_type', + diffableFieldName: 'building_block', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"from" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + from: 'now-10m', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + from: 'now-5m', + }; + + testDiffCalculationEquality({ + fieldName: 'from', + diffableFieldName: 'rule_schedule', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"to" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + to: 'now', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + to: 'now-5m', + }; + + testDiffCalculationEquality({ + fieldName: 'to', + diffableFieldName: 'rule_schedule', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"interval" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + interval: '5m', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + interval: '10m', + }; + + testDiffCalculationEquality({ + fieldName: 'interval', + diffableFieldName: 'rule_schedule', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"type" field', () => { + testDiffCalculationEquality({ + fieldName: 'type', + diffableFieldName: 'type', + baseRule: CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + modifiedRule: EQL_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('custom query rule fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + query: 'event.code: "test"', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + query: 'event.code: "updated test"', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"language" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + language: 'lucene' as const, + } as RuleResponse; + + const MODIFIED_PREBUILT_RULE_RESPONSE: RuleResponse = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + language: 'kuery' as const, + } as RuleResponse; + + testDiffCalculationEquality({ + fieldName: 'language', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + filters: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'filters', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + alert_suppression: undefined, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('saved_query rule fields', () => { + it('"saved_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + saved_id: 'saved-id', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + saved_id: 'updated-saved-id', + }; + + testDiffCalculationEquality({ + fieldName: 'saved_id', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + alert_suppression: undefined, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...SAVED_QUERY_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('eql rule fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + query: 'process where true', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + query: 'process where false', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'eql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + filters: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'filters', + diffableFieldName: 'eql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"event_category_override" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + event_category_override: 'host.name', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + event_category_override: 'host.type', + }; + + testDiffCalculationEquality({ + fieldName: 'event_category_override', + diffableFieldName: 'eql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"tiebreaker_field" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + tiebreaker_field: 'host.name', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + tiebreaker_field: 'host.type', + }; + + testDiffCalculationEquality({ + fieldName: 'tiebreaker_field', + diffableFieldName: 'eql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"timestamp_field" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + timestamp_field: 'event.ingested', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + timestamp_field: 'event.occurance', + }; + + testDiffCalculationEquality({ + fieldName: 'timestamp_field', + diffableFieldName: 'eql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + alert_suppression: undefined, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...EQL_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('esql rule fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...ESQL_PREBUILT_RULE_RESPONSE, + query: 'GET event IN *', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...ESQL_PREBUILT_RULE_RESPONSE, + query: 'GET host IN *', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'esql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...ESQL_PREBUILT_RULE_RESPONSE, + alert_suppression: undefined, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...ESQL_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('threat match rule fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + query: 'event.code: "test"', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + query: 'event.code: "updated test"', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"language" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + language: 'lucene' as const, + } as RuleResponse; + + const MODIFIED_PREBUILT_RULE_RESPONSE: RuleResponse = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + language: 'kuery' as const, + } as RuleResponse; + + testDiffCalculationEquality({ + fieldName: 'language', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + filters: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'filters', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_query: 'event.code: "test"', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_query: 'event.code: "updated test"', + }; + + testDiffCalculationEquality({ + fieldName: 'threat_query', + diffableFieldName: 'threat_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_filters: [], + }; + + testDiffCalculationEquality({ + fieldName: 'threat_filters', + diffableFieldName: 'threat_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_language" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_language: 'kuery' as const, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_language: 'lucene' as const, + }; + + testDiffCalculationEquality({ + fieldName: 'threat_language', + diffableFieldName: 'threat_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_index: ['test-index-1'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_index: ['test-index-2'], + }; + + testDiffCalculationEquality({ + fieldName: 'threat_index', + diffableFieldName: 'threat_index', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_indicator_path" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_indicator_path: 'C:over/there.exe', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_indicator_path: 'C:over/here.exe', + }; + + testDiffCalculationEquality({ + fieldName: 'threat_indicator_path', + diffableFieldName: 'threat_indicator_path', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threat_mapping" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_mapping: [ + { + entries: [ + { + field: 'Endpoint.capabilities', + type: 'mapping' as const, + value: 'Target.dll.pe.description', + }, + ], + }, + ], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + threat_mapping: [ + { + entries: [ + { + field: 'Endpoint.capabilities', + type: 'mapping' as const, + value: 'Target.dll.pe.name', + }, + ], + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'threat_mapping', + diffableFieldName: 'threat_mapping', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + alert_suppression: undefined, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THREAT_MATCH_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('threshold rule fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + query: 'event.code: "test"', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + query: 'event.code: "updated test"', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"language" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + language: 'lucene' as const, + } as RuleResponse; + + const MODIFIED_PREBUILT_RULE_RESPONSE: RuleResponse = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + language: 'kuery' as const, + } as RuleResponse; + + testDiffCalculationEquality({ + fieldName: 'language', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + filters: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'filters', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"threshold" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + threshold: { + field: ['Responses.process.pid'], + value: 100, + cardinality: [{ field: 'host.id', value: 2 }], + }, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + threshold: { + field: ['host.name'], + value: 50, + cardinality: [{ field: 'host.id', value: 2 }], + }, + }; + + testDiffCalculationEquality({ + fieldName: 'threshold', + diffableFieldName: 'threshold', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 1, unit: AlertSuppressionDurationUnitEnum.h }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...THRESHOLD_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('machine learning rule fields', () => { + it('"machine_learning_job_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + machine_learning_job_id: 'base-ml-test-id', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + machine_learning_job_id: 'updated-ml-test-id', + }; + + testDiffCalculationEquality({ + fieldName: 'machine_learning_job_id', + diffableFieldName: 'machine_learning_job_id', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"anomaly_threshold" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + anomaly_threshold: 20, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + anomaly_threshold: 45, + }; + + testDiffCalculationEquality({ + fieldName: 'anomaly_threshold', + diffableFieldName: 'anomaly_threshold', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 10, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...MACHINE_LEARNING_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('new terms fields', () => { + it('"query" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + query: 'event.code: "test"', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + query: 'event.code: "updated test"', + }; + + testDiffCalculationEquality({ + fieldName: 'query', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"language" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + language: 'lucene' as const, + } as RuleResponse; + + const MODIFIED_PREBUILT_RULE_RESPONSE: RuleResponse = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + language: 'kuery' as const, + } as RuleResponse; + + testDiffCalculationEquality({ + fieldName: 'language', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"filters" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + filters: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + filters: [ + { + meta: { + negate: false, + disabled: false, + type: 'phrase', + key: 'test', + params: { + query: 'value', + }, + }, + query: { + term: { + field: 'value', + }, + }, + }, + ], + }; + + testDiffCalculationEquality({ + fieldName: 'filters', + diffableFieldName: 'kql_query', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"new_terms_fields" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + new_terms_fields: ['host.name'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + new_terms_fields: ['host.name', 'event.action'], + }; + + testDiffCalculationEquality({ + fieldName: 'new_terms_fields', + diffableFieldName: 'new_terms_fields', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"history_window_start" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + history_window_start: 'now-7d', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + history_window_start: 'now-21d', + }; + + testDiffCalculationEquality({ + fieldName: 'history_window_start', + diffableFieldName: 'history_window_start', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"data_view_id" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + data_view_id: 'test-data-view', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + data_view_id: 'updated-test-data-view', + }; + + testDiffCalculationEquality({ + fieldName: 'data_view_id', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"index" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + index: ['pattern-1', 'pattern-2', 'pattern-3'], + }; + + testDiffCalculationEquality({ + fieldName: 'index', + diffableFieldName: 'data_source', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + + it('"alert_suppression" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['event.code'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...NEW_TERMS_PREBUILT_RULE_RESPONSE, + alert_suppression: { + group_by: ['host.name'], + duration: { value: 5, unit: AlertSuppressionDurationUnitEnum.m }, + missing_fields_strategy: AlertSuppressionMissingFieldsStrategyEnum.suppress, + }, + }; + + testDiffCalculationEquality({ + fieldName: 'alert_suppression', + diffableFieldName: 'alert_suppression', + baseRule: BASE_PREBUILT_RULE_RESPONSE, + modifiedRule: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + }); + }); + + describe('non-customizable fields', () => { + it('"actions" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + actions: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + actions: [ + { + id: 'id', + group: 'group', + params: {}, + action_type_id: 'action_type_id', + }, + ], + }; + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: BASE_PREBUILT_RULE_RESPONSE, + ruleB: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(BASE_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(MODIFIED_PREBUILT_RULE_RESPONSE), + }); + + expect(twoWayDiff).not.toHaveProperty('actions'); + expect(threeWayDiff).not.toHaveProperty('actions'); + }); + + it('"exceptions_list" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + exceptions_list: [], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + namespace_type: 'agnostic' as const, + type: 'endpoint' as const, + }, + ], + }; + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: BASE_PREBUILT_RULE_RESPONSE, + ruleB: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(BASE_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(MODIFIED_PREBUILT_RULE_RESPONSE), + }); + + expect(twoWayDiff).not.toHaveProperty('exceptions_list'); + expect(threeWayDiff).not.toHaveProperty('exceptions_list'); + }); + + it('"enabled" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + enabled: true, + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + enabled: false, + }; + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: BASE_PREBUILT_RULE_RESPONSE, + ruleB: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(BASE_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(MODIFIED_PREBUILT_RULE_RESPONSE), + }); + + expect(twoWayDiff).not.toHaveProperty('enabled'); + expect(threeWayDiff).not.toHaveProperty('enabled'); + }); + + it('"author" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + author: ['base author'], + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + author: ['new author'], + }; + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: BASE_PREBUILT_RULE_RESPONSE, + ruleB: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(BASE_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(MODIFIED_PREBUILT_RULE_RESPONSE), + }); + + expect(twoWayDiff).not.toHaveProperty('author'); + expect(threeWayDiff).not.toHaveProperty('author'); + }); + + it('"license" field', () => { + const BASE_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + license: 'base-license', + }; + const MODIFIED_PREBUILT_RULE_RESPONSE = { + ...CUSTOM_QUERY_PREBUILT_RULE_RESPONSE, + license: 'updated-license', + }; + const twoWayDiff = calculateRuleFieldsDiff({ + ruleA: BASE_PREBUILT_RULE_RESPONSE, + ruleB: MODIFIED_PREBUILT_RULE_RESPONSE, + }); + const threeWayDiff = calculateThreeWayRuleFieldsDiff({ + base_version: MissingVersion, + current_version: convertRuleToDiffable(BASE_PREBUILT_RULE_RESPONSE), + target_version: convertRuleToDiffable(MODIFIED_PREBUILT_RULE_RESPONSE), + }); + + expect(twoWayDiff).not.toHaveProperty('license'); + expect(threeWayDiff).not.toHaveProperty('license'); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index 0f2544ca99f5c..38910f9c261d3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -5,388 +5,204 @@ * 2.0. */ -import { assertUnreachable } from '../../../../../../../common/utility_types'; -import { invariant } from '../../../../../../../common/utils/invariant'; - import type { - AllFieldsDiff, - DiffableAllFields, - DiffableCommonFields, - DiffableCustomQueryFields, - DiffableEqlFields, - DiffableEsqlFields, - DiffableMachineLearningFields, - DiffableNewTermsFields, - DiffableRule, - DiffableSavedQueryFields, - DiffableThreatMatchFields, - DiffableThresholdFields, - CommonFieldsDiff, - CustomQueryFieldsDiff, - EqlFieldsDiff, - EsqlFieldsDiff, - MachineLearningFieldsDiff, - NewTermsFieldsDiff, - RuleFieldsDiff, - SavedQueryFieldsDiff, - ThreatMatchFieldsDiff, - ThresholdFieldsDiff, -} from '../../../../../../../common/api/detection_engine/prebuilt_rules'; - -import type { FieldsDiffAlgorithmsFor } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/rule_diff/fields_diff'; -import type { ThreeVersionsOf } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; -import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; -import { calculateFieldsDiffFor } from './diff_calculation_helpers'; -import { - dataSourceDiffAlgorithm, - multiLineStringDiffAlgorithm, - numberDiffAlgorithm, - simpleDiffAlgorithm, - singleLineStringDiffAlgorithm, - kqlQueryDiffAlgorithm, - eqlQueryDiffAlgorithm, - esqlQueryDiffAlgorithm, - ruleTypeDiffAlgorithm, - forceTargetVersionDiffAlgorithm, -} from './algorithms'; -import { - ScalarArrayDiffMissingBaseVersionStrategy, - createScalarArrayDiffAlgorithm, -} from './algorithms/scalar_array_diff_algorithm'; - -const BASE_TYPE_ERROR = `Base version can't be of different rule type`; -const TARGET_TYPE_ERROR = `Target version can't be of different rule type`; + TwoWayRuleDiffCommonFields, + TwoWayRuleDiffCustomQueryFields, + TwoWayRuleDiffEqlFields, + TwoWayRuleDiffEsqlFields, + TwoWayRuleDiffMachineLearningFields, + TwoWayRuleDiffNewTermsFields, + TwoWayDiffRule, + TwoWayRuleDiffSavedQueryFields, + TwoWayRuleDiffThreatMatchFields, + TwoWayRuleDiffThresholdFields, + TwoWayRuleFieldsDiff, +} from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_rule_diff'; +import { normalizeRuleResponse } from '../../../../../../../common/detection_engine/prebuilt_rules/diff/normalize_rule_response'; +import type { RuleResponse } from '../../../../../../../common/api/detection_engine'; +import type { TwoWayFieldsDiffAlgorithmsFor } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/two_way_diff/two_way_diff_outcome'; +import { deepEqualityDiffAlgorithm } from './two_way_diff_algorithms/deep_equality_diff_algorithm'; +import { orderAgnosticArrayDiffAlgorithm } from './two_way_diff_algorithms/order_agnostic_array_diff_algorithm'; /** - * Calculates a three-way diff per each top-level rule field. - * Returns an object which keys are equal to rule's field names and values are - * three-way diffs calculated for those fields. + * Determines the diff between two rule response objects and returns a list of every rule field */ -export const calculateRuleFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean = false -): RuleFieldsDiff => { - const commonFieldsDiff = calculateCommonFieldsDiff(ruleVersions, isRuleCustomized); - // eslint-disable-next-line @typescript-eslint/naming-convention - const { base_version, current_version, target_version } = ruleVersions; - const hasBaseVersion = base_version !== MissingVersion; - - const isRuleTypeDifferentInTargetVersion = current_version.type !== target_version.type; - const isRuleTypeDifferentInBaseVersion = hasBaseVersion - ? current_version.type !== base_version.type - : false; - - if (isRuleTypeDifferentInTargetVersion || isRuleTypeDifferentInBaseVersion) { - // If rule type has been changed by Elastic in the target version (can happen) - // or by user in the current version (should never happen), we can't calculate the diff - // only for fields of a single rule type, and need to calculate it for all fields - // of all the rule types we have. - // TODO: Try to get rid of "as" casting - return calculateAllFieldsDiff( - { - base_version: base_version as DiffableAllFields | MissingVersion, - current_version: current_version as DiffableAllFields, - target_version: target_version as DiffableAllFields, - }, - isRuleCustomized - ) as RuleFieldsDiff; - } - - switch (current_version.type) { - case 'query': { - if (hasBaseVersion) { - invariant(base_version.type === 'query', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'query', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateCustomQueryFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'saved_query': { - if (hasBaseVersion) { - invariant(base_version.type === 'saved_query', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'saved_query', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateSavedQueryFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'eql': { - if (hasBaseVersion) { - invariant(base_version.type === 'eql', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'eql', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateEqlFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'threat_match': { - if (hasBaseVersion) { - invariant(base_version.type === 'threat_match', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'threat_match', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateThreatMatchFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'threshold': { - if (hasBaseVersion) { - invariant(base_version.type === 'threshold', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'threshold', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateThresholdFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), +export const calculateRuleFieldsDiff = ({ + ruleA, + ruleB, +}: { + ruleA: RuleResponse; + ruleB: RuleResponse; +}): TwoWayRuleFieldsDiff => { + const normalizedRuleA = normalizeRuleResponse(ruleA); + const normalizedRuleB = normalizeRuleResponse(ruleB); + + const fieldsDiff: Partial = {}; + + const keys = new Set([...Object.keys(normalizedRuleA), ...Object.keys(normalizedRuleB)]); + + for (const key of keys) { + const fieldKey = key as keyof TwoWayDiffRule; + const valueA = normalizedRuleA[fieldKey]; + const valueB = normalizedRuleB[fieldKey]; + + const comparator = allFieldsComparators[fieldKey] as + | ((a: unknown, b: unknown) => boolean) + | undefined; + + // We only compare fields if there is a comparator explicitly defined for the field in the lists below + if (comparator) { + fieldsDiff[fieldKey] = { + is_equal: comparator(valueA, valueB), + value_a: valueA, + value_b: valueB, }; } - case 'machine_learning': { - if (hasBaseVersion) { - invariant(base_version.type === 'machine_learning', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'machine_learning', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateMachineLearningFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'new_terms': { - if (hasBaseVersion) { - invariant(base_version.type === 'new_terms', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'new_terms', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateNewTermsFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - case 'esql': { - if (hasBaseVersion) { - invariant(base_version.type === 'esql', BASE_TYPE_ERROR); - } - invariant(target_version.type === 'esql', TARGET_TYPE_ERROR); - return { - ...commonFieldsDiff, - ...calculateEsqlFieldsDiff( - { base_version, current_version, target_version }, - isRuleCustomized - ), - }; - } - default: { - return assertUnreachable(current_version, 'Unhandled rule type'); - } } + return fieldsDiff as TwoWayRuleFieldsDiff; }; -const calculateCommonFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): CommonFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, commonFieldsDiffAlgorithms, isRuleCustomized); -}; - -const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - rule_id: simpleDiffAlgorithm, - /** - * `version` shouldn't have a conflict. It always get target value automatically. - * Diff has informational purpose. - */ - version: forceTargetVersionDiffAlgorithm, - name: singleLineStringDiffAlgorithm, - tags: createScalarArrayDiffAlgorithm({ - missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, - }), - description: multiLineStringDiffAlgorithm, - severity: singleLineStringDiffAlgorithm, - severity_mapping: simpleDiffAlgorithm, - risk_score: numberDiffAlgorithm, - risk_score_mapping: simpleDiffAlgorithm, - references: createScalarArrayDiffAlgorithm({ - missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, - }), - false_positives: simpleDiffAlgorithm, - threat: simpleDiffAlgorithm, - note: multiLineStringDiffAlgorithm, - setup: multiLineStringDiffAlgorithm, - related_integrations: simpleDiffAlgorithm, - required_fields: simpleDiffAlgorithm, - rule_schedule: simpleDiffAlgorithm, - max_signals: numberDiffAlgorithm, - rule_name_override: simpleDiffAlgorithm, - timestamp_override: simpleDiffAlgorithm, - timeline_template: simpleDiffAlgorithm, - building_block: simpleDiffAlgorithm, - investigation_fields: simpleDiffAlgorithm, -}; - -const calculateCustomQueryFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): CustomQueryFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, customQueryFieldsDiffAlgorithms, isRuleCustomized); -}; - -const customQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - kql_query: kqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateSavedQueryFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): SavedQueryFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, savedQueryFieldsDiffAlgorithms, isRuleCustomized); -}; - -const savedQueryFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - kql_query: kqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateEqlFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): EqlFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, eqlFieldsDiffAlgorithms, isRuleCustomized); -}; - -const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - eql_query: eqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateEsqlFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): EsqlFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, esqlFieldsDiffAlgorithms, isRuleCustomized); -}; - -const esqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - esql_query: esqlQueryDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateThreatMatchFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): ThreatMatchFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, threatMatchFieldsDiffAlgorithms, isRuleCustomized); -}; - -const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - kql_query: kqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - threat_query: kqlQueryDiffAlgorithm, - threat_index: createScalarArrayDiffAlgorithm({ - missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, - }), - threat_mapping: simpleDiffAlgorithm, - threat_indicator_path: singleLineStringDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateThresholdFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): ThresholdFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, thresholdFieldsDiffAlgorithms, isRuleCustomized); -}; - -const thresholdFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - kql_query: kqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - threshold: simpleDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, -}; - -const calculateMachineLearningFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): MachineLearningFieldsDiff => { - return calculateFieldsDiffFor( - ruleVersions, - machineLearningFieldsDiffAlgorithms, - isRuleCustomized - ); -}; +/** + * Field Comparators + * + * This is an exhaustive list of comparators based on the `RuleDiffField` types which is, aside from minor + * omittances for non-diffable fields, a 1-to-1 extension of the `RuleResponse` schema. Every field we diff on + * is listed here with its corresponding comparison logic. If a rule field is not listed here, it will not be + * used in the diffing logic and will not be returned in the final outcome object. + * + * NOTE: When adding a new field to these comparators, also add a test for diff synchronization in `./calculate_rule_diff_synchronization.test.ts` + */ -const machineLearningFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = +const commonFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + version: deepEqualityDiffAlgorithm, + name: deepEqualityDiffAlgorithm, + tags: orderAgnosticArrayDiffAlgorithm, + description: deepEqualityDiffAlgorithm, + severity: deepEqualityDiffAlgorithm, + severity_mapping: deepEqualityDiffAlgorithm, + risk_score: deepEqualityDiffAlgorithm, + risk_score_mapping: deepEqualityDiffAlgorithm, + references: orderAgnosticArrayDiffAlgorithm, + false_positives: deepEqualityDiffAlgorithm, + threat: deepEqualityDiffAlgorithm, + note: deepEqualityDiffAlgorithm, + setup: deepEqualityDiffAlgorithm, + related_integrations: deepEqualityDiffAlgorithm, + required_fields: deepEqualityDiffAlgorithm, + from: deepEqualityDiffAlgorithm, + to: deepEqualityDiffAlgorithm, + interval: deepEqualityDiffAlgorithm, + investigation_fields: deepEqualityDiffAlgorithm, + building_block_type: deepEqualityDiffAlgorithm, + timeline_id: deepEqualityDiffAlgorithm, + timeline_title: deepEqualityDiffAlgorithm, + timestamp_override: deepEqualityDiffAlgorithm, + timestamp_override_fallback_disabled: deepEqualityDiffAlgorithm, + rule_name_override: deepEqualityDiffAlgorithm, + max_signals: deepEqualityDiffAlgorithm, +}; + +const customQueryFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - machine_learning_job_id: simpleDiffAlgorithm, - anomaly_threshold: numberDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + saved_id: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, }; -const calculateNewTermsFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): NewTermsFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, newTermsFieldsDiffAlgorithms, isRuleCustomized); -}; +const savedQueryFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + saved_id: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, +}; + +const eqlFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, + event_category_override: deepEqualityDiffAlgorithm, + tiebreaker_field: deepEqualityDiffAlgorithm, + timestamp_field: deepEqualityDiffAlgorithm, +}; + +const esqlFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, +}; + +const threatMatchFieldComparators: TwoWayFieldsDiffAlgorithmsFor = + { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + saved_id: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, + threat_query: deepEqualityDiffAlgorithm, + threat_index: orderAgnosticArrayDiffAlgorithm, + threat_filters: deepEqualityDiffAlgorithm, + threat_indicator_path: deepEqualityDiffAlgorithm, + threat_language: deepEqualityDiffAlgorithm, + threat_mapping: deepEqualityDiffAlgorithm, + }; -const newTermsFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - type: ruleTypeDiffAlgorithm, - kql_query: kqlQueryDiffAlgorithm, - data_source: dataSourceDiffAlgorithm, - new_terms_fields: createScalarArrayDiffAlgorithm({ - missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, - }), - history_window_start: singleLineStringDiffAlgorithm, - alert_suppression: simpleDiffAlgorithm, +const thresholdFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + saved_id: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, + threshold: deepEqualityDiffAlgorithm, }; -const calculateAllFieldsDiff = ( - ruleVersions: ThreeVersionsOf, - isRuleCustomized: boolean -): AllFieldsDiff => { - return calculateFieldsDiffFor(ruleVersions, allFieldsDiffAlgorithms, isRuleCustomized); -}; +const machineLearningFieldComparators: TwoWayFieldsDiffAlgorithmsFor = + { + type: deepEqualityDiffAlgorithm, + anomaly_threshold: deepEqualityDiffAlgorithm, + machine_learning_job_id: deepEqualityDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, + }; -const allFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { - ...commonFieldsDiffAlgorithms, - ...customQueryFieldsDiffAlgorithms, - ...savedQueryFieldsDiffAlgorithms, - ...eqlFieldsDiffAlgorithms, - ...esqlFieldsDiffAlgorithms, - ...threatMatchFieldsDiffAlgorithms, - ...thresholdFieldsDiffAlgorithms, - ...machineLearningFieldsDiffAlgorithms, - ...newTermsFieldsDiffAlgorithms, - type: ruleTypeDiffAlgorithm, +const newTermsFieldComparators: TwoWayFieldsDiffAlgorithmsFor = { + type: deepEqualityDiffAlgorithm, + query: deepEqualityDiffAlgorithm, + language: deepEqualityDiffAlgorithm, + filters: deepEqualityDiffAlgorithm, + data_view_id: deepEqualityDiffAlgorithm, + index: orderAgnosticArrayDiffAlgorithm, + alert_suppression: deepEqualityDiffAlgorithm, + new_terms_fields: orderAgnosticArrayDiffAlgorithm, + history_window_start: deepEqualityDiffAlgorithm, +}; + +const allFieldsComparators: TwoWayFieldsDiffAlgorithmsFor = { + ...commonFieldComparators, + ...customQueryFieldComparators, + ...savedQueryFieldComparators, + ...eqlFieldComparators, + ...esqlFieldComparators, + ...threatMatchFieldComparators, + ...thresholdFieldComparators, + ...machineLearningFieldComparators, + ...newTermsFieldComparators, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_three_way_rule_fields_diff.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_three_way_rule_fields_diff.ts new file mode 100644 index 0000000000000..aa00b6a1ca19d --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_three_way_rule_fields_diff.ts @@ -0,0 +1,394 @@ +/* + * 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 { assertUnreachable } from '../../../../../../../common/utility_types'; +import { invariant } from '../../../../../../../common/utils/invariant'; + +import type { + AllThreeWayFieldsDiff, + DiffableAllFields, + DiffableCommonFields, + DiffableCustomQueryFields, + DiffableEqlFields, + DiffableEsqlFields, + DiffableMachineLearningFields, + DiffableNewTermsFields, + DiffableRule, + DiffableSavedQueryFields, + DiffableThreatMatchFields, + DiffableThresholdFields, + CommonThreeWayFieldsDiff, + CustomQueryThreeWayFieldsDiff, + EqlThreeWayFieldsDiff, + EsqlThreeWayFieldsDiff, + MachineLearningThreeWayFieldsDiff, + NewTermsThreeWayFieldsDiff, + ThreeWayRuleFieldsDiff, + SavedQueryThreeWayFieldsDiff, + ThreatMatchThreeWayFieldsDiff, + ThresholdThreeWayFieldsDiff, +} from '../../../../../../../common/api/detection_engine/prebuilt_rules'; + +import type { ThreeWayFieldsDiffAlgorithmsFor } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_fields_diff'; +import type { ThreeVersionsOf } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; +import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; +import { calculateFieldsDiffFor } from './diff_calculation_helpers'; +import { + dataSourceDiffAlgorithm, + multiLineStringDiffAlgorithm, + numberDiffAlgorithm, + simpleDiffAlgorithm, + singleLineStringDiffAlgorithm, + kqlQueryDiffAlgorithm, + eqlQueryDiffAlgorithm, + esqlQueryDiffAlgorithm, + ruleTypeDiffAlgorithm, + forceTargetVersionDiffAlgorithm, +} from './three_way_diff_algorithms'; +import { + ScalarArrayDiffMissingBaseVersionStrategy, + createScalarArrayDiffAlgorithm, +} from './three_way_diff_algorithms/scalar_array_diff_algorithm'; + +const BASE_TYPE_ERROR = `Base version can't be of different rule type`; +const TARGET_TYPE_ERROR = `Target version can't be of different rule type`; + +/** + * Calculates a three-way diff per each top-level rule field. + * Returns an object which keys are equal to rule's field names and values are + * three-way diffs calculated for those fields. + */ +export const calculateThreeWayRuleFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean = false +): ThreeWayRuleFieldsDiff => { + const commonFieldsDiff = calculateCommonFieldsDiff(ruleVersions, isRuleCustomized); + // eslint-disable-next-line @typescript-eslint/naming-convention + const { base_version, current_version, target_version } = ruleVersions; + const hasBaseVersion = base_version !== MissingVersion; + + const isRuleTypeDifferentInTargetVersion = current_version.type !== target_version.type; + const isRuleTypeDifferentInBaseVersion = hasBaseVersion + ? current_version.type !== base_version.type + : false; + + if (isRuleTypeDifferentInTargetVersion || isRuleTypeDifferentInBaseVersion) { + // If rule type has been changed by Elastic in the target version (can happen) + // or by user in the current version (should never happen), we can't calculate the diff + // only for fields of a single rule type, and need to calculate it for all fields + // of all the rule types we have. + // TODO: Try to get rid of "as" casting + return calculateAllFieldsDiff( + { + base_version: base_version as DiffableAllFields | MissingVersion, + current_version: current_version as DiffableAllFields, + target_version: target_version as DiffableAllFields, + }, + isRuleCustomized + ) as ThreeWayRuleFieldsDiff; + } + + switch (current_version.type) { + case 'query': { + if (hasBaseVersion) { + invariant(base_version.type === 'query', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'query', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateCustomQueryFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'saved_query': { + if (hasBaseVersion) { + invariant(base_version.type === 'saved_query', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'saved_query', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateSavedQueryFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'eql': { + if (hasBaseVersion) { + invariant(base_version.type === 'eql', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'eql', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateEqlFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'threat_match': { + if (hasBaseVersion) { + invariant(base_version.type === 'threat_match', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'threat_match', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateThreatMatchFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'threshold': { + if (hasBaseVersion) { + invariant(base_version.type === 'threshold', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'threshold', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateThresholdFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'machine_learning': { + if (hasBaseVersion) { + invariant(base_version.type === 'machine_learning', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'machine_learning', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateMachineLearningFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'new_terms': { + if (hasBaseVersion) { + invariant(base_version.type === 'new_terms', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'new_terms', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateNewTermsFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + case 'esql': { + if (hasBaseVersion) { + invariant(base_version.type === 'esql', BASE_TYPE_ERROR); + } + invariant(target_version.type === 'esql', TARGET_TYPE_ERROR); + return { + ...commonFieldsDiff, + ...calculateEsqlFieldsDiff( + { base_version, current_version, target_version }, + isRuleCustomized + ), + }; + } + default: { + return assertUnreachable(current_version, 'Unhandled rule type'); + } + } +}; + +const calculateCommonFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): CommonThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, commonFieldsDiffAlgorithms, isRuleCustomized); +}; + +const commonFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + rule_id: simpleDiffAlgorithm, + /** + * `version` shouldn't have a conflict. It always get target value automatically. + * Diff has informational purpose. + */ + version: forceTargetVersionDiffAlgorithm, + name: singleLineStringDiffAlgorithm, + tags: createScalarArrayDiffAlgorithm({ + missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, + }), + description: multiLineStringDiffAlgorithm, + severity: singleLineStringDiffAlgorithm, + severity_mapping: simpleDiffAlgorithm, + risk_score: numberDiffAlgorithm, + risk_score_mapping: simpleDiffAlgorithm, + references: createScalarArrayDiffAlgorithm({ + missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, + }), + false_positives: simpleDiffAlgorithm, + threat: simpleDiffAlgorithm, + note: multiLineStringDiffAlgorithm, + setup: multiLineStringDiffAlgorithm, + related_integrations: simpleDiffAlgorithm, + required_fields: simpleDiffAlgorithm, + rule_schedule: simpleDiffAlgorithm, + max_signals: numberDiffAlgorithm, + rule_name_override: simpleDiffAlgorithm, + timestamp_override: simpleDiffAlgorithm, + timeline_template: simpleDiffAlgorithm, + building_block: simpleDiffAlgorithm, + investigation_fields: simpleDiffAlgorithm, +}; + +const calculateCustomQueryFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): CustomQueryThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, customQueryFieldsDiffAlgorithms, isRuleCustomized); +}; + +const customQueryFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = + { + type: ruleTypeDiffAlgorithm, + kql_query: kqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, + }; + +const calculateSavedQueryFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): SavedQueryThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, savedQueryFieldsDiffAlgorithms, isRuleCustomized); +}; + +const savedQueryFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + type: ruleTypeDiffAlgorithm, + kql_query: kqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, +}; + +const calculateEqlFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): EqlThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, eqlFieldsDiffAlgorithms, isRuleCustomized); +}; + +const eqlFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + type: ruleTypeDiffAlgorithm, + eql_query: eqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, +}; + +const calculateEsqlFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): EsqlThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, esqlFieldsDiffAlgorithms, isRuleCustomized); +}; + +const esqlFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + type: ruleTypeDiffAlgorithm, + esql_query: esqlQueryDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, +}; + +const calculateThreatMatchFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): ThreatMatchThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, threatMatchFieldsDiffAlgorithms, isRuleCustomized); +}; + +const threatMatchFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = + { + type: ruleTypeDiffAlgorithm, + kql_query: kqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + threat_query: kqlQueryDiffAlgorithm, + threat_index: createScalarArrayDiffAlgorithm({ + missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, + }), + threat_mapping: simpleDiffAlgorithm, + threat_indicator_path: singleLineStringDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, + }; + +const calculateThresholdFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): ThresholdThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, thresholdFieldsDiffAlgorithms, isRuleCustomized); +}; + +const thresholdFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + type: ruleTypeDiffAlgorithm, + kql_query: kqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + threshold: simpleDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, +}; + +const calculateMachineLearningFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): MachineLearningThreeWayFieldsDiff => { + return calculateFieldsDiffFor( + ruleVersions, + machineLearningFieldsDiffAlgorithms, + isRuleCustomized + ); +}; + +const machineLearningFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = + { + type: ruleTypeDiffAlgorithm, + machine_learning_job_id: simpleDiffAlgorithm, + anomaly_threshold: numberDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, + }; + +const calculateNewTermsFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): NewTermsThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, newTermsFieldsDiffAlgorithms, isRuleCustomized); +}; + +const newTermsFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + type: ruleTypeDiffAlgorithm, + kql_query: kqlQueryDiffAlgorithm, + data_source: dataSourceDiffAlgorithm, + new_terms_fields: createScalarArrayDiffAlgorithm({ + missingBaseVersionStrategy: ScalarArrayDiffMissingBaseVersionStrategy.UseTarget, + }), + history_window_start: singleLineStringDiffAlgorithm, + alert_suppression: simpleDiffAlgorithm, +}; + +const calculateAllFieldsDiff = ( + ruleVersions: ThreeVersionsOf, + isRuleCustomized: boolean +): AllThreeWayFieldsDiff => { + return calculateFieldsDiffFor(ruleVersions, allFieldsDiffAlgorithms, isRuleCustomized); +}; + +const allFieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor = { + ...commonFieldsDiffAlgorithms, + ...customQueryFieldsDiffAlgorithms, + ...savedQueryFieldsDiffAlgorithms, + ...eqlFieldsDiffAlgorithms, + ...esqlFieldsDiffAlgorithms, + ...threatMatchFieldsDiffAlgorithms, + ...thresholdFieldsDiffAlgorithms, + ...machineLearningFieldsDiffAlgorithms, + ...newTermsFieldsDiffAlgorithms, + type: ruleTypeDiffAlgorithm, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts index 9fe0a958eab2b..69f2c8332fcc1 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/diff_calculation_helpers.ts @@ -7,17 +7,17 @@ import { mapValues } from 'lodash'; import type { - FieldsDiff, - FieldsDiffAlgorithmsFor, + ThreeWayFieldsDiff, + ThreeWayFieldsDiffAlgorithmsFor, ThreeVersionsOf, } from '../../../../../../../common/api/detection_engine/prebuilt_rules'; import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules'; export const calculateFieldsDiffFor = ( ruleVersions: ThreeVersionsOf, - fieldsDiffAlgorithms: FieldsDiffAlgorithmsFor, + fieldsDiffAlgorithms: ThreeWayFieldsDiffAlgorithmsFor, isRuleCustomized: boolean -): FieldsDiff => { +): ThreeWayFieldsDiff => { const result = mapValues(fieldsDiffAlgorithms, (calculateFieldDiff, fieldName) => { const fieldVersions = pickField(fieldName as keyof TObject, ruleVersions); const fieldDiff = calculateFieldDiff(fieldVersions, isRuleCustomized); @@ -25,7 +25,7 @@ export const calculateFieldsDiffFor = ( }); // TODO: try to improve strict typing and get rid of this "as" operator. - return result as FieldsDiff; + return result as ThreeWayFieldsDiff; }; const pickField = ( diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/data_source_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/data_source_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/data_source_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/data_source_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/data_source_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/eql_query_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/eql_query_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/eql_query_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/eql_query_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/eql_query_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/eql_query_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/eql_query_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/eql_query_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/esql_query_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/esql_query_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/esql_query_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/esql_query_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/esql_query_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/esql_query_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/esql_query_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/esql_query_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/force_target_version_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/force_target_version_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/force_target_version_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/force_target_version_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/force_target_version_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/helpers.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/helpers.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/helpers.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/helpers.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/index.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/index.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/kql_query_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/kql_query_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/kql_query_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/kql_query_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/kql_query_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/kql_query_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/kql_query_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/kql_query_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.mock.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.mock.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.mock.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.mock.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/multi_line_string_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/multi_line_string_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/number_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/number_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/number_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/number_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/rule_type_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/rule_type_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/rule_type_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/rule_type_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/rule_type_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/rule_type_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/rule_type_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/rule_type_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/scalar_array_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/scalar_array_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/scalar_array_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/scalar_array_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/scalar_array_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/simple_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/simple_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/simple_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/single_line_string_diff_algorithm.test.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/single_line_string_diff_algorithm.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/single_line_string_diff_algorithm.ts similarity index 100% rename from x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/three_way_diff_algorithms/single_line_string_diff_algorithm.ts diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/deep_equality_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/deep_equality_diff_algorithm.ts new file mode 100644 index 0000000000000..eadb4ce7ed3b7 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/deep_equality_diff_algorithm.ts @@ -0,0 +1,12 @@ +/* + * 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 { isEqual } from 'lodash'; + +export const deepEqualityDiffAlgorithm = (a: TValue, b: TValue): boolean => { + return isEqual(a, b); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/order_agnostic_array_diff_algorithm.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/order_agnostic_array_diff_algorithm.ts new file mode 100644 index 0000000000000..868c6391d7c90 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/two_way_diff_algorithms/order_agnostic_array_diff_algorithm.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEqual } from 'lodash'; + +export const orderAgnosticArrayDiffAlgorithm = ( + a: TValue[] | undefined, + b: TValue[] | undefined +): boolean => { + // Converts to Sets to compare agnostic of order + return isEqual(new Set(a), new Set(b)); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts index 78c94fa250793..66b1e09339833 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/mergers/rule_source/calculate_is_customized.ts @@ -5,11 +5,9 @@ * 2.0. */ +import { calculateRuleFieldsDiff } from '../../../../../prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; import type { RuleResponse } from '../../../../../../../../common/api/detection_engine'; -import { MissingVersion } from '../../../../../../../../common/api/detection_engine'; import type { PrebuiltRuleAsset } from '../../../../../prebuilt_rules'; -import { calculateRuleFieldsDiff } from '../../../../../prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff'; -import { convertRuleToDiffable } from '../../../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable'; import { convertPrebuiltRuleAssetToRuleResponse } from '../../converters/convert_prebuilt_rule_asset_to_rule_response'; interface CalculateIsCustomizedArgs { @@ -61,11 +59,6 @@ export function calculateIsCustomized({ * @returns true if all rule fields are equal, false otherwise */ function areRulesEqual(ruleA: RuleResponse, ruleB: RuleResponse) { - const fieldsDiff = calculateRuleFieldsDiff({ - base_version: MissingVersion, - current_version: convertRuleToDiffable(ruleA), - target_version: convertRuleToDiffable(ruleB), - }); - - return Object.values(fieldsDiff).every((diff) => diff.has_update === false); + const fieldsDiff = calculateRuleFieldsDiff({ ruleA, ruleB }); + return Object.values(fieldsDiff).every((field) => field.is_equal === true); } diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/upgrade_prebuilt_rules/diffable_rule_fields/test_helpers.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/upgrade_prebuilt_rules/diffable_rule_fields/test_helpers.ts index 0ab439cbde954..954a554c4c648 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/upgrade_prebuilt_rules/diffable_rule_fields/test_helpers.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/customization_enabled/upgrade_prebuilt_rules/diffable_rule_fields/test_helpers.ts @@ -8,7 +8,7 @@ import expect from 'expect'; import { isUndefined, omitBy } from 'lodash'; import type { - PartialRuleDiff, + PartialThreeWayRuleDiff, RuleResponse, UpgradeConflictResolution, } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -326,7 +326,7 @@ interface FieldAbsenceAssertParams { * in the diff (`AAA` diff case) */ function expectAAAFieldDiff( - ruleDiff: PartialRuleDiff, + ruleDiff: PartialThreeWayRuleDiff, fieldAssertParams: FieldAbsenceAssertParams ): void { expect(ruleDiff).toMatchObject({ @@ -348,7 +348,10 @@ interface FieldAssertParams { * Asserts provided non-customized `diffableRuleFieldName` doesn't have a conflict * and ready for upgrade (`AAB` diff case) */ -function expectAABFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldAssertParams): void { +function expectAABFieldDiff( + ruleDiff: PartialThreeWayRuleDiff, + fieldAssertParams: FieldAssertParams +): void { expect(ruleDiff).toMatchObject({ num_fields_with_updates: 2, // counts + version field num_fields_with_conflicts: 0, @@ -374,7 +377,10 @@ function expectAABFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldA * Asserts provided customized `diffableRuleFieldName` without an upgrade doesn't have a conflict * and ready for upgrade (`ABA` diff case) */ -function expectABAFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldAssertParams): void { +function expectABAFieldDiff( + ruleDiff: PartialThreeWayRuleDiff, + fieldAssertParams: FieldAssertParams +): void { expect(ruleDiff).toMatchObject({ num_fields_with_updates: 1, // counts version field num_fields_with_conflicts: 0, @@ -400,7 +406,10 @@ function expectABAFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldA * Asserts provided customized `diffableRuleFieldName` with the matching update * doesn't have a conflict and is ready for upgrade (`ABB` diff case) */ -function expectABBFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldAssertParams): void { +function expectABBFieldDiff( + ruleDiff: PartialThreeWayRuleDiff, + fieldAssertParams: FieldAssertParams +): void { expect(ruleDiff).toMatchObject({ num_fields_with_updates: 1, // counts version field num_fields_with_conflicts: 0, @@ -427,7 +436,7 @@ function expectABBFieldDiff(ruleDiff: PartialRuleDiff, fieldAssertParams: FieldA * has a solvable conflict (`ABC` diff case) */ function expectSolvableABCFieldDiff( - ruleDiff: PartialRuleDiff, + ruleDiff: PartialThreeWayRuleDiff, fieldAssertParams: FieldAssertParams ): void { expect(ruleDiff).toMatchObject({ @@ -456,7 +465,7 @@ function expectSolvableABCFieldDiff( * has a non-solvable conflict (`ABC` diff case) */ function expectNonSolvableABCFieldDiff( - ruleDiff: PartialRuleDiff, + ruleDiff: PartialThreeWayRuleDiff, fieldAssertParams: FieldAssertParams ): void { expect(ruleDiff).toMatchObject({ @@ -486,7 +495,7 @@ function expectNonSolvableABCFieldDiff( * has the matching upgrade (`-AA` diff case) */ function expectMissingBaseAAFieldDiff( - ruleDiff: PartialRuleDiff, + ruleDiff: PartialThreeWayRuleDiff, fieldAssertParams: FieldAbsenceAssertParams ): void { expect(ruleDiff).toMatchObject({ @@ -511,7 +520,7 @@ interface MissingBaseFieldAssertParams { * has an upgrade (`-AB` diff case) */ function expectMissingBaseABFieldDiff( - ruleDiff: PartialRuleDiff, + ruleDiff: PartialThreeWayRuleDiff, fieldAssertParams: MissingBaseFieldAssertParams ): void { expect(ruleDiff).toMatchObject({ diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts index add115230f618..7c9f4f8fbe73b 100644 --- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts +++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/utils/rules/prebuilt_rules/review_upgrade_prebuilt_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PartialRuleDiff } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import type { PartialThreeWayRuleDiff } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { REVIEW_RULE_UPGRADE_URL } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/urls'; import type { ReviewRuleUpgradeResponseBody } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules/review_rule_upgrade/review_rule_upgrade_route'; import type SuperTest from 'supertest'; @@ -32,7 +32,7 @@ export const reviewPrebuiltRulesToUpgrade = async ( export async function fetchFirstPrebuiltRuleUpgradeReviewDiff( supertest: SuperTest.Agent -): Promise { +): Promise { const response = await reviewPrebuiltRulesToUpgrade(supertest); return response.rules[0].diff;