diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts index 97e587646e524..b25320e1131ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/create_upgradeable_rules_payload.ts @@ -5,6 +5,7 @@ * 2.0. */ import { pickBy } from 'lodash'; +import { withSecuritySpanSync } from '../../../../../utils/with_security_span'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { PickVersionValuesEnum, @@ -36,77 +37,82 @@ export const createModifiedPrebuiltRuleAssets = ({ upgradeableRules, requestBody, }: CreateModifiedPrebuiltRuleAssetsProps) => { - const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; - - const { modifiedPrebuiltRuleAssets, processingErrors } = upgradeableRules.reduce( - (processedRules, upgradeableRule) => { - const targetRuleType = upgradeableRule.target.type; - const ruleId = upgradeableRule.target.rule_id; - const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); - - try { - if (fieldNames === undefined) { - throw new Error(`Unexpected rule type: ${targetRuleType}`); - } - - const { current, target } = upgradeableRule; - if (current.type !== target.type) { - assertPickVersionIsTarget({ ruleId, requestBody }); - } - - const calculatedRuleDiff = calculateRuleFieldsDiff({ - base_version: upgradeableRule.base - ? convertRuleToDiffable(convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base)) - : MissingVersion, - current_version: convertRuleToDiffable(upgradeableRule.current), - target_version: convertRuleToDiffable( - convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) - ), - }) as AllFieldsDiff; - - if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { - const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); - if (fieldsWithConflicts.length > 0) { - // If the mode is ALL_RULES, no fields can be overriden to any other pick_version - // than "MERGED", so throw an error for the fields that have conflicts. - throw new Error( - `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( - ', ' - )}. Please resolve the conflict manually or choose another value for 'pick_version'` - ); + return withSecuritySpanSync(createModifiedPrebuiltRuleAssets.name, () => { + const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody; + + const { modifiedPrebuiltRuleAssets, processingErrors } = + upgradeableRules.reduce( + (processedRules, upgradeableRule) => { + const targetRuleType = upgradeableRule.target.type; + const ruleId = upgradeableRule.target.rule_id; + const fieldNames = FIELD_NAMES_BY_RULE_TYPE_MAP.get(targetRuleType); + + try { + if (fieldNames === undefined) { + throw new Error(`Unexpected rule type: ${targetRuleType}`); + } + + const { current, target } = upgradeableRule; + if (current.type !== target.type) { + assertPickVersionIsTarget({ ruleId, requestBody }); + } + + const calculatedRuleDiff = calculateRuleFieldsDiff({ + base_version: upgradeableRule.base + ? convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.base) + ) + : MissingVersion, + current_version: convertRuleToDiffable(upgradeableRule.current), + target_version: convertRuleToDiffable( + convertPrebuiltRuleAssetToRuleResponse(upgradeableRule.target) + ), + }) as AllFieldsDiff; + + if (mode === 'ALL_RULES' && globalPickVersion === 'MERGED') { + const fieldsWithConflicts = Object.keys(getFieldsDiffConflicts(calculatedRuleDiff)); + if (fieldsWithConflicts.length > 0) { + // If the mode is ALL_RULES, no fields can be overriden to any other pick_version + // than "MERGED", so throw an error for the fields that have conflicts. + throw new Error( + `Merge conflicts found in rule '${ruleId}' for fields: ${fieldsWithConflicts.join( + ', ' + )}. Please resolve the conflict manually or choose another value for 'pick_version'` + ); + } + } + + const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ + upgradeableRule, + fieldNames, + requestBody, + globalPickVersion, + calculatedRuleDiff, + }); + + processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); + + return processedRules; + } catch (err) { + processedRules.processingErrors.push({ + error: err, + item: { rule_id: ruleId }, + }); + + return processedRules; } + }, + { + modifiedPrebuiltRuleAssets: [], + processingErrors: [], } + ); - const modifiedPrebuiltRuleAsset = createModifiedPrebuiltRuleAsset({ - upgradeableRule, - fieldNames, - requestBody, - globalPickVersion, - calculatedRuleDiff, - }); - - processedRules.modifiedPrebuiltRuleAssets.push(modifiedPrebuiltRuleAsset); - - return processedRules; - } catch (err) { - processedRules.processingErrors.push({ - error: err, - item: { rule_id: ruleId }, - }); - - return processedRules; - } - }, - { - modifiedPrebuiltRuleAssets: [], - processingErrors: [], - } - ); - - return { - modifiedPrebuiltRuleAssets, - processingErrors, - }; + return { + modifiedPrebuiltRuleAssets, + processingErrors, + }; + }); }; interface CreateModifiedPrebuiltRuleAssetParams { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts index acfdb674c309a..750561b9858a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/get_upgradeable_rules.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { withSecuritySpanSync } from '../../../../../utils/with_security_span'; import type { RuleResponse, RuleUpgradeSpecifier, @@ -26,58 +26,60 @@ export const getUpgradeableRules = ({ versionSpecifiers?: RuleUpgradeSpecifier[]; mode: Mode; }) => { - const upgradeableRules = new Map( - rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) - ); - const fetchErrors: Array> = []; - const skippedRules: SkippedRuleUpgrade[] = []; + return withSecuritySpanSync(getUpgradeableRules.name, () => { + const upgradeableRules = new Map( + rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule]) + ); + const fetchErrors: Array> = []; + const skippedRules: SkippedRuleUpgrade[] = []; - if (mode === ModeEnum.SPECIFIC_RULES) { - const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); - const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); - versionSpecifiers?.forEach((rule) => { - // Check that the requested rule was found - if (!installedRuleIds.has(rule.rule_id)) { - fetchErrors.push({ - error: new Error( - `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` - ), - item: rule, - }); - return; - } + if (mode === ModeEnum.SPECIFIC_RULES) { + const installedRuleIds = new Set(currentRules.map((rule) => rule.rule_id)); + const upgradeableRuleIds = new Set(rawUpgradeableRules.map(({ current }) => current.rule_id)); + versionSpecifiers?.forEach((rule) => { + // Check that the requested rule was found + if (!installedRuleIds.has(rule.rule_id)) { + fetchErrors.push({ + error: new Error( + `Rule with rule_id "${rule.rule_id}" and version "${rule.version}" not found` + ), + item: rule, + }); + return; + } - // Check that the requested rule is upgradeable - if (!upgradeableRuleIds.has(rule.rule_id)) { - skippedRules.push({ - rule_id: rule.rule_id, - reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, - }); - return; - } + // Check that the requested rule is upgradeable + if (!upgradeableRuleIds.has(rule.rule_id)) { + skippedRules.push({ + rule_id: rule.rule_id, + reason: SkipRuleUpgradeReasonEnum.RULE_UP_TO_DATE, + }); + return; + } - // Check that rule revisions match (no update slipped in since the user reviewed the list) - const currentRevision = currentRules.find( - (currentRule) => currentRule.rule_id === rule.rule_id - )?.revision; - if (rule.revision !== currentRevision) { - fetchErrors.push({ - error: new Error( - `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` - ), - item: rule, - }); - // Remove the rule from the list of upgradeable rules - if (upgradeableRules.has(rule.rule_id)) { - upgradeableRules.delete(rule.rule_id); + // Check that rule revisions match (no update slipped in since the user reviewed the list) + const currentRevision = currentRules.find( + (currentRule) => currentRule.rule_id === rule.rule_id + )?.revision; + if (rule.revision !== currentRevision) { + fetchErrors.push({ + error: new Error( + `Revision mismatch for rule_id ${rule.rule_id}: expected ${currentRevision}, got ${rule.revision}` + ), + item: rule, + }); + // Remove the rule from the list of upgradeable rules + if (upgradeableRules.has(rule.rule_id)) { + upgradeableRules.delete(rule.rule_id); + } } - } - }); - } + }); + } - return { - upgradeableRules: Array.from(upgradeableRules.values()), - fetchErrors, - skippedRules, - }; + return { + upgradeableRules: Array.from(upgradeableRules.values()), + fetchErrors, + skippedRules, + }; + }); }; diff --git a/x-pack/plugins/security_solution/server/utils/with_security_span.ts b/x-pack/plugins/security_solution/server/utils/with_security_span.ts index 58787dc45d09b..f9f78600cfb8d 100644 --- a/x-pack/plugins/security_solution/server/utils/with_security_span.ts +++ b/x-pack/plugins/security_solution/server/utils/with_security_span.ts @@ -6,7 +6,7 @@ */ import type { SpanOptions } from '@kbn/apm-utils'; import { withSpan } from '@kbn/apm-utils'; -import type agent from 'elastic-apm-node'; +import agent from 'elastic-apm-node'; import { APP_ID } from '../../common/constants'; type Span = Exclude; @@ -35,3 +35,16 @@ export const withSecuritySpan = ( }, cb ); + +export const withSecuritySpanSync = (name: string, fn: (span: Span | null) => T): T => { + const span = agent.startSpan(name, APP_ID); + + try { + const result = fn(span); + return result; + } finally { + if (span) { + span.end(); + } + } +};