mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Allow add_prepackaged_rules to change rule types (#128283)
This commit is contained in:
parent
bae7ee7f89
commit
2d12c94c2f
6 changed files with 156 additions and 70 deletions
|
@ -73,7 +73,7 @@ export const addPrepackedRulesRoute = (router: SecuritySolutionPluginRouter) =>
|
|||
);
|
||||
};
|
||||
|
||||
class PrepackagedRulesError extends Error {
|
||||
export class PrepackagedRulesError extends Error {
|
||||
public readonly statusCode: number;
|
||||
constructor(message: string, statusCode: number) {
|
||||
super(message);
|
||||
|
@ -147,10 +147,10 @@ export const createPrepackagedRules = async (
|
|||
await updatePrepackagedRules(
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
context.getSpaceId(),
|
||||
rulesToUpdate,
|
||||
signalsIndex,
|
||||
ruleRegistryEnabled
|
||||
ruleRegistryEnabled,
|
||||
context.getRuleExecutionLog()
|
||||
);
|
||||
|
||||
const prepackagedRulesOutput: PrePackagedRulesAndTimelinesSchema = {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
normalizeThresholdObject,
|
||||
} from '../../../../common/detection_engine/utils';
|
||||
import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions';
|
||||
import { SanitizedAlert } from '../../../../../alerting/common';
|
||||
import { AlertTypeParams, SanitizedAlert } from '../../../../../alerting/common';
|
||||
import {
|
||||
DEFAULT_INDICATOR_SOURCE_PATH,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
|
@ -20,7 +20,7 @@ import {
|
|||
} from '../../../../common/constants';
|
||||
import { CreateRulesOptions } from './types';
|
||||
import { addTags } from './add_tags';
|
||||
import { PartialFilter, RuleTypeParams } from '../types';
|
||||
import { PartialFilter } from '../types';
|
||||
import { transformToAlertThrottle, transformToNotifyWhen } from './utils';
|
||||
|
||||
export const createRules = async ({
|
||||
|
@ -76,8 +76,12 @@ export const createRules = async ({
|
|||
exceptionsList,
|
||||
actions,
|
||||
isRuleRegistryEnabled,
|
||||
}: CreateRulesOptions): Promise<SanitizedAlert<RuleTypeParams>> => {
|
||||
const rule = await rulesClient.create<RuleTypeParams>({
|
||||
id,
|
||||
}: CreateRulesOptions): Promise<SanitizedAlert<AlertTypeParams>> => {
|
||||
const rule = await rulesClient.create<AlertTypeParams>({
|
||||
options: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
name,
|
||||
tags: addTags(tags, ruleId, immutable),
|
||||
|
|
|
@ -194,6 +194,7 @@ export interface CreateRulesOptions {
|
|||
actions: RuleAlertAction[];
|
||||
isRuleRegistryEnabled: boolean;
|
||||
namespace?: NamespaceOrUndefined;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface UpdateRulesOptions {
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getFindResultWithSingleHit } from '../routes/__mocks__/request_response
|
|||
import { updatePrepackagedRules } from './update_prepacked_rules';
|
||||
import { patchRules } from './patch_rules';
|
||||
import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock';
|
||||
import { ruleExecutionLogMock } from '../rule_execution_log/__mocks__';
|
||||
|
||||
jest.mock('./patch_rules');
|
||||
|
||||
|
@ -20,10 +21,12 @@ describe.each([
|
|||
])('updatePrepackagedRules - %s', (_, isRuleRegistryEnabled) => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
|
||||
let ruleExecutionLog: ReturnType<typeof ruleExecutionLogMock.forRoutes.create>;
|
||||
|
||||
beforeEach(() => {
|
||||
rulesClient = rulesClientMock.create();
|
||||
savedObjectsClient = savedObjectsClientMock.create();
|
||||
ruleExecutionLog = ruleExecutionLogMock.forRoutes.create();
|
||||
});
|
||||
|
||||
it('should omit actions and enabled when calling patchRules', async () => {
|
||||
|
@ -42,10 +45,10 @@ describe.each([
|
|||
await updatePrepackagedRules(
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
'default',
|
||||
[{ ...prepackagedRule, actions }],
|
||||
outputIndex,
|
||||
isRuleRegistryEnabled
|
||||
isRuleRegistryEnabled,
|
||||
ruleExecutionLog
|
||||
);
|
||||
|
||||
expect(patchRules).toHaveBeenCalledWith(
|
||||
|
@ -73,10 +76,10 @@ describe.each([
|
|||
await updatePrepackagedRules(
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
'default',
|
||||
[{ ...prepackagedRule, ...updatedThreatParams }],
|
||||
'output-index',
|
||||
isRuleRegistryEnabled
|
||||
isRuleRegistryEnabled,
|
||||
ruleExecutionLog
|
||||
);
|
||||
|
||||
expect(patchRules).toHaveBeenCalledWith(
|
||||
|
|
|
@ -15,6 +15,11 @@ import { readRules } from './read_rules';
|
|||
import { PartialFilter } from '../types';
|
||||
import { RuleParams } from '../schemas/rule_schemas';
|
||||
import { legacyMigrate } from './utils';
|
||||
import { deleteRules } from './delete_rules';
|
||||
import { PrepackagedRulesError } from '../routes/rules/add_prepackaged_rules_route';
|
||||
import { IRuleExecutionLogForRoutes } from '../rule_execution_log';
|
||||
import { createRules } from './create_rules';
|
||||
import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions';
|
||||
|
||||
/**
|
||||
* Updates the prepackaged rules given a set of rules and output index.
|
||||
|
@ -28,20 +33,20 @@ import { legacyMigrate } from './utils';
|
|||
export const updatePrepackagedRules = async (
|
||||
rulesClient: RulesClient,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
spaceId: string,
|
||||
rules: AddPrepackagedRulesSchemaDecoded[],
|
||||
outputIndex: string,
|
||||
isRuleRegistryEnabled: boolean
|
||||
isRuleRegistryEnabled: boolean,
|
||||
ruleExecutionLog: IRuleExecutionLogForRoutes
|
||||
): Promise<void> => {
|
||||
const ruleChunks = chunk(MAX_RULES_TO_UPDATE_IN_PARALLEL, rules);
|
||||
for (const ruleChunk of ruleChunks) {
|
||||
const rulePromises = createPromises(
|
||||
rulesClient,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
ruleChunk,
|
||||
outputIndex,
|
||||
isRuleRegistryEnabled
|
||||
isRuleRegistryEnabled,
|
||||
ruleExecutionLog
|
||||
);
|
||||
await Promise.all(rulePromises);
|
||||
}
|
||||
|
@ -58,10 +63,10 @@ export const updatePrepackagedRules = async (
|
|||
export const createPromises = (
|
||||
rulesClient: RulesClient,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
spaceId: string,
|
||||
rules: AddPrepackagedRulesSchemaDecoded[],
|
||||
outputIndex: string,
|
||||
isRuleRegistryEnabled: boolean
|
||||
isRuleRegistryEnabled: boolean,
|
||||
ruleExecutionLog: IRuleExecutionLogForRoutes
|
||||
): Array<Promise<PartialAlert<RuleParams> | null>> => {
|
||||
return rules.map(async (rule) => {
|
||||
const {
|
||||
|
@ -128,58 +133,130 @@ export const createPromises = (
|
|||
rule: existingRule,
|
||||
});
|
||||
|
||||
// Note: we do not pass down enabled as we do not want to suddenly disable
|
||||
// or enable rules on the user when they were not expecting it if a rule updates
|
||||
return patchRules({
|
||||
rulesClient,
|
||||
author,
|
||||
buildingBlockType,
|
||||
description,
|
||||
eventCategoryOverride,
|
||||
falsePositives,
|
||||
from,
|
||||
query,
|
||||
language,
|
||||
license,
|
||||
outputIndex,
|
||||
rule: migratedRule,
|
||||
savedId,
|
||||
meta,
|
||||
filters,
|
||||
index,
|
||||
interval,
|
||||
maxSignals,
|
||||
riskScore,
|
||||
riskScoreMapping,
|
||||
ruleNameOverride,
|
||||
name,
|
||||
severity,
|
||||
severityMapping,
|
||||
tags,
|
||||
timestampOverride,
|
||||
to,
|
||||
type,
|
||||
threat,
|
||||
threshold,
|
||||
threatFilters,
|
||||
threatIndex,
|
||||
threatIndicatorPath,
|
||||
threatQuery,
|
||||
threatMapping,
|
||||
threatLanguage,
|
||||
concurrentSearches,
|
||||
itemsPerSearch,
|
||||
references,
|
||||
version,
|
||||
note,
|
||||
anomalyThreshold,
|
||||
enabled: undefined,
|
||||
timelineId,
|
||||
timelineTitle,
|
||||
machineLearningJobId,
|
||||
exceptionsList,
|
||||
throttle,
|
||||
actions: undefined,
|
||||
});
|
||||
if (!migratedRule) {
|
||||
throw new PrepackagedRulesError(`Failed to find rule ${ruleId}`, 500);
|
||||
}
|
||||
|
||||
// If we're trying to change the type of a prepackaged rule, we need to delete the old one
|
||||
// and replace it with the new rule, keeping the enabled setting, actions, throttle, id,
|
||||
// and exception lists from the old rule
|
||||
if (type !== migratedRule.params.type) {
|
||||
await deleteRules({
|
||||
ruleId: migratedRule.id,
|
||||
rulesClient,
|
||||
ruleExecutionLog,
|
||||
});
|
||||
|
||||
return (await createRules({
|
||||
id: migratedRule.id,
|
||||
isRuleRegistryEnabled,
|
||||
rulesClient,
|
||||
anomalyThreshold,
|
||||
author,
|
||||
buildingBlockType,
|
||||
description,
|
||||
enabled: migratedRule.enabled, // Enabled comes from existing rule
|
||||
eventCategoryOverride,
|
||||
falsePositives,
|
||||
from,
|
||||
immutable: true, // At the moment we force all prepackaged rules to be immutable
|
||||
query,
|
||||
language,
|
||||
license,
|
||||
machineLearningJobId,
|
||||
outputIndex,
|
||||
savedId,
|
||||
timelineId,
|
||||
timelineTitle,
|
||||
meta,
|
||||
filters,
|
||||
ruleId,
|
||||
index,
|
||||
interval,
|
||||
maxSignals,
|
||||
riskScore,
|
||||
riskScoreMapping,
|
||||
ruleNameOverride,
|
||||
name,
|
||||
severity,
|
||||
severityMapping,
|
||||
tags,
|
||||
to,
|
||||
type,
|
||||
threat,
|
||||
threatFilters,
|
||||
threatMapping,
|
||||
threatLanguage,
|
||||
concurrentSearches,
|
||||
itemsPerSearch,
|
||||
threatQuery,
|
||||
threatIndex,
|
||||
threatIndicatorPath,
|
||||
threshold,
|
||||
throttle: migratedRule.throttle, // Throttle comes from the existing rule
|
||||
timestampOverride,
|
||||
references,
|
||||
note,
|
||||
version,
|
||||
// The exceptions list passed in to this function has already been merged with the exceptions list of
|
||||
// the existing rule
|
||||
exceptionsList,
|
||||
actions: migratedRule.actions.map(transformAlertToRuleAction), // Actions come from the existing rule
|
||||
})) as PartialAlert<RuleParams>; // TODO: Replace AddPrepackagedRulesSchema with type specific rules schema so we can clean up these types
|
||||
} else {
|
||||
// Note: we do not pass down enabled as we do not want to suddenly disable
|
||||
// or enable rules on the user when they were not expecting it if a rule updates
|
||||
return patchRules({
|
||||
rulesClient,
|
||||
author,
|
||||
buildingBlockType,
|
||||
description,
|
||||
eventCategoryOverride,
|
||||
falsePositives,
|
||||
from,
|
||||
query,
|
||||
language,
|
||||
license,
|
||||
outputIndex,
|
||||
rule: migratedRule,
|
||||
savedId,
|
||||
meta,
|
||||
filters,
|
||||
index,
|
||||
interval,
|
||||
maxSignals,
|
||||
riskScore,
|
||||
riskScoreMapping,
|
||||
ruleNameOverride,
|
||||
name,
|
||||
severity,
|
||||
severityMapping,
|
||||
tags,
|
||||
timestampOverride,
|
||||
to,
|
||||
type,
|
||||
threat,
|
||||
threshold,
|
||||
threatFilters,
|
||||
threatIndex,
|
||||
threatIndicatorPath,
|
||||
threatQuery,
|
||||
threatMapping,
|
||||
threatLanguage,
|
||||
concurrentSearches,
|
||||
itemsPerSearch,
|
||||
references,
|
||||
version,
|
||||
note,
|
||||
anomalyThreshold,
|
||||
enabled: undefined,
|
||||
timelineId,
|
||||
timelineTitle,
|
||||
machineLearningJobId,
|
||||
exceptionsList,
|
||||
throttle,
|
||||
actions: undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -80,6 +80,7 @@ export interface RuleTypeParams extends AlertTypeParams {
|
|||
query?: QueryOrUndefined;
|
||||
filters?: unknown[];
|
||||
maxSignals: MaxSignals;
|
||||
namespace?: string;
|
||||
riskScore: RiskScore;
|
||||
riskScoreMapping: RiskScoreMappingOrUndefined;
|
||||
ruleNameOverride: RuleNameOverrideOrUndefined;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue