Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] /upgrade/_perform performance testing #197898

Merged
merged 10 commits into from
Nov 6, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -36,77 +37,82 @@ export const createModifiedPrebuiltRuleAssets = ({
upgradeableRules,
requestBody,
}: CreateModifiedPrebuiltRuleAssetsProps) => {
const { pick_version: globalPickVersion = PickVersionValuesEnum.MERGED, mode } = requestBody;

const { modifiedPrebuiltRuleAssets, processingErrors } = upgradeableRules.reduce<ProcessedRules>(
(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>(
(processedRules, upgradeableRule) => {
const targetRuleType = upgradeableRule.target.type;
Comment on lines +43 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a follow-up PR it would be great to try to refactor this function - the nesting here is already beyond manageable/readable.

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<PromisePoolError<{ rule_id: string }, Error>> = [];
const skippedRules: SkippedRuleUpgrade[] = [];
return withSecuritySpanSync(getUpgradeableRules.name, () => {
const upgradeableRules = new Map(
rawUpgradeableRules.map((_rule) => [_rule.current.rule_id, _rule])
);
const fetchErrors: Array<PromisePoolError<{ rule_id: string }, Error>> = [];
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,
};
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof agent.currentSpan, undefined | null>;
Expand Down Expand Up @@ -35,3 +35,16 @@ export const withSecuritySpan = <T>(
},
cb
);

export const withSecuritySpanSync = <T>(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();
}
}
};