From 62554c7f563bbc0cd170f84139ea1b34140c8c55 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Tue, 2 Jun 2020 11:56:55 -0500 Subject: [PATCH] [SIEM][Detections] Allow synchronous rule actions to be updated via PATCH (#67914) * Update synchronous actions in patchRules This method was typed to accept actions, but it was not doing anything with them. This was mainly a "bug by omission" so I'm simply adding unit tests for regression purposes. * Allow synchronous actions to be patched either individually or in bulk Now that patchRules uses this field, we simply need to pass it. Co-authored-by: Garrett Spong Co-authored-by: Elastic Machine # Conflicts: # x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts --- .../routes/rules/patch_rules_bulk_route.ts | 1 + .../routes/rules/patch_rules_route.ts | 1 + .../rules/patch_rules.test.ts | 78 +++++++++++++++++++ .../lib/detection_engine/rules/patch_rules.ts | 4 +- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index cc4d5e03500a4..f3e3124fb2297 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -122,6 +122,7 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts index ae23e0efc857d..2a1ac9862e7d0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -118,6 +118,7 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { version, anomalyThreshold, machineLearningJobId, + actions, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts index a42500223012e..cbe8af8561e3b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.test.ts @@ -91,4 +91,82 @@ describe('patchRules', () => { }) ); }); + + describe('regression tests', () => { + it("updates the rule's actions if provided", async () => { + const existingRule = getResult(); + + const action = { + action_type_id: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + actions: [action], + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + + it('does not update actions if none are specified', async () => { + const existingRule = { + ...getResult(), + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }; + + await patchRules({ + alertsClient, + savedObjectsClient, + rule: existingRule, + }); + + expect(alertsClient.update).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + actions: [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', + }, + group: 'default', + }, + ], + }), + }) + ); + }); + }); }); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index 6dfb72532afbb..c57eae0bef0f1 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -6,6 +6,7 @@ import { defaults } from 'lodash/fp'; import { PartialAlert } from '../../../../../alerting/server'; +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PatchRuleParams } from './types'; import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval } from './utils'; @@ -44,6 +45,7 @@ export const patchRules = async ({ exceptions_list, anomalyThreshold, machineLearningJobId, + actions, }: PatchRuleParams): Promise => { if (rule == null) { return null; @@ -121,7 +123,7 @@ export const patchRules = async ({ schedule: { interval: calculateInterval(interval, rule.schedule.interval), }, - actions: rule.actions, + actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, params: nextParams, }, });