mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[RAM][SECURITYSOLUTION][ALERTS] - Integrate Alert summary inside of security solution rule page (#154990)
## Summary [Main ticket](https://github.com/elastic/kibana/issues/151916) This PR dependant on [these changes](https://github.com/elastic/kibana/pull/153101) These changes cover next two tickets: - [RAM][SECURITYSOLUTION][ALERTS] - Integrate per-action frequency field in security solution APIs #154532 - [RAM][SECURITYSOLUTION][ALERTS] - Integrate per-action frequency UI in security solution #154534 With this PR we will integrate per-action `frequency` field which already works within alert framework and will update security solution UI to incorporate the possibility to select "summary" vs "for each alert" type of actions.  ## NOTES: - There will be no more "Perform no actions" option which mutes all the actions of the rule. For back compatibility, we need to show that rule is muted in the UI cc @peluja1012 @ARWNightingale - The ability to generate per-alert action will be done as part of https://github.com/elastic/kibana/issues/153611 ## Technical Notes: Here are the overview of the conversions and transformations that we are going to do as part of these changes for devs who are going to review. On rule **create**/**read**/**update**/**patch**: - We always gonna set rule level `throttle` to `undefined` from now on - If each action has `frequency` attribute set, then we just use those values - If actions do not have `frequency` attribute set (or for some reason there is a mix of actions with some of them having `frequency` attribute and some not), then we transform rule level `throttle` into `frequency` and set it for each action in the rule On rule **bulk editing**: - We always gonna set rule level `throttle` to `undefined` - If each action has `frequency` attribute set, then we just use those values - If actions do not have `frequency` attribute set, then we transform rule level `throttle` into `frequency` and set it for each action in the rule - If user passed only `throttle` attribute with empty actions (`actions = []`), this will only remove all actions from the rule This will bring breaking changes which we agreed on with the Advanced Correlation Group cc @XavierM @vitaliidm @peluja1012 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Maxim Palenov <maxim.palenov@elastic.co>
This commit is contained in:
parent
118daf778b
commit
68719bdb07
81 changed files with 1720 additions and 672 deletions
|
@ -20,6 +20,7 @@ export * from './src/default_severity_mapping_array';
|
|||
export * from './src/default_threat_array';
|
||||
export * from './src/default_to_string';
|
||||
export * from './src/default_uuid';
|
||||
export * from './src/frequency';
|
||||
export * from './src/language';
|
||||
export * from './src/machine_learning_job_id';
|
||||
export * from './src/max_signals';
|
||||
|
|
|
@ -9,6 +9,7 @@ import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';
|
|||
|
||||
import * as t from 'io-ts';
|
||||
import { saved_object_attributes } from '../saved_object_attributes';
|
||||
import { RuleActionFrequency } from '../frequency';
|
||||
|
||||
export type RuleActionGroup = t.TypeOf<typeof RuleActionGroup>;
|
||||
export const RuleActionGroup = t.string;
|
||||
|
@ -94,7 +95,11 @@ export const RuleAction = t.exact(
|
|||
action_type_id: RuleActionTypeId,
|
||||
params: RuleActionParams,
|
||||
}),
|
||||
t.partial({ uuid: RuleActionUuid, alerts_filter: RuleActionAlertsFilter }),
|
||||
t.partial({
|
||||
uuid: RuleActionUuid,
|
||||
alerts_filter: RuleActionAlertsFilter,
|
||||
frequency: RuleActionFrequency,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
|
@ -110,7 +115,11 @@ export const RuleActionCamel = t.exact(
|
|||
actionTypeId: RuleActionTypeId,
|
||||
params: RuleActionParams,
|
||||
}),
|
||||
t.partial({ uuid: RuleActionUuid, alertsFilter: RuleActionAlertsFilter }),
|
||||
t.partial({
|
||||
uuid: RuleActionUuid,
|
||||
alertsFilter: RuleActionAlertsFilter,
|
||||
frequency: RuleActionFrequency,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import { RuleActionThrottle } from '../throttle';
|
||||
|
||||
/**
|
||||
* Action summary indicates whether we will send a summary notification about all the generate alerts or notification per individual alert
|
||||
*/
|
||||
export type RuleActionSummary = t.TypeOf<typeof RuleActionSummary>;
|
||||
export const RuleActionSummary = t.boolean;
|
||||
|
||||
/**
|
||||
* The condition for throttling the notification: `onActionGroupChange`, `onActiveAlert`, or `onThrottleInterval`
|
||||
*/
|
||||
export type RuleActionNotifyWhen = t.TypeOf<typeof RuleActionNotifyWhen>;
|
||||
export const RuleActionNotifyWhen = t.union([
|
||||
t.literal('onActionGroupChange'),
|
||||
t.literal('onActiveAlert'),
|
||||
t.literal('onThrottleInterval'),
|
||||
]);
|
||||
|
||||
/**
|
||||
* The action frequency defines when the action runs (for example, only on rule execution or at specific time intervals).
|
||||
*/
|
||||
export type RuleActionFrequency = t.TypeOf<typeof RuleActionFrequency>;
|
||||
export const RuleActionFrequency = t.type({
|
||||
summary: RuleActionSummary,
|
||||
notifyWhen: RuleActionNotifyWhen,
|
||||
throttle: t.union([RuleActionThrottle, t.null]),
|
||||
});
|
|
@ -13,5 +13,5 @@ export type RuleActionThrottle = t.TypeOf<typeof RuleActionThrottle>;
|
|||
export const RuleActionThrottle = t.union([
|
||||
t.literal('no_actions'),
|
||||
t.literal('rule'),
|
||||
TimeDuration({ allowedUnits: ['h', 'd'] }),
|
||||
TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }),
|
||||
]);
|
||||
|
|
|
@ -19,6 +19,17 @@ const ruleActionSchema = schema.object({
|
|||
id: schema.string(),
|
||||
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
|
||||
uuid: schema.maybe(schema.string()),
|
||||
frequency: schema.maybe(
|
||||
schema.object({
|
||||
summary: schema.boolean(),
|
||||
throttle: schema.nullable(schema.string()),
|
||||
notifyWhen: schema.oneOf([
|
||||
schema.literal('onActionGroupChange'),
|
||||
schema.literal('onActiveAlert'),
|
||||
schema.literal('onThrottleInterval'),
|
||||
]),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const operationsSchema = schema.arrayOf(
|
||||
|
|
|
@ -14,6 +14,7 @@ import { injectReferencesIntoActions } from '../../common';
|
|||
import { transformToNotifyWhen } from './transform_to_notify_when';
|
||||
import { transformFromLegacyActions } from './transform_legacy_actions';
|
||||
import { LegacyIRuleActionsAttributes, legacyRuleActionsSavedObjectType } from './types';
|
||||
import { transformToAlertThrottle } from './transform_to_alert_throttle';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -136,7 +137,9 @@ export const formatLegacyActions = async <T extends Rule>(
|
|||
return {
|
||||
...rule,
|
||||
actions: [...rule.actions, ...legacyRuleActions],
|
||||
throttle: (legacyRuleActions.length ? ruleThrottle : rule.throttle) ?? 'no_actions',
|
||||
throttle: transformToAlertThrottle(
|
||||
(legacyRuleActions.length ? ruleThrottle : rule.throttle) ?? 'no_actions'
|
||||
),
|
||||
notifyWhen: transformToNotifyWhen(ruleThrottle),
|
||||
// muteAll property is disregarded in further rule processing in Security Solution when legacy actions are present.
|
||||
// So it should be safe to set it as false, so it won't be displayed to user as w/o actions see transformFromAlertThrottle method
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('transformFromLegacyActions', () => {
|
|||
(transformToNotifyWhen as jest.Mock).mockReturnValueOnce(null);
|
||||
const actions = transformFromLegacyActions(legacyActionsAttr, references);
|
||||
|
||||
expect(actions[0].frequency?.notifyWhen).toBe('onThrottleInterval');
|
||||
expect(actions[0].frequency?.notifyWhen).toBe('onActiveAlert');
|
||||
});
|
||||
|
||||
it('should return transformed legacy actions', () => {
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { SavedObjectReference } from '@kbn/core/server';
|
|||
import { RawRuleAction } from '../../../types';
|
||||
import { transformToNotifyWhen } from './transform_to_notify_when';
|
||||
import { LegacyIRuleActionsAttributes } from './types';
|
||||
import { transformToAlertThrottle } from './transform_to_alert_throttle';
|
||||
|
||||
/**
|
||||
* @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function
|
||||
|
@ -50,8 +51,8 @@ export const transformFromLegacyActions = (
|
|||
actionTypeId,
|
||||
frequency: {
|
||||
summary: true,
|
||||
notifyWhen: transformToNotifyWhen(legacyActionsAttr.ruleThrottle) ?? 'onThrottleInterval',
|
||||
throttle: legacyActionsAttr.ruleThrottle,
|
||||
notifyWhen: transformToNotifyWhen(legacyActionsAttr.ruleThrottle) ?? 'onActiveAlert',
|
||||
throttle: transformToAlertThrottle(legacyActionsAttr.ruleThrottle),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { transformToAlertThrottle } from './transform_to_alert_throttle';
|
||||
|
||||
describe('transformToAlertThrottle', () => {
|
||||
it('should return null when throttle is null OR no_actions', () => {
|
||||
expect(transformToAlertThrottle(null)).toBeNull();
|
||||
expect(transformToAlertThrottle('rule')).toBeNull();
|
||||
expect(transformToAlertThrottle('no_actions')).toBeNull();
|
||||
});
|
||||
it('should return same value for other throttle values', () => {
|
||||
expect(transformToAlertThrottle('1h')).toBe('1h');
|
||||
expect(transformToAlertThrottle('1m')).toBe('1m');
|
||||
expect(transformToAlertThrottle('1d')).toBe('1d');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given a throttle from a "security_solution" rule this will transform it into an "alerting" "throttle"
|
||||
* on their saved object.
|
||||
* @params throttle The throttle from a "security_solution" rule
|
||||
* @returns The "alerting" throttle
|
||||
*/
|
||||
export const transformToAlertThrottle = (throttle: string | null | undefined): string | null => {
|
||||
if (throttle == null || throttle === 'rule' || throttle === 'no_actions') {
|
||||
return null;
|
||||
} else {
|
||||
return throttle;
|
||||
}
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import pMap from 'p-map';
|
||||
import Boom from '@hapi/boom';
|
||||
import { cloneDeep, omit } from 'lodash';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { KueryNode, nodeBuilder } from '@kbn/es-query';
|
||||
import {
|
||||
|
@ -638,19 +638,6 @@ async function getUpdatedAttributesFromOperations(
|
|||
isAttributesUpdateSkipped = false;
|
||||
}
|
||||
|
||||
// TODO https://github.com/elastic/kibana/issues/148414
|
||||
// If any action-level frequencies get pushed into a SIEM rule, strip their frequencies
|
||||
const firstFrequency = updatedOperation.value.find(
|
||||
(action) => action?.frequency
|
||||
)?.frequency;
|
||||
if (rule.attributes.consumer === AlertConsumers.SIEM && firstFrequency) {
|
||||
ruleActions.actions = ruleActions.actions.map((action) => omit(action, 'frequency'));
|
||||
if (!attributes.notifyWhen) {
|
||||
attributes.notifyWhen = firstFrequency.notifyWhen;
|
||||
attributes.throttle = firstFrequency.throttle;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'snoozeSchedule': {
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
*/
|
||||
import Semver from 'semver';
|
||||
import Boom from '@hapi/boom';
|
||||
import { omit } from 'lodash';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { SavedObjectsUtils } from '@kbn/core/server';
|
||||
import { withSpan } from '@kbn/apm-utils';
|
||||
import { parseDuration } from '../../../common/parse_duration';
|
||||
|
@ -111,17 +109,6 @@ export async function create<Params extends RuleTypeParams = never>(
|
|||
throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`);
|
||||
}
|
||||
|
||||
// TODO https://github.com/elastic/kibana/issues/148414
|
||||
// If any action-level frequencies get pushed into a SIEM rule, strip their frequencies
|
||||
const firstFrequency = data.actions.find((action) => action?.frequency)?.frequency;
|
||||
if (data.consumer === AlertConsumers.SIEM && firstFrequency) {
|
||||
data.actions = data.actions.map((action) => omit(action, 'frequency'));
|
||||
if (!data.notifyWhen) {
|
||||
data.notifyWhen = firstFrequency.notifyWhen;
|
||||
data.throttle = firstFrequency.throttle;
|
||||
}
|
||||
}
|
||||
|
||||
await withSpan({ name: 'validateActions', type: 'rules' }, () =>
|
||||
validateActions(context, ruleType, data, allowMissingConnectorSecrets)
|
||||
);
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { isEqual, omit } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import { SavedObject } from '@kbn/core/server';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import type { ShouldIncrementRevision } from './bulk_edit';
|
||||
import {
|
||||
PartialRule,
|
||||
|
@ -186,17 +185,6 @@ async function updateAlert<Params extends RuleTypeParams>(
|
|||
|
||||
const ruleType = context.ruleTypeRegistry.get(attributes.alertTypeId);
|
||||
|
||||
// TODO https://github.com/elastic/kibana/issues/148414
|
||||
// If any action-level frequencies get pushed into a SIEM rule, strip their frequencies
|
||||
const firstFrequency = data.actions.find((action) => action?.frequency)?.frequency;
|
||||
if (attributes.consumer === AlertConsumers.SIEM && firstFrequency) {
|
||||
data.actions = data.actions.map((action) => omit(action, 'frequency'));
|
||||
if (!attributes.notifyWhen) {
|
||||
attributes.notifyWhen = firstFrequency.notifyWhen;
|
||||
attributes.throttle = firstFrequency.throttle;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
const validatedAlertTypeParams = validateRuleTypeParams(data.params, ruleType.validate.params);
|
||||
await validateActions(context, ruleType, data, allowMissingConnectorSecrets);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
|
||||
|
||||
/**
|
||||
* as const
|
||||
*
|
||||
|
@ -377,9 +379,18 @@ export const ML_GROUP_ID = 'security' as const;
|
|||
export const LEGACY_ML_GROUP_ID = 'siem' as const;
|
||||
export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID] as const;
|
||||
|
||||
/**
|
||||
* Rule Actions
|
||||
*/
|
||||
export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions' as const;
|
||||
export const NOTIFICATION_THROTTLE_RULE = 'rule' as const;
|
||||
|
||||
export const NOTIFICATION_DEFAULT_FREQUENCY = {
|
||||
notifyWhen: RuleNotifyWhen.ACTIVE,
|
||||
throttle: null,
|
||||
summary: true,
|
||||
};
|
||||
|
||||
export const showAllOthersBucket: string[] = [
|
||||
'destination.ip',
|
||||
'event.action',
|
||||
|
|
|
@ -515,28 +515,6 @@ describe('Perform bulk action request schema', () => {
|
|||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('invalid request: missing throttle in payload', () => {
|
||||
const payload = {
|
||||
query: 'name: test',
|
||||
action: BulkActionType.edit,
|
||||
[BulkActionType.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_rule_actions,
|
||||
value: {
|
||||
actions: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const message = retrieveValidationMessage(payload);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(
|
||||
expect.arrayContaining(['Invalid value "undefined" supplied to "edit,value,throttle"'])
|
||||
);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('invalid request: missing actions in payload', () => {
|
||||
const payload = {
|
||||
query: 'name: test',
|
||||
|
|
|
@ -9,6 +9,7 @@ import * as t from 'io-ts';
|
|||
|
||||
import { NonEmptyArray, TimeDuration } from '@kbn/securitysolution-io-ts-types';
|
||||
import {
|
||||
RuleActionFrequency,
|
||||
RuleActionGroup,
|
||||
RuleActionId,
|
||||
RuleActionParams,
|
||||
|
@ -96,11 +97,14 @@ const BulkActionEditPayloadTimeline = t.type({
|
|||
*/
|
||||
type NormalizedRuleAction = t.TypeOf<typeof NormalizedRuleAction>;
|
||||
const NormalizedRuleAction = t.exact(
|
||||
t.type({
|
||||
group: RuleActionGroup,
|
||||
id: RuleActionId,
|
||||
params: RuleActionParams,
|
||||
})
|
||||
t.intersection([
|
||||
t.type({
|
||||
group: RuleActionGroup,
|
||||
id: RuleActionId,
|
||||
params: RuleActionParams,
|
||||
}),
|
||||
t.partial({ frequency: RuleActionFrequency }),
|
||||
])
|
||||
);
|
||||
|
||||
export type BulkActionEditPayloadRuleActions = t.TypeOf<typeof BulkActionEditPayloadRuleActions>;
|
||||
|
@ -109,10 +113,12 @@ export const BulkActionEditPayloadRuleActions = t.type({
|
|||
t.literal(BulkActionEditType.add_rule_actions),
|
||||
t.literal(BulkActionEditType.set_rule_actions),
|
||||
]),
|
||||
value: t.type({
|
||||
throttle: ThrottleForBulkActions,
|
||||
actions: t.array(NormalizedRuleAction),
|
||||
}),
|
||||
value: t.intersection([
|
||||
t.partial({ throttle: ThrottleForBulkActions }),
|
||||
t.type({
|
||||
actions: t.array(NormalizedRuleAction),
|
||||
}),
|
||||
]),
|
||||
});
|
||||
|
||||
type BulkActionEditPayloadSchedule = t.TypeOf<typeof BulkActionEditPayloadSchedule>;
|
||||
|
|
|
@ -119,6 +119,8 @@ export const baseSchema = buildRuleSchemas({
|
|||
output_index: AlertsIndex,
|
||||
namespace: AlertsIndexNamespace,
|
||||
meta: RuleMetadata,
|
||||
// Throttle
|
||||
throttle: RuleActionThrottle,
|
||||
},
|
||||
defaultable: {
|
||||
// Main attributes
|
||||
|
@ -134,7 +136,6 @@ export const baseSchema = buildRuleSchemas({
|
|||
to: RuleIntervalTo,
|
||||
// Rule actions
|
||||
actions: RuleActionArray,
|
||||
throttle: RuleActionThrottle,
|
||||
// Rule exceptions
|
||||
exceptions_list: ExceptionListArray,
|
||||
// Misc attributes
|
||||
|
|
|
@ -16,6 +16,7 @@ export const transformRuleToAlertAction = ({
|
|||
action_type_id: actionTypeId,
|
||||
params,
|
||||
uuid,
|
||||
frequency,
|
||||
alerts_filter: alertsFilter,
|
||||
}: RuleAlertAction): RuleAction => ({
|
||||
group,
|
||||
|
@ -24,6 +25,7 @@ export const transformRuleToAlertAction = ({
|
|||
actionTypeId,
|
||||
...(alertsFilter && { alertsFilter }),
|
||||
...(uuid && { uuid }),
|
||||
...(frequency && { frequency }),
|
||||
});
|
||||
|
||||
export const transformAlertToRuleAction = ({
|
||||
|
@ -32,6 +34,7 @@ export const transformAlertToRuleAction = ({
|
|||
actionTypeId,
|
||||
params,
|
||||
uuid,
|
||||
frequency,
|
||||
alertsFilter,
|
||||
}: RuleAction): RuleAlertAction => ({
|
||||
group,
|
||||
|
@ -40,6 +43,7 @@ export const transformAlertToRuleAction = ({
|
|||
action_type_id: actionTypeId,
|
||||
...(alertsFilter && { alerts_filter: alertsFilter }),
|
||||
...(uuid && { uuid }),
|
||||
...(frequency && { frequency }),
|
||||
});
|
||||
|
||||
export const transformRuleToAlertResponseAction = ({
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { ROLES } from '../../../common/test';
|
||||
|
||||
import {
|
||||
|
@ -15,11 +16,18 @@ import {
|
|||
import { actionFormSelector } from '../../screens/common/rule_actions';
|
||||
|
||||
import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common';
|
||||
import type { RuleActionCustomFrequency } from '../../tasks/common/rule_actions';
|
||||
import {
|
||||
addSlackRuleAction,
|
||||
assertSlackRuleAction,
|
||||
addEmailConnectorAndRuleAction,
|
||||
assertEmailRuleAction,
|
||||
assertSelectedCustomFrequencyOption,
|
||||
assertSelectedPerRuleRunFrequencyOption,
|
||||
assertSelectedSummaryOfAlertsOption,
|
||||
pickCustomFrequencyOption,
|
||||
pickPerRuleRunFrequencyOption,
|
||||
pickSummaryOfAlertsOption,
|
||||
} from '../../tasks/common/rule_actions';
|
||||
import {
|
||||
waitForRulesTableToBeLoaded,
|
||||
|
@ -32,10 +40,8 @@ import {
|
|||
submitBulkEditForm,
|
||||
checkOverwriteRuleActionsCheckbox,
|
||||
openBulkEditRuleActionsForm,
|
||||
pickActionFrequency,
|
||||
openBulkActionsMenu,
|
||||
} from '../../tasks/rules_bulk_edit';
|
||||
import { assertSelectedActionFrequency } from '../../tasks/edit_rule';
|
||||
import { login, visitWithoutDateRange } from '../../tasks/login';
|
||||
import { esArchiverResetKibana } from '../../tasks/es_archiver';
|
||||
|
||||
|
@ -75,7 +81,7 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
esArchiverResetKibana();
|
||||
|
||||
createSlackConnector().then(({ body }) => {
|
||||
const actions = [
|
||||
const actions: RuleActionArray = [
|
||||
{
|
||||
id: body.id,
|
||||
action_type_id: '.slack',
|
||||
|
@ -83,6 +89,11 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
params: {
|
||||
message: expectedExistingSlackMessage,
|
||||
},
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -120,7 +131,10 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
});
|
||||
|
||||
it('Add a rule action to rules (existing connector)', () => {
|
||||
const expectedActionFrequency = 'Daily';
|
||||
const expectedActionFrequency: RuleActionCustomFrequency = {
|
||||
throttle: 1,
|
||||
throttleUnit: 'd',
|
||||
};
|
||||
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
|
||||
|
@ -131,8 +145,9 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
// ensure rule actions info callout displayed on the form
|
||||
cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible');
|
||||
|
||||
pickActionFrequency(expectedActionFrequency);
|
||||
addSlackRuleAction(expectedSlackMessage);
|
||||
pickSummaryOfAlertsOption();
|
||||
pickCustomFrequencyOption(expectedActionFrequency);
|
||||
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: expectedNumberOfRulesToBeEdited });
|
||||
|
@ -140,7 +155,8 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
// check if rule has been updated
|
||||
goToEditRuleActionsSettingsOf(ruleNameToAssert);
|
||||
|
||||
assertSelectedActionFrequency(expectedActionFrequency);
|
||||
assertSelectedSummaryOfAlertsOption();
|
||||
assertSelectedCustomFrequencyOption(expectedActionFrequency, 1);
|
||||
assertSlackRuleAction(expectedExistingSlackMessage, 0);
|
||||
assertSlackRuleAction(expectedSlackMessage, 1);
|
||||
// ensure there is no third action
|
||||
|
@ -148,16 +164,15 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
});
|
||||
|
||||
it('Overwrite rule actions in rules', () => {
|
||||
const expectedActionFrequency = 'On each rule execution';
|
||||
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
|
||||
// select both custom and prebuilt rules
|
||||
selectNumberOfRules(expectedNumberOfRulesToBeEdited);
|
||||
openBulkEditRuleActionsForm();
|
||||
|
||||
pickActionFrequency(expectedActionFrequency);
|
||||
addSlackRuleAction(expectedSlackMessage);
|
||||
pickSummaryOfAlertsOption();
|
||||
pickPerRuleRunFrequencyOption();
|
||||
|
||||
// check overwrite box, ensure warning is displayed
|
||||
checkOverwriteRuleActionsCheckbox();
|
||||
|
@ -171,22 +186,27 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
// check if rule has been updated
|
||||
goToEditRuleActionsSettingsOf(ruleNameToAssert);
|
||||
|
||||
assertSelectedActionFrequency(expectedActionFrequency);
|
||||
assertSelectedSummaryOfAlertsOption();
|
||||
assertSelectedPerRuleRunFrequencyOption();
|
||||
assertSlackRuleAction(expectedSlackMessage);
|
||||
// ensure existing action was overwritten
|
||||
cy.get(actionFormSelector(1)).should('not.exist');
|
||||
});
|
||||
|
||||
it('Add a rule action to rules (new connector)', () => {
|
||||
const expectedActionFrequency = 'Hourly';
|
||||
const expectedActionFrequency: RuleActionCustomFrequency = {
|
||||
throttle: 2,
|
||||
throttleUnit: 'h',
|
||||
};
|
||||
const expectedEmail = 'test@example.com';
|
||||
const expectedSubject = 'Subject';
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
openBulkEditRuleActionsForm();
|
||||
|
||||
pickActionFrequency(expectedActionFrequency);
|
||||
addEmailConnectorAndRuleAction(expectedEmail, expectedSubject);
|
||||
pickSummaryOfAlertsOption();
|
||||
pickCustomFrequencyOption(expectedActionFrequency);
|
||||
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
@ -194,7 +214,8 @@ describe.skip('Detection rules, bulk edit of rule actions', () => {
|
|||
// check if rule has been updated
|
||||
goToEditRuleActionsSettingsOf(ruleNameToAssert);
|
||||
|
||||
assertSelectedActionFrequency(expectedActionFrequency);
|
||||
assertSelectedSummaryOfAlertsOption();
|
||||
assertSelectedCustomFrequencyOption(expectedActionFrequency, 1);
|
||||
assertEmailRuleAction(expectedEmail, expectedSubject);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,10 +19,13 @@ import {
|
|||
RULE_SWITCH,
|
||||
SEVERITY,
|
||||
} from '../../screens/alerts_detection_rules';
|
||||
import {
|
||||
ACTIONS_NOTIFY_WHEN_BUTTON,
|
||||
ACTIONS_SUMMARY_BUTTON,
|
||||
} from '../../screens/common/rule_actions';
|
||||
import {
|
||||
ABOUT_CONTINUE_BTN,
|
||||
ABOUT_EDIT_BUTTON,
|
||||
ACTIONS_THROTTLE_INPUT,
|
||||
CUSTOM_QUERY_INPUT,
|
||||
DEFINE_CONTINUE_BUTTON,
|
||||
DEFINE_EDIT_BUTTON,
|
||||
|
@ -401,12 +404,11 @@ describe('Custom query rules', () => {
|
|||
|
||||
goToActionsStepTab();
|
||||
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions');
|
||||
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).select('Weekly');
|
||||
|
||||
addEmailConnectorAndRuleAction('test@example.com', 'Subject');
|
||||
|
||||
cy.get(ACTIONS_SUMMARY_BUTTON).should('have.text', 'Summary of alerts');
|
||||
cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).should('have.text', 'Per rule run');
|
||||
|
||||
goToAboutStepTab();
|
||||
cy.get(TAGS_CLEAR_BUTTON).click({ force: true });
|
||||
fillAboutRule(getEditedRule());
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Rule actions during detection rule creation', () => {
|
|||
});
|
||||
|
||||
const rule = getSimpleCustomQueryRule();
|
||||
const actions = { throttle: 'rule', connectors: [indexConnector] };
|
||||
const actions = { connectors: [indexConnector] };
|
||||
const index = actions.connectors[0].index;
|
||||
const initialNumberOfDocuments = 0;
|
||||
const expectedJson = JSON.parse(actions.connectors[0].document);
|
||||
|
|
|
@ -578,7 +578,6 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RuleResponse
|
|||
language: 'kuery',
|
||||
index: getIndexPatterns(),
|
||||
query,
|
||||
throttle: 'no_actions',
|
||||
actions: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -5,13 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
import type { Connectors } from './connector';
|
||||
|
||||
export type CreateRulePropsRewrites<CreateRuleProps> = Partial<Exclude<CreateRuleProps, 'type'>>;
|
||||
|
||||
export interface Actions {
|
||||
throttle: RuleActionThrottle;
|
||||
connectors: Connectors[];
|
||||
}
|
||||
|
|
|
@ -41,3 +41,20 @@ export const INDEX_SELECTOR = "[data-test-subj='.index-siem-ActionTypeSelectOpti
|
|||
|
||||
export const actionFormSelector = (position: number) =>
|
||||
`[data-test-subj="alertActionAccordion-${position}"]`;
|
||||
|
||||
export const ACTIONS_SUMMARY_BUTTON = '[data-test-subj="summaryOrPerRuleSelect"]';
|
||||
|
||||
export const ACTIONS_NOTIFY_WHEN_BUTTON = '[data-test-subj="notifyWhenSelect"]';
|
||||
|
||||
export const ACTIONS_NOTIFY_PER_RULE_RUN_BUTTON = '[data-test-subj="onActiveAlert"]';
|
||||
|
||||
export const ACTIONS_NOTIFY_CUSTOM_FREQUENCY_BUTTON = '[data-test-subj="onThrottleInterval"]';
|
||||
|
||||
export const ACTIONS_THROTTLE_INPUT = '[data-test-subj="throttleInput"]';
|
||||
|
||||
export const ACTIONS_THROTTLE_UNIT_INPUT = '[data-test-subj="throttleUnitInput"]';
|
||||
|
||||
export const ACTIONS_SUMMARY_ALERT_BUTTON = '[data-test-subj="actionNotifyWhen-option-summary"]';
|
||||
|
||||
export const ACTIONS_SUMMARY_FOR_EACH_ALERT_BUTTON =
|
||||
'[data-test-subj="actionNotifyWhen-option-for_each"]';
|
||||
|
|
|
@ -13,9 +13,6 @@ export const ABOUT_EDIT_TAB = '[data-test-subj="edit-rule-about-tab"]';
|
|||
|
||||
export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]';
|
||||
|
||||
export const ACTIONS_THROTTLE_INPUT =
|
||||
'[data-test-subj="stepRuleActions"] [data-test-subj="select"]';
|
||||
|
||||
export const ADD_FALSE_POSITIVE_BTN =
|
||||
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text';
|
||||
|
||||
|
|
|
@ -66,9 +66,6 @@ export const UPDATE_SCHEDULE_LOOKBACK_INPUT =
|
|||
|
||||
export const UPDATE_SCHEDULE_TIME_UNIT_SELECT = '[data-test-subj="timeType"]';
|
||||
|
||||
export const RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT =
|
||||
'[data-test-subj="bulkEditRulesRuleActionThrottle"] [data-test-subj="select"]';
|
||||
|
||||
export const RULES_BULK_EDIT_ACTIONS_INFO = '[data-test-subj="bulkEditRulesRuleActionInfo"]';
|
||||
|
||||
export const RULES_BULK_EDIT_ACTIONS_WARNING = '[data-test-subj="bulkEditRulesRuleActionsWarning"]';
|
||||
|
|
|
@ -22,6 +22,15 @@ import {
|
|||
EMAIL_CONNECTOR_PASSWORD_INPUT,
|
||||
FORM_VALIDATION_ERROR,
|
||||
JSON_EDITOR,
|
||||
ACTIONS_SUMMARY_BUTTON,
|
||||
ACTIONS_NOTIFY_WHEN_BUTTON,
|
||||
ACTIONS_THROTTLE_INPUT,
|
||||
ACTIONS_THROTTLE_UNIT_INPUT,
|
||||
ACTIONS_SUMMARY_ALERT_BUTTON,
|
||||
ACTIONS_SUMMARY_FOR_EACH_ALERT_BUTTON,
|
||||
ACTIONS_NOTIFY_CUSTOM_FREQUENCY_BUTTON,
|
||||
actionFormSelector,
|
||||
ACTIONS_NOTIFY_PER_RULE_RUN_BUTTON,
|
||||
} from '../../screens/common/rule_actions';
|
||||
import { COMBO_BOX_INPUT, COMBO_BOX_SELECTION } from '../../screens/common/controls';
|
||||
import type { EmailConnector, IndexConnector } from '../../objects/connector';
|
||||
|
@ -84,3 +93,79 @@ export const fillIndexConnectorForm = (connector: IndexConnector = getIndexConne
|
|||
parseSpecialCharSequences: false,
|
||||
});
|
||||
};
|
||||
|
||||
export interface RuleActionCustomFrequency {
|
||||
throttle?: number;
|
||||
throttleUnit?: 's' | 'm' | 'h' | 'd';
|
||||
}
|
||||
|
||||
export const pickSummaryOfAlertsOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_SUMMARY_BUTTON).click();
|
||||
});
|
||||
cy.get(ACTIONS_SUMMARY_ALERT_BUTTON).click();
|
||||
};
|
||||
export const pickForEachAlertOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_SUMMARY_BUTTON).click();
|
||||
});
|
||||
cy.get(ACTIONS_SUMMARY_FOR_EACH_ALERT_BUTTON).click();
|
||||
};
|
||||
|
||||
export const pickCustomFrequencyOption = (
|
||||
{ throttle = 1, throttleUnit = 'h' }: RuleActionCustomFrequency,
|
||||
index = 0
|
||||
) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).click();
|
||||
});
|
||||
cy.get(ACTIONS_NOTIFY_CUSTOM_FREQUENCY_BUTTON).click();
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).type(`{selectAll}${throttle}`);
|
||||
cy.get(ACTIONS_THROTTLE_UNIT_INPUT).select(throttleUnit);
|
||||
});
|
||||
};
|
||||
|
||||
export const pickPerRuleRunFrequencyOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).click();
|
||||
});
|
||||
cy.get(ACTIONS_NOTIFY_PER_RULE_RUN_BUTTON).click();
|
||||
};
|
||||
|
||||
export const assertSelectedSummaryOfAlertsOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_SUMMARY_BUTTON).should('have.text', 'Summary of alerts');
|
||||
});
|
||||
};
|
||||
|
||||
export const assertSelectedForEachAlertOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_SUMMARY_BUTTON).should('have.text', 'For each alert');
|
||||
});
|
||||
};
|
||||
|
||||
export const assertSelectedCustomFrequencyOption = (
|
||||
{ throttle = 1, throttleUnit = 'h' }: RuleActionCustomFrequency,
|
||||
index = 0
|
||||
) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).should('have.text', 'Custom frequency');
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).should('have.value', throttle);
|
||||
cy.get(ACTIONS_THROTTLE_UNIT_INPUT).should('have.value', throttleUnit);
|
||||
});
|
||||
};
|
||||
|
||||
export const assertSelectedPerRuleRunFrequencyOption = (index = 0) => {
|
||||
const form = cy.get(actionFormSelector(index));
|
||||
form.within(() => {
|
||||
cy.get(ACTIONS_NOTIFY_WHEN_BUTTON).should('have.text', 'Per rule run');
|
||||
});
|
||||
};
|
||||
|
|
|
@ -99,7 +99,6 @@ import {
|
|||
NEW_TERMS_HISTORY_SIZE,
|
||||
NEW_TERMS_HISTORY_TIME_TYPE,
|
||||
NEW_TERMS_INPUT_AREA,
|
||||
ACTIONS_THROTTLE_INPUT,
|
||||
CONTINUE_BUTTON,
|
||||
CREATE_WITHOUT_ENABLING_BTN,
|
||||
RULE_INDICES,
|
||||
|
@ -407,7 +406,6 @@ export const fillFrom = (from: RuleIntervalFrom = ruleFields.ruleIntervalFrom) =
|
|||
};
|
||||
|
||||
export const fillRuleAction = (actions: Actions) => {
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).select(actions.throttle);
|
||||
actions.connectors.forEach((connector) => {
|
||||
switch (connector.type) {
|
||||
case 'index':
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { BACK_TO_RULE_DETAILS, EDIT_SUBMIT_BUTTON } from '../screens/edit_rule';
|
||||
import { ACTIONS_THROTTLE_INPUT } from '../screens/create_new_rule';
|
||||
|
||||
export const saveEditedRule = () => {
|
||||
cy.get(EDIT_SUBMIT_BUTTON).should('exist').click({ force: true });
|
||||
|
@ -17,7 +16,3 @@ export const goBackToRuleDetails = () => {
|
|||
cy.get(BACK_TO_RULE_DETAILS).should('exist').click();
|
||||
cy.get(BACK_TO_RULE_DETAILS).should('not.exist');
|
||||
};
|
||||
|
||||
export const assertSelectedActionFrequency = (frequency: string) => {
|
||||
cy.get(ACTIONS_THROTTLE_INPUT).find('option:selected').should('have.text', frequency);
|
||||
};
|
||||
|
|
|
@ -39,7 +39,6 @@ import {
|
|||
UPDATE_SCHEDULE_LOOKBACK_INPUT,
|
||||
RULES_BULK_EDIT_SCHEDULES_WARNING,
|
||||
RULES_BULK_EDIT_OVERWRITE_ACTIONS_CHECKBOX,
|
||||
RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT,
|
||||
} from '../screens/rules_bulk_edit';
|
||||
import { SCHEDULE_DETAILS } from '../screens/rule_details';
|
||||
|
||||
|
@ -292,7 +291,3 @@ export const assertRuleScheduleValues = ({ interval, lookback }: RuleSchedule) =
|
|||
cy.get('dd').eq(1).should('contain.text', lookback);
|
||||
});
|
||||
};
|
||||
|
||||
export const pickActionFrequency = (frequency: string) => {
|
||||
cy.get(RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT).select(frequency);
|
||||
};
|
||||
|
|
|
@ -797,91 +797,6 @@ describe('helpers', () => {
|
|||
meta: {
|
||||
kibana_siem_app_url: 'http://localhost:5601/app/siem',
|
||||
},
|
||||
throttle: 'no_actions',
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('returns proper throttle value for no_actions', () => {
|
||||
const mockStepData: ActionsStepRule = {
|
||||
...mockData,
|
||||
throttle: 'no_actions',
|
||||
};
|
||||
const result = formatActionsStepData(mockStepData);
|
||||
const expected: ActionsStepRuleJson = {
|
||||
actions: [],
|
||||
enabled: false,
|
||||
meta: {
|
||||
kibana_siem_app_url: mockStepData.kibanaSiemAppUrl,
|
||||
},
|
||||
throttle: 'no_actions',
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('returns proper throttle value for rule', () => {
|
||||
const mockStepData: ActionsStepRule = {
|
||||
...mockData,
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'id',
|
||||
actionTypeId: 'actionTypeId',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = formatActionsStepData(mockStepData);
|
||||
const expected: ActionsStepRuleJson = {
|
||||
actions: [
|
||||
{
|
||||
group: mockStepData.actions[0].group,
|
||||
id: mockStepData.actions[0].id,
|
||||
action_type_id: mockStepData.actions[0].actionTypeId,
|
||||
params: mockStepData.actions[0].params,
|
||||
},
|
||||
],
|
||||
enabled: false,
|
||||
meta: {
|
||||
kibana_siem_app_url: mockStepData.kibanaSiemAppUrl,
|
||||
},
|
||||
throttle: 'rule',
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('returns proper throttle value for interval', () => {
|
||||
const mockStepData: ActionsStepRule = {
|
||||
...mockData,
|
||||
throttle: '1d',
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: 'id',
|
||||
actionTypeId: 'actionTypeId',
|
||||
params: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = formatActionsStepData(mockStepData);
|
||||
const expected: ActionsStepRuleJson = {
|
||||
actions: [
|
||||
{
|
||||
group: mockStepData.actions[0].group,
|
||||
id: mockStepData.actions[0].id,
|
||||
action_type_id: mockStepData.actions[0].actionTypeId,
|
||||
params: mockStepData.actions[0].params,
|
||||
},
|
||||
],
|
||||
enabled: false,
|
||||
meta: {
|
||||
kibana_siem_app_url: mockStepData.kibanaSiemAppUrl,
|
||||
},
|
||||
throttle: mockStepData.throttle,
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -913,7 +828,6 @@ describe('helpers', () => {
|
|||
meta: {
|
||||
kibana_siem_app_url: mockStepData.kibanaSiemAppUrl,
|
||||
},
|
||||
throttle: 'no_actions',
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
|
|
@ -25,7 +25,6 @@ import type {
|
|||
Type,
|
||||
} from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../common/constants';
|
||||
import { assertUnreachable } from '../../../../../common/utility_types';
|
||||
import {
|
||||
transformAlertToRuleAction,
|
||||
|
@ -563,19 +562,12 @@ export const formatAboutStepData = (
|
|||
};
|
||||
|
||||
export const formatActionsStepData = (actionsStepData: ActionsStepRule): ActionsStepRuleJson => {
|
||||
const {
|
||||
actions = [],
|
||||
responseActions,
|
||||
enabled,
|
||||
kibanaSiemAppUrl,
|
||||
throttle = NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
} = actionsStepData;
|
||||
const { actions = [], responseActions, enabled, kibanaSiemAppUrl } = actionsStepData;
|
||||
|
||||
return {
|
||||
actions: actions.map(transformAlertToRuleAction),
|
||||
response_actions: responseActions?.map(transformAlertToRuleResponseAction),
|
||||
enabled,
|
||||
throttle: actions.length ? throttle : NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
meta: {
|
||||
kibana_siem_app_url: kibanaSiemAppUrl,
|
||||
},
|
||||
|
|
|
@ -200,7 +200,6 @@ export const mockActionsStepRule = (enabled = false): ActionsStepRule => ({
|
|||
actions: [],
|
||||
kibanaSiemAppUrl: 'http://localhost:5601/app/siem',
|
||||
enabled,
|
||||
throttle: 'no_actions',
|
||||
});
|
||||
|
||||
export const mockDefineStepRule = (): DefineStepRule => ({
|
||||
|
|
|
@ -23,21 +23,13 @@ import {
|
|||
Field,
|
||||
} from '../../../../../../shared_imports';
|
||||
import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import type {
|
||||
BulkActionEditPayload,
|
||||
ThrottleForBulkActions,
|
||||
} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import { NOTIFICATION_THROTTLE_RULE } from '../../../../../../../common/constants';
|
||||
import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
|
||||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { bulkAddRuleActions as i18n } from '../translations';
|
||||
|
||||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
|
||||
import {
|
||||
ThrottleSelectField,
|
||||
THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS,
|
||||
} from '../../../../../../detections/components/rules/throttle_select_field';
|
||||
import { getAllActionMessageParams } from '../../../../../../detections/pages/detection_engine/rules/helpers';
|
||||
|
||||
import { RuleActionsField } from '../../../../../../detections/components/rules/rule_actions_field';
|
||||
|
@ -45,19 +37,16 @@ import { debouncedValidateRuleActionsField } from '../../../../../../detections/
|
|||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
type BulkActionsRuleAction = RuleAction & Required<Pick<RuleAction, 'frequency'>>;
|
||||
|
||||
export interface RuleActionsFormData {
|
||||
throttle: ThrottleForBulkActions;
|
||||
actions: RuleAction[];
|
||||
actions: BulkActionsRuleAction[];
|
||||
overwrite: boolean;
|
||||
}
|
||||
|
||||
const getFormSchema = (
|
||||
actionTypeRegistry: ActionTypeRegistryContract
|
||||
): FormSchema<RuleActionsFormData> => ({
|
||||
throttle: {
|
||||
label: i18n.THROTTLE_LABEL,
|
||||
helpText: i18n.THROTTLE_HELP_TEXT,
|
||||
},
|
||||
actions: {
|
||||
validations: [
|
||||
{
|
||||
|
@ -75,7 +64,6 @@ const getFormSchema = (
|
|||
});
|
||||
|
||||
const defaultFormData: RuleActionsFormData = {
|
||||
throttle: NOTIFICATION_THROTTLE_RULE,
|
||||
actions: [],
|
||||
overwrite: false,
|
||||
};
|
||||
|
@ -108,7 +96,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
return;
|
||||
}
|
||||
|
||||
const { actions = [], throttle: throttleToSubmit, overwrite: overwriteValue } = data;
|
||||
const { actions = [], overwrite: overwriteValue } = data;
|
||||
const editAction = overwriteValue
|
||||
? BulkActionEditType.set_rule_actions
|
||||
: BulkActionEditType.add_rule_actions;
|
||||
|
@ -117,23 +105,10 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
type: editAction,
|
||||
value: {
|
||||
actions: actions.map(({ actionTypeId, ...action }) => action),
|
||||
throttle: throttleToSubmit,
|
||||
},
|
||||
});
|
||||
}, [form, onConfirm]);
|
||||
|
||||
const throttleFieldComponentProps = useMemo(
|
||||
() => ({
|
||||
idAria: 'bulkEditRulesRuleActionThrottle',
|
||||
'data-test-subj': 'bulkEditRulesRuleActionThrottle',
|
||||
hasNoInitialSelection: false,
|
||||
euiFieldProps: {
|
||||
options: THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS,
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const messageVariables = useMemo(() => getAllActionMessageParams(), []);
|
||||
|
||||
return (
|
||||
|
@ -156,24 +131,11 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
}
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.actionFrequencyDetail"
|
||||
defaultMessage="The actions frequency you select below is applied to all actions (both new and existing) for all selected rules."
|
||||
/>
|
||||
</li>
|
||||
<li>{i18n.RULE_VARIABLES_DETAIL}</li>
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<UseField
|
||||
path="throttle"
|
||||
component={ThrottleSelectField}
|
||||
componentProps={throttleFieldComponentProps}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<UseField
|
||||
path="actions"
|
||||
component={RuleActionsField}
|
||||
|
|
|
@ -71,21 +71,6 @@ export const bulkAddRuleActions = {
|
|||
}
|
||||
),
|
||||
|
||||
THROTTLE_LABEL: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleLabel',
|
||||
{
|
||||
defaultMessage: 'Actions frequency',
|
||||
}
|
||||
),
|
||||
|
||||
THROTTLE_HELP_TEXT: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Select when automated actions should be performed if a rule evaluates as true.',
|
||||
}
|
||||
),
|
||||
|
||||
RULE_VARIABLES_DETAIL: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.ruleVariablesDetail',
|
||||
{
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
/* eslint-disable complexity */
|
||||
|
||||
import { omit } from 'lodash';
|
||||
import type { EuiContextMenuPanelDescriptor } from '@elastic/eui';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTextColor } from '@elastic/eui';
|
||||
import type { Toast } from '@kbn/core/public';
|
||||
|
@ -228,16 +227,6 @@ export const useBulkActions = ({
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: https://github.com/elastic/kibana/issues/148414
|
||||
// Strip frequency from actions to comply with Security Solution alert API
|
||||
if ('actions' in editPayload.value) {
|
||||
// `actions.frequency` is included in the payload from TriggersActionsUI ActionForm
|
||||
// but is not included in the type definition for the editPayload, because this type
|
||||
// definition comes from the Security Solution alert API
|
||||
// TODO https://github.com/elastic/kibana/issues/148414 fix this discrepancy
|
||||
editPayload.value.actions = editPayload.value.actions.map((a) => omit(a, 'frequency'));
|
||||
}
|
||||
|
||||
startTransaction({ name: BULK_RULE_ACTIONS.EDIT });
|
||||
|
||||
const hideWarningToast = () => {
|
||||
|
|
|
@ -50,7 +50,7 @@ export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkAc
|
|||
return [
|
||||
{
|
||||
type: editAction,
|
||||
value: { throttle: '1h', actions: [] },
|
||||
value: { actions: [] },
|
||||
},
|
||||
];
|
||||
case BulkActionEditType.set_schedule:
|
||||
|
|
|
@ -16,7 +16,6 @@ import type {
|
|||
ActionVariables,
|
||||
NotifyWhenSelectOptions,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
|
||||
import type {
|
||||
RuleAction,
|
||||
RuleActionAlertsFilterProperty,
|
||||
|
@ -24,6 +23,7 @@ import type {
|
|||
} from '@kbn/alerting-plugin/common';
|
||||
import { SecurityConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { NOTIFICATION_DEFAULT_FREQUENCY } from '../../../../../common/constants';
|
||||
import type { FieldHook } from '../../../../shared_imports';
|
||||
import { useFormContext } from '../../../../shared_imports';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
@ -33,12 +33,6 @@ import {
|
|||
FORM_ON_ACTIVE_ALERT_OPTION,
|
||||
} from './translations';
|
||||
|
||||
const DEFAULT_FREQUENCY = {
|
||||
notifyWhen: RuleNotifyWhen.ACTIVE,
|
||||
throttle: null,
|
||||
summary: true,
|
||||
};
|
||||
|
||||
const NOTIFY_WHEN_OPTIONS: NotifyWhenSelectOptions[] = [
|
||||
{
|
||||
isSummaryOption: true,
|
||||
|
@ -214,6 +208,23 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
|
|||
[field]
|
||||
);
|
||||
|
||||
const setActionFrequency = useCallback(
|
||||
(key: string, value: RuleActionParam, index: number) => {
|
||||
field.setValue((prevValue: RuleAction[]) => {
|
||||
const updatedActions = [...prevValue];
|
||||
updatedActions[index] = {
|
||||
...updatedActions[index],
|
||||
frequency: {
|
||||
...(updatedActions[index].frequency ?? NOTIFICATION_DEFAULT_FREQUENCY),
|
||||
[key]: value,
|
||||
},
|
||||
};
|
||||
return updatedActions;
|
||||
});
|
||||
},
|
||||
[field]
|
||||
);
|
||||
|
||||
const actionForm = useMemo(
|
||||
() =>
|
||||
getActionForm({
|
||||
|
@ -223,22 +234,22 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
|
|||
setActionIdByIndex,
|
||||
setActions: setAlertActionsProperty,
|
||||
setActionParamsProperty,
|
||||
setActionFrequencyProperty: () => {},
|
||||
setActionFrequencyProperty: setActionFrequency,
|
||||
setActionAlertsFilterProperty,
|
||||
featureId: SecurityConnectorFeatureId,
|
||||
defaultActionMessage: DEFAULT_ACTION_MESSAGE,
|
||||
defaultSummaryMessage: DEFAULT_ACTION_MESSAGE,
|
||||
hideActionHeader: true,
|
||||
hideNotifyWhen: true,
|
||||
hasSummary: true,
|
||||
notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS,
|
||||
defaultRuleFrequency: DEFAULT_FREQUENCY,
|
||||
defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
showActionAlertsFilter: true,
|
||||
}),
|
||||
[
|
||||
actions,
|
||||
getActionForm,
|
||||
messageVariables,
|
||||
setActionFrequency,
|
||||
setActionIdByIndex,
|
||||
setActionParamsProperty,
|
||||
setAlertActionsProperty,
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { debouncedValidateRuleActionsField } from '../../../containers/detection_engine/rules/validate_rule_actions_field';
|
||||
|
||||
|
@ -30,12 +28,4 @@ export const getSchema = ({
|
|||
responseActions: {},
|
||||
enabled: {},
|
||||
kibanaSiemAppUrl: {},
|
||||
throttle: {
|
||||
label: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel',
|
||||
{
|
||||
defaultMessage: 'Actions frequency',
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { findIndex } from 'lodash/fp';
|
||||
import type { FC } from 'react';
|
||||
import React, { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -29,20 +28,13 @@ import { ResponseActionsForm } from '../../../../detection_engine/rule_response_
|
|||
import type { RuleStepProps, ActionsStepRule } from '../../../pages/detection_engine/rules/types';
|
||||
import { RuleStep } from '../../../pages/detection_engine/rules/types';
|
||||
import { StepRuleDescription } from '../description_step';
|
||||
import { Form, UseField, useForm, useFormData } from '../../../../shared_imports';
|
||||
import { Form, UseField, useForm } from '../../../../shared_imports';
|
||||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
import {
|
||||
ThrottleSelectField,
|
||||
THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING,
|
||||
DEFAULT_THROTTLE_OPTION,
|
||||
} from '../throttle_select_field';
|
||||
import { RuleActionsField } from '../rule_actions_field';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { getSchema } from './get_schema';
|
||||
import * as I18n from './translations';
|
||||
import { APP_UI_ID } from '../../../../../common/constants';
|
||||
import { useManageCaseAction } from './use_manage_case_action';
|
||||
import { THROTTLE_FIELD_HELP_TEXT, THROTTLE_FIELD_HELP_TEXT_WHEN_QUERY } from './translations';
|
||||
|
||||
interface StepRuleActionsProps extends RuleStepProps {
|
||||
defaultValues?: ActionsStepRule | null;
|
||||
|
@ -55,23 +47,10 @@ export const stepActionsDefaultValue: ActionsStepRule = {
|
|||
actions: [],
|
||||
responseActions: [],
|
||||
kibanaSiemAppUrl: '',
|
||||
throttle: DEFAULT_THROTTLE_OPTION.value,
|
||||
};
|
||||
|
||||
const GhostFormField = () => <></>;
|
||||
|
||||
const getThrottleOptions = (throttle?: string | null) => {
|
||||
// Add support for throttle options set by the API
|
||||
if (
|
||||
throttle &&
|
||||
findIndex(['value', throttle], THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING) < 0
|
||||
) {
|
||||
return [...THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING, { value: throttle, text: throttle }];
|
||||
}
|
||||
|
||||
return THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING;
|
||||
};
|
||||
|
||||
const DisplayActionsHeader = () => {
|
||||
return (
|
||||
<>
|
||||
|
@ -99,7 +78,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
actionMessageParams,
|
||||
ruleType,
|
||||
}) => {
|
||||
const [isLoadingCaseAction] = useManageCaseAction();
|
||||
const {
|
||||
services: {
|
||||
application,
|
||||
|
@ -127,11 +105,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
schema,
|
||||
});
|
||||
const { getFields, getFormData, submit } = form;
|
||||
const [{ throttle: formThrottle }] = useFormData<ActionsStepRule>({
|
||||
form,
|
||||
watch: ['throttle'],
|
||||
});
|
||||
const throttle = formThrottle || initialState.throttle;
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(enabled: boolean) => {
|
||||
|
@ -163,44 +136,20 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
};
|
||||
}, [getData, setForm]);
|
||||
|
||||
const throttleOptions = useMemo(() => {
|
||||
return getThrottleOptions(throttle);
|
||||
}, [throttle]);
|
||||
|
||||
const throttleFieldComponentProps = useMemo(
|
||||
() => ({
|
||||
idAria: 'detectionEngineStepRuleActionsThrottle',
|
||||
isDisabled: isLoading,
|
||||
isLoading: isLoadingCaseAction,
|
||||
dataTestSubj: 'detectionEngineStepRuleActionsThrottle',
|
||||
hasNoInitialSelection: false,
|
||||
helpText: isQueryRule(ruleType)
|
||||
? THROTTLE_FIELD_HELP_TEXT_WHEN_QUERY
|
||||
: THROTTLE_FIELD_HELP_TEXT,
|
||||
euiFieldProps: {
|
||||
options: throttleOptions,
|
||||
},
|
||||
}),
|
||||
[isLoading, isLoadingCaseAction, ruleType, throttleOptions]
|
||||
);
|
||||
|
||||
const displayActionsOptions = useMemo(
|
||||
() =>
|
||||
throttle !== stepActionsDefaultValue.throttle ? (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<UseField
|
||||
path="actions"
|
||||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables: actionMessageParams,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<UseField path="actions" component={GhostFormField} />
|
||||
),
|
||||
[throttle, actionMessageParams]
|
||||
() => (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<UseField
|
||||
path="actions"
|
||||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables: actionMessageParams,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
[actionMessageParams]
|
||||
);
|
||||
const displayResponseActionsOptions = useMemo(() => {
|
||||
if (isQueryRule(ruleType)) {
|
||||
|
@ -217,11 +166,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
return application.capabilities.actions.show ? (
|
||||
<>
|
||||
<DisplayActionsHeader />
|
||||
<UseField
|
||||
path="throttle"
|
||||
component={ThrottleSelectField}
|
||||
componentProps={throttleFieldComponentProps}
|
||||
/>
|
||||
{displayActionsOptions}
|
||||
{responseActionsEnabled && displayResponseActionsOptions}
|
||||
|
||||
|
@ -231,14 +175,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
) : (
|
||||
<>
|
||||
<EuiText>{I18n.NO_ACTIONS_READ_PERMISSIONS}</EuiText>
|
||||
<UseField
|
||||
path="throttle"
|
||||
componentProps={throttleFieldComponentProps}
|
||||
component={GhostFormField}
|
||||
/>
|
||||
<UseField path="actions" component={GhostFormField} />
|
||||
<UseField path="kibanaSiemAppUrl" component={GhostFormField} />
|
||||
<UseField path="enabled" component={GhostFormField} />
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
|
@ -246,7 +182,6 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
|
|||
displayActionsOptions,
|
||||
displayResponseActionsOptions,
|
||||
responseActionsEnabled,
|
||||
throttleFieldComponentProps,
|
||||
]);
|
||||
|
||||
if (isReadOnlyView) {
|
||||
|
|
|
@ -28,19 +28,3 @@ export const NO_ACTIONS_READ_PERMISSIONS = i18n.translate(
|
|||
'Cannot create rule actions. You do not have "Read" permissions for the "Actions" plugin.',
|
||||
}
|
||||
);
|
||||
|
||||
export const THROTTLE_FIELD_HELP_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Select when automated actions should be performed if a rule evaluates as true.',
|
||||
}
|
||||
);
|
||||
|
||||
export const THROTTLE_FIELD_HELP_TEXT_WHEN_QUERY = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpTextWhenQuery',
|
||||
{
|
||||
defaultMessage:
|
||||
'Select when automated actions should be performed if a rule evaluates as true. This frequency does not apply to Response Actions.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,66 +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 { useEffect, useRef, useState } from 'react';
|
||||
import { getAllConnectorsUrl, getCreateConnectorUrl } from '@kbn/cases-plugin/common';
|
||||
import { convertArrayToCamelCase, KibanaServices } from '../../../../common/lib/kibana';
|
||||
|
||||
interface CaseAction {
|
||||
connectorTypeId: string;
|
||||
id: string;
|
||||
isPreconfigured: boolean;
|
||||
name: string;
|
||||
referencedByCount: number;
|
||||
}
|
||||
|
||||
const CASE_ACTION_NAME = 'Cases';
|
||||
|
||||
export const useManageCaseAction = () => {
|
||||
const hasInit = useRef(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const abortCtrl = new AbortController();
|
||||
const fetchActions = async () => {
|
||||
try {
|
||||
const actions = convertArrayToCamelCase(
|
||||
await KibanaServices.get().http.fetch<CaseAction[]>(getAllConnectorsUrl(), {
|
||||
method: 'GET',
|
||||
signal: abortCtrl.signal,
|
||||
})
|
||||
) as CaseAction[];
|
||||
|
||||
if (!actions.some((a) => a.connectorTypeId === '.case' && a.name === CASE_ACTION_NAME)) {
|
||||
await KibanaServices.get().http.post<CaseAction[]>(getCreateConnectorUrl(), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
connector_type_id: '.case',
|
||||
config: {},
|
||||
name: CASE_ACTION_NAME,
|
||||
secrets: {},
|
||||
}),
|
||||
signal: abortCtrl.signal,
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
} catch {
|
||||
setLoading(false);
|
||||
setHasError(true);
|
||||
}
|
||||
};
|
||||
if (hasInit.current) {
|
||||
hasInit.current = false;
|
||||
fetchActions();
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortCtrl.abort();
|
||||
};
|
||||
}, []);
|
||||
return [loading, hasError];
|
||||
};
|
|
@ -35,6 +35,7 @@ import type {
|
|||
ActionsStepRule,
|
||||
} from './types';
|
||||
import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock';
|
||||
import type { RuleAlertAction } from '../../../../../common/detection_engine/types';
|
||||
|
||||
describe('rule helpers', () => {
|
||||
moment.suppressDeprecationWarnings = true;
|
||||
|
@ -146,7 +147,6 @@ describe('rule helpers', () => {
|
|||
const scheduleRuleStepData = { from: '0s', interval: '5m' };
|
||||
const ruleActionsStepData = {
|
||||
enabled: true,
|
||||
throttle: 'no_actions',
|
||||
actions: [],
|
||||
responseActions: undefined,
|
||||
};
|
||||
|
@ -410,16 +410,22 @@ describe('rule helpers', () => {
|
|||
|
||||
describe('getActionsStepsData', () => {
|
||||
test('returns expected ActionsStepRule rule object', () => {
|
||||
const actions: RuleAlertAction[] = [
|
||||
{
|
||||
id: 'id',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: 'action_type_id',
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
},
|
||||
},
|
||||
];
|
||||
const mockedRule = {
|
||||
...mockRule('test-id'),
|
||||
actions: [
|
||||
{
|
||||
id: 'id',
|
||||
group: 'group',
|
||||
params: {},
|
||||
action_type_id: 'action_type_id',
|
||||
},
|
||||
],
|
||||
actions,
|
||||
};
|
||||
const result: ActionsStepRule = getActionsStepsData(mockedRule);
|
||||
const expected = {
|
||||
|
@ -429,11 +435,15 @@ describe('rule helpers', () => {
|
|||
group: 'group',
|
||||
params: {},
|
||||
actionTypeId: 'action_type_id',
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
},
|
||||
},
|
||||
],
|
||||
responseActions: undefined,
|
||||
enabled: mockedRule.enabled,
|
||||
throttle: 'no_actions',
|
||||
};
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
|
|
@ -76,12 +76,11 @@ export const getActionsStepsData = (
|
|||
response_actions?: ResponseAction[];
|
||||
}
|
||||
): ActionsStepRule => {
|
||||
const { enabled, throttle, meta, actions = [], response_actions: responseActions } = rule;
|
||||
const { enabled, meta, actions = [], response_actions: responseActions } = rule;
|
||||
|
||||
return {
|
||||
actions: actions?.map(transformRuleToAlertAction),
|
||||
responseActions: responseActions?.map(transformRuleToAlertResponseAction),
|
||||
throttle,
|
||||
kibanaSiemAppUrl: meta?.kibana_siem_app_url,
|
||||
enabled,
|
||||
};
|
||||
|
|
|
@ -194,7 +194,6 @@ export interface ActionsStepRule {
|
|||
responseActions?: RuleResponseAction[];
|
||||
enabled: boolean;
|
||||
kibanaSiemAppUrl?: string;
|
||||
throttle?: string | null;
|
||||
}
|
||||
|
||||
export interface DefineStepRuleJson {
|
||||
|
|
|
@ -65,7 +65,7 @@ export const getOutputRuleAlertForRest = (): RuleResponse => ({
|
|||
severity_mapping: [],
|
||||
updated_by: 'elastic',
|
||||
tags: [],
|
||||
throttle: 'no_actions',
|
||||
throttle: undefined,
|
||||
threat: getThreatMock(),
|
||||
exceptions_list: getListArrayMock(),
|
||||
filters: [
|
||||
|
|
|
@ -109,8 +109,6 @@ describe('duplicateRule', () => {
|
|||
consumer: rule.consumer,
|
||||
schedule: rule.schedule,
|
||||
actions: rule.actions,
|
||||
throttle: null, // TODO: fix?
|
||||
notifyWhen: null, // TODO: fix?
|
||||
enabled: false, // covered in a separate test
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ruleTypeMappings } from '@kbn/securitysolution-rules';
|
|||
import type { SanitizedRule } from '@kbn/alerting-plugin/common';
|
||||
import { SERVER_APP_ID } from '../../../../../../common/constants';
|
||||
import type { InternalRuleCreate, RuleParams } from '../../../rule_schema';
|
||||
import { transformToActionFrequency } from '../../normalization/rule_actions';
|
||||
|
||||
const DUPLICATE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.cloneRule.duplicateTitle',
|
||||
|
@ -33,6 +34,7 @@ export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise<Inte
|
|||
const relatedIntegrations = isPrebuilt ? [] : rule.params.relatedIntegrations;
|
||||
const requiredFields = isPrebuilt ? [] : rule.params.requiredFields;
|
||||
const setup = isPrebuilt ? '' : rule.params.setup;
|
||||
const actions = transformToActionFrequency(rule.actions, rule.throttle);
|
||||
|
||||
return {
|
||||
name: `${rule.name} [${DUPLICATE_TITLE}]`,
|
||||
|
@ -50,8 +52,6 @@ export const duplicateRule = async ({ rule }: DuplicateRuleParams): Promise<Inte
|
|||
},
|
||||
schedule: rule.schedule,
|
||||
enabled: false,
|
||||
actions: rule.actions,
|
||||
throttle: null,
|
||||
notifyWhen: null,
|
||||
actions,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,22 +10,7 @@ import type { BulkEditOperation } from '@kbn/alerting-plugin/server';
|
|||
import type { BulkActionEditForRuleAttributes } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import { assertUnreachable } from '../../../../../../common/utility_types';
|
||||
|
||||
import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions';
|
||||
|
||||
const getThrottleOperation = (throttle: string) =>
|
||||
({
|
||||
field: 'throttle',
|
||||
operation: 'set',
|
||||
value: transformToAlertThrottle(throttle),
|
||||
} as const);
|
||||
|
||||
const getNotifyWhenOperation = (throttle: string) =>
|
||||
({
|
||||
field: 'notifyWhen',
|
||||
operation: 'set',
|
||||
value: transformToNotifyWhen(throttle),
|
||||
} as const);
|
||||
import { transformToActionFrequency } from '../../normalization/rule_actions';
|
||||
|
||||
/**
|
||||
* converts bulk edit action to format of rulesClient.bulkEdit operation
|
||||
|
@ -70,10 +55,8 @@ export const bulkEditActionToRulesClientOperation = (
|
|||
{
|
||||
field: 'actions',
|
||||
operation: 'add',
|
||||
value: action.value.actions,
|
||||
value: transformToActionFrequency(action.value.actions, action.value.throttle),
|
||||
},
|
||||
getThrottleOperation(action.value.throttle),
|
||||
getNotifyWhenOperation(action.value.throttle),
|
||||
];
|
||||
|
||||
case BulkActionEditType.set_rule_actions:
|
||||
|
@ -81,10 +64,8 @@ export const bulkEditActionToRulesClientOperation = (
|
|||
{
|
||||
field: 'actions',
|
||||
operation: 'set',
|
||||
value: action.value.actions,
|
||||
value: transformToActionFrequency(action.value.actions, action.value.throttle),
|
||||
},
|
||||
getThrottleOperation(action.value.throttle),
|
||||
getNotifyWhenOperation(action.value.throttle),
|
||||
];
|
||||
|
||||
// schedule actions
|
||||
|
|
|
@ -123,6 +123,7 @@ describe('patchRules', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals',
|
||||
},
|
||||
group: 'default',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -158,6 +159,7 @@ describe('patchRules', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals',
|
||||
},
|
||||
group: 'default',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { RuleUpdateProps } from '../../../../../../common/detection_engine/
|
|||
import { transformRuleToAlertAction } from '../../../../../../common/detection_engine/transform_actions';
|
||||
|
||||
import type { InternalRuleUpdate, RuleParams, RuleAlertType } from '../../../rule_schema';
|
||||
import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions';
|
||||
import { transformToActionFrequency } from '../../normalization/rule_actions';
|
||||
import { typeSpecificSnakeToCamel } from '../../normalization/rule_converters';
|
||||
|
||||
export interface UpdateRulesOptions {
|
||||
|
@ -30,6 +30,9 @@ export const updateRules = async ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const alertActions = ruleUpdate.actions?.map(transformRuleToAlertAction) ?? [];
|
||||
const actions = transformToActionFrequency(alertActions, ruleUpdate.throttle);
|
||||
|
||||
const typeSpecificParams = typeSpecificSnakeToCamel(ruleUpdate);
|
||||
const enabled = ruleUpdate.enabled ?? true;
|
||||
const newInternalRule: InternalRuleUpdate = {
|
||||
|
@ -70,9 +73,7 @@ export const updateRules = async ({
|
|||
...typeSpecificParams,
|
||||
},
|
||||
schedule: { interval: ruleUpdate.interval ?? '5m' },
|
||||
actions: ruleUpdate.actions != null ? ruleUpdate.actions.map(transformRuleToAlertAction) : [],
|
||||
throttle: transformToAlertThrottle(ruleUpdate.throttle),
|
||||
notifyWhen: transformToNotifyWhen(ruleUpdate.throttle),
|
||||
actions,
|
||||
};
|
||||
|
||||
const update = await rulesClient.update({
|
||||
|
|
|
@ -131,7 +131,6 @@ describe('getExportAll', () => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'no_actions',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
exceptions_list: getListArrayMock(),
|
||||
|
@ -275,6 +274,7 @@ describe('getExportAll', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.slack',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
building_block_type: 'default',
|
||||
|
@ -313,7 +313,6 @@ describe('getExportAll', () => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'rule',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
@ -416,6 +415,7 @@ describe('getExportAll', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.email',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -128,7 +128,6 @@ describe('get_export_by_object_ids', () => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'no_actions',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
exceptions_list: getListArrayMock(),
|
||||
|
@ -284,6 +283,7 @@ describe('get_export_by_object_ids', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.slack',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
building_block_type: 'default',
|
||||
|
@ -322,7 +322,6 @@ describe('get_export_by_object_ids', () => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'rule',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
@ -426,6 +425,7 @@ describe('get_export_by_object_ids', () => {
|
|||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.email',
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -508,7 +508,7 @@ describe('get_export_by_object_ids', () => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'no_actions',
|
||||
throttle: undefined,
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SanitizedRuleAction } from '@kbn/alerting-plugin/common';
|
||||
import {
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '../../../../../common/constants';
|
||||
|
@ -14,6 +16,7 @@ import type { RuleAlertType } from '../../rule_schema';
|
|||
|
||||
import {
|
||||
transformFromAlertThrottle,
|
||||
transformToActionFrequency,
|
||||
transformToAlertThrottle,
|
||||
transformToNotifyWhen,
|
||||
} from './rule_actions';
|
||||
|
@ -151,4 +154,152 @@ describe('Rule actions normalization', () => {
|
|||
).toEqual(NOTIFICATION_THROTTLE_RULE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformToActionFrequency', () => {
|
||||
describe('actions without frequencies', () => {
|
||||
const actionsWithoutFrequencies: SanitizedRuleAction[] = [
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-123',
|
||||
actionTypeId: 'id-456',
|
||||
params: {},
|
||||
},
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-789',
|
||||
actionTypeId: 'id-012',
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
|
||||
test.each([undefined, null, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE])(
|
||||
`it sets each action's frequency attribute to default value when 'throttle' is '%s'`,
|
||||
(throttle) => {
|
||||
expect(transformToActionFrequency(actionsWithoutFrequencies, throttle)).toEqual(
|
||||
actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.each(['47s', '10m', '3h', '101d'])(
|
||||
`it correctly transforms 'throttle = %s' and sets it as a frequency of each action`,
|
||||
(throttle) => {
|
||||
expect(transformToActionFrequency(actionsWithoutFrequencies, throttle)).toEqual(
|
||||
actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
const actionsWithFrequencies: SanitizedRuleAction[] = [
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-123',
|
||||
actionTypeId: 'id-456',
|
||||
params: {},
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-789',
|
||||
actionTypeId: 'id-012',
|
||||
params: {},
|
||||
frequency: {
|
||||
summary: false,
|
||||
throttle: '1s',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each([
|
||||
undefined,
|
||||
null,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'1h',
|
||||
'1d',
|
||||
])(`it does not change actions frequency attributes when 'throttle' is '%s'`, (throttle) => {
|
||||
expect(transformToActionFrequency(actionsWithFrequencies, throttle)).toEqual(
|
||||
actionsWithFrequencies
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
const someActionsWithFrequencies: SanitizedRuleAction[] = [
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-123',
|
||||
actionTypeId: 'id-456',
|
||||
params: {},
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notifyWhen: 'onActiveAlert',
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-789',
|
||||
actionTypeId: 'id-012',
|
||||
params: {},
|
||||
frequency: {
|
||||
summary: false,
|
||||
throttle: '1s',
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
},
|
||||
{
|
||||
group: 'group',
|
||||
id: 'id-345',
|
||||
actionTypeId: 'id-678',
|
||||
params: {},
|
||||
},
|
||||
];
|
||||
|
||||
test.each([undefined, null, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE])(
|
||||
`it overrides each action's frequency attribute to default value when 'throttle' is '%s'`,
|
||||
(throttle) => {
|
||||
expect(transformToActionFrequency(someActionsWithFrequencies, throttle)).toEqual(
|
||||
someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.each(['47s', '10m', '3h', '101d'])(
|
||||
`it correctly transforms 'throttle = %s' and overrides frequency attribute of each action`,
|
||||
(throttle) => {
|
||||
expect(transformToActionFrequency(someActionsWithFrequencies, throttle)).toEqual(
|
||||
someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RuleNotifyWhenType } from '@kbn/alerting-plugin/common';
|
||||
import type { RuleActionFrequency, RuleNotifyWhenType } from '@kbn/alerting-plugin/common';
|
||||
|
||||
import {
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
|
@ -14,6 +14,38 @@ import {
|
|||
|
||||
import type { RuleAlertType } from '../../rule_schema';
|
||||
|
||||
export const transformToFrequency = (throttle: string | null | undefined): RuleActionFrequency => {
|
||||
return {
|
||||
summary: true,
|
||||
notifyWhen: transformToNotifyWhen(throttle) ?? 'onActiveAlert',
|
||||
throttle: transformToAlertThrottle(throttle),
|
||||
};
|
||||
};
|
||||
|
||||
interface ActionWithFrequency {
|
||||
frequency?: RuleActionFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The action level `frequency` attribute should always take precedence over the rule level `throttle`
|
||||
* Frequency's default value is `{ summary: true, throttle: null, notifyWhen: 'onActiveAlert' }`
|
||||
*
|
||||
* The transformation follows the next rules:
|
||||
* - Both rule level `throttle` and all actions have `frequency` are set: we will ignore rule level `throttle`
|
||||
* - Rule level `throttle` set and actions don't have `frequency` set: we will transform rule level `throttle` in action level `frequency`
|
||||
* - All actions have `frequency` set: do nothing
|
||||
* - Neither of them is set: we will set action level `frequency` to default value
|
||||
* - Rule level `throttle` and some of the actions have `frequency` set: we will transform rule level `throttle` and set it to actions without the frequency attribute
|
||||
* - Only some actions have `frequency` set and there is no rule level `throttle`: we will set default `frequency` to actions without frequency attribute
|
||||
*/
|
||||
export const transformToActionFrequency = <T extends ActionWithFrequency>(
|
||||
actions: T[],
|
||||
throttle: string | null | undefined
|
||||
): T[] => {
|
||||
const defaultFrequency = transformToFrequency(throttle);
|
||||
return actions.map((action) => ({ ...action, frequency: action.frequency ?? defaultFrequency }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen
|
||||
* on their saved object.
|
||||
|
|
|
@ -39,10 +39,10 @@ import {
|
|||
} from '../../../../../common/detection_engine/rule_schema';
|
||||
|
||||
import {
|
||||
transformAlertToRuleAction,
|
||||
transformAlertToRuleResponseAction,
|
||||
transformRuleToAlertAction,
|
||||
transformRuleToAlertResponseAction,
|
||||
transformAlertToRuleAction,
|
||||
} from '../../../../../common/detection_engine/transform_actions';
|
||||
|
||||
import {
|
||||
|
@ -73,11 +73,7 @@ import type {
|
|||
NewTermsRuleParams,
|
||||
NewTermsSpecificRuleParams,
|
||||
} from '../../rule_schema';
|
||||
import {
|
||||
transformFromAlertThrottle,
|
||||
transformToAlertThrottle,
|
||||
transformToNotifyWhen,
|
||||
} from './rule_actions';
|
||||
import { transformFromAlertThrottle, transformToActionFrequency } from './rule_actions';
|
||||
import { convertAlertSuppressionToCamel, convertAlertSuppressionToSnake } from '../utils/utils';
|
||||
import { createRuleExecutionSummary } from '../../rule_monitoring';
|
||||
|
||||
|
@ -399,6 +395,11 @@ export const convertPatchAPIToInternalSchema = (
|
|||
): InternalRuleUpdate => {
|
||||
const typeSpecificParams = patchTypeSpecificSnakeToCamel(nextParams, existingRule.params);
|
||||
const existingParams = existingRule.params;
|
||||
|
||||
const alertActions = nextParams.actions?.map(transformRuleToAlertAction) ?? existingRule.actions;
|
||||
const throttle = nextParams.throttle ?? transformFromAlertThrottle(existingRule);
|
||||
const actions = transformToActionFrequency(alertActions, throttle);
|
||||
|
||||
return {
|
||||
name: nextParams.name ?? existingRule.name,
|
||||
tags: nextParams.tags ?? existingRule.tags,
|
||||
|
@ -438,15 +439,7 @@ export const convertPatchAPIToInternalSchema = (
|
|||
...typeSpecificParams,
|
||||
},
|
||||
schedule: { interval: nextParams.interval ?? existingRule.schedule.interval },
|
||||
actions: nextParams.actions
|
||||
? nextParams.actions.map(transformRuleToAlertAction)
|
||||
: existingRule.actions,
|
||||
throttle: nextParams.throttle
|
||||
? transformToAlertThrottle(nextParams.throttle)
|
||||
: existingRule.throttle ?? null,
|
||||
notifyWhen: nextParams.throttle
|
||||
? transformToNotifyWhen(nextParams.throttle)
|
||||
: existingRule.notifyWhen ?? null,
|
||||
actions,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -462,6 +455,10 @@ export const convertCreateAPIToInternalSchema = (
|
|||
): InternalRuleCreate => {
|
||||
const typeSpecificParams = typeSpecificSnakeToCamel(input);
|
||||
const newRuleId = input.rule_id ?? uuidv4();
|
||||
|
||||
const alertActions = input.actions?.map(transformRuleToAlertAction) ?? [];
|
||||
const actions = transformToActionFrequency(alertActions, input.throttle);
|
||||
|
||||
return {
|
||||
name: input.name,
|
||||
tags: input.tags ?? [],
|
||||
|
@ -502,9 +499,7 @@ export const convertCreateAPIToInternalSchema = (
|
|||
},
|
||||
schedule: { interval: input.interval ?? '5m' },
|
||||
enabled: input.enabled ?? defaultEnabled,
|
||||
actions: input.actions?.map(transformRuleToAlertAction) ?? [],
|
||||
throttle: transformToAlertThrottle(input.throttle),
|
||||
notifyWhen: transformToNotifyWhen(input.throttle),
|
||||
actions,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -651,6 +646,10 @@ export const internalRuleToAPIResponse = (
|
|||
const isResolvedRule = (obj: unknown): obj is ResolvedSanitizedRule<RuleParams> =>
|
||||
(obj as ResolvedSanitizedRule<RuleParams>).outcome != null;
|
||||
|
||||
const alertActions = rule.actions.map(transformAlertToRuleAction);
|
||||
const throttle = transformFromAlertThrottle(rule);
|
||||
const actions = transformToActionFrequency(alertActions, throttle);
|
||||
|
||||
return {
|
||||
// saved object properties
|
||||
outcome: isResolvedRule(rule) ? rule.outcome : undefined,
|
||||
|
@ -672,8 +671,8 @@ export const internalRuleToAPIResponse = (
|
|||
// Type specific security solution rule params
|
||||
...typeSpecificCamelToSnake(rule.params),
|
||||
// Actions
|
||||
throttle: transformFromAlertThrottle(rule),
|
||||
actions: rule.actions.map(transformAlertToRuleAction),
|
||||
throttle: undefined,
|
||||
actions,
|
||||
// Execution summary
|
||||
execution_summary: executionSummary ?? undefined,
|
||||
};
|
||||
|
|
|
@ -43,7 +43,7 @@ export const ruleOutput = (): RuleResponse => ({
|
|||
tags: [],
|
||||
to: 'now',
|
||||
type: 'query',
|
||||
throttle: 'no_actions',
|
||||
throttle: undefined,
|
||||
threat: getThreatMock(),
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
RiskScore,
|
||||
RiskScoreMapping,
|
||||
RuleActionArrayCamel,
|
||||
RuleActionNotifyWhen,
|
||||
RuleActionThrottle,
|
||||
RuleIntervalFrom,
|
||||
RuleIntervalTo,
|
||||
|
@ -259,13 +260,6 @@ export interface CompleteRule<T extends RuleParams> {
|
|||
ruleConfig: SanitizedRuleConfig;
|
||||
}
|
||||
|
||||
export const notifyWhen = t.union([
|
||||
t.literal('onActionGroupChange'),
|
||||
t.literal('onActiveAlert'),
|
||||
t.literal('onThrottleInterval'),
|
||||
t.null,
|
||||
]);
|
||||
|
||||
export const allRuleTypes = t.union([
|
||||
t.literal(SIGNALS_ID),
|
||||
t.literal(EQL_RULE_TYPE_ID),
|
||||
|
@ -291,7 +285,7 @@ const internalRuleCreateRequired = t.type({
|
|||
});
|
||||
const internalRuleCreateOptional = t.partial({
|
||||
throttle: t.union([RuleActionThrottle, t.null]),
|
||||
notifyWhen,
|
||||
notifyWhen: t.union([RuleActionNotifyWhen, t.null]),
|
||||
});
|
||||
export const internalRuleCreate = t.intersection([
|
||||
internalRuleCreateOptional,
|
||||
|
@ -310,7 +304,7 @@ const internalRuleUpdateRequired = t.type({
|
|||
});
|
||||
const internalRuleUpdateOptional = t.partial({
|
||||
throttle: t.union([RuleActionThrottle, t.null]),
|
||||
notifyWhen,
|
||||
notifyWhen: t.union([RuleActionNotifyWhen, t.null]),
|
||||
});
|
||||
export const internalRuleUpdate = t.intersection([
|
||||
internalRuleUpdateOptional,
|
||||
|
|
|
@ -28999,9 +28999,6 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription": "Sélectionner des index de menaces",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchoutputIndiceNameFieldRequiredError": "Au minimum un modèle d'indexation est requis.",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.thresholdField.thresholdFieldPlaceholderText": "Tous les résultats",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpText": "Sélectionnez le moment auquel les actions automatiques doivent être effectuées si une règle est évaluée comme vraie.",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpTextWhenQuery": "Sélectionnez le moment auquel les actions automatiques doivent être effectuées si une règle est évaluée comme vraie. Cette fréquence ne s'applique pas aux actions de réponse.",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel": "Fréquence des actions",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.noReadActionsPrivileges": "Impossible de créer des actions de règle. Vous ne disposez pas des autorisations \"Lire\" pour le plug-in \"Actions\".",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithEnablingTitle": "Créer et activer la règle",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithoutEnablingTitle": "Créer la règle sans l’activer",
|
||||
|
@ -29959,12 +29956,9 @@
|
|||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.tooltip": " Si vous dupliquez les exceptions, la liste des exceptions partagée sera dupliquée par référence et l'exception de la règle par défaut sera copiée et créée comme une nouvelle exception",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.successToastTitle": "Règles dupliquées",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicateTitle": "Dupliquer",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.actionFrequencyDetail": "La fréquence des actions que vous sélectionnez ci-dessous est appliquée à toutes les actions (nouvelles et existantes) pour toutes les règles sélectionnées.",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.formTitle": "Ajouter des actions sur les règles",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.overwriteCheckboxLabel": "Écraser toutes les actions sur les règles sélectionnées",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.ruleVariablesDetail": "Les variables de règle peuvent affecter uniquement certaines règles sélectionnées, en fonction des types de règle (par exemple, \\u007b\\u007bcontext.rule.threshold\\u007d\\u007d affichera uniquement les valeurs des règles de seuil).",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleHelpText": "Sélectionnez le moment auquel les actions automatiques doivent être effectuées si une règle est évaluée comme vraie.",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleLabel": "Fréquence des actions",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.warningCalloutMessage.buttonLabel": "Enregistrer",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.formTitle": "Appliquer le modèle de chronologie",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorDefaultValue": "Aucun",
|
||||
|
|
|
@ -28978,9 +28978,6 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription": "脅威インデックスを選択",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchoutputIndiceNameFieldRequiredError": "インデックスパターンが最低1つ必要です。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.thresholdField.thresholdFieldPlaceholderText": "すべての結果",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpText": "ルールが true であると評価された場合に自動アクションを実行するタイミングを選択します。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpTextWhenQuery": "ルールが true であると評価された場合に自動アクションを実行するタイミングを選択します。この頻度は対応アクションには適用されません。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel": "アクション頻度",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.noReadActionsPrivileges": "ルールアクションを作成できません。「Actions」プラグインの「読み取り」アクセス権がありません。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithEnablingTitle": "ルールを作成して有効にする",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithoutEnablingTitle": "有効にせずにルールを作成",
|
||||
|
@ -29938,12 +29935,9 @@
|
|||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.tooltip": " 例外を複製する場合は、参照によって共有例外リストが複製されます。それから、デフォルトルール例外がコピーされ、新しい例外が作成されます",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.successToastTitle": "ルールが複製されました",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicateTitle": "複製",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.actionFrequencyDetail": "以下で選択したアクション頻度は、すべての選択したルールのすべてのアクション(新規と既存のアクション)に適用されます。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.formTitle": "ルールアクションを追加",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.overwriteCheckboxLabel": "すべての選択したルールアクションを上書き",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.ruleVariablesDetail": "ルールタイプによっては、ルール変数が選択舌一部のルールにのみ影響する場合があります(例:\\u007b\\u007bcontext.rule.threshold\\u007d\\u007dはしきい値ルールの値のみを表示します)。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleHelpText": "ルールが true であると評価された場合に自動アクションを実行するタイミングを選択します。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleLabel": "アクション頻度",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.warningCalloutMessage.buttonLabel": "保存",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.formTitle": "タイムラインテンプレートを適用",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorDefaultValue": "なし",
|
||||
|
|
|
@ -28994,9 +28994,6 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchingIcesHelperDescription": "选择威胁索引",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.threatMatchoutputIndiceNameFieldRequiredError": "至少需要一种索引模式。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepDefineRule.thresholdField.thresholdFieldPlaceholderText": "所有结果",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpText": "选择在规则评估为 true 时应执行自动操作的时间。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleHelpTextWhenQuery": "选择在规则评估为 true 时应执行自动操作的时间。此频率不适用于响应操作。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.fieldThrottleLabel": "操作频率",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepRuleActions.noReadActionsPrivileges": "无法创建规则操作。您对“操作”插件没有“读”权限。",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithEnablingTitle": "创建并启用规则",
|
||||
"xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.completeWithoutEnablingTitle": "创建规则但不启用",
|
||||
|
@ -29954,12 +29951,9 @@
|
|||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.exceptionsConfirmation.tooltip": " 如果您复制例外,则会通过引用复制共享例外列表,然后复制默认规则例外,并将其创建为新例外",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicate.successToastTitle": "规则已复制",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.duplicateTitle": "复制",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.actionFrequencyDetail": "您在下面选择的操作频率将应用于所有选定规则的所有操作(新操作和现有操作)。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.formTitle": "添加规则操作",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.overwriteCheckboxLabel": "覆盖所有选定规则操作",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.ruleVariablesDetail": "基于规则类型,规则变量可能仅影响您选择的某些规则(例如,\\u007b\\u007bcontext.rule.threshold\\u007d\\u007d 将仅显示阈值规则的值)。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleHelpText": "选择在规则评估为 true 时应执行自动操作的时间。",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.throttleLabel": "操作频率",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.warningCalloutMessage.buttonLabel": "保存",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.formTitle": "应用时间线模板",
|
||||
"xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorDefaultValue": "无",
|
||||
|
|
|
@ -99,7 +99,6 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: [],
|
||||
throttle: 'no_actions',
|
||||
exceptions_list: [],
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import {
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { ROLES } from '@kbn/security-solution-plugin/common/test';
|
||||
|
@ -32,8 +37,15 @@ import {
|
|||
waitForSignalsToBePresent,
|
||||
getThresholdRuleForSignalTesting,
|
||||
waitForRulePartialFailure,
|
||||
createRule,
|
||||
} from '../../utils';
|
||||
import { createUserAndRole, deleteUserAndRole } from '../../../common/services/security_solution';
|
||||
import {
|
||||
getActionsWithFrequencies,
|
||||
getActionsWithoutFrequencies,
|
||||
getSomeActionsWithFrequencies,
|
||||
} from '../../utils/get_rule_actions';
|
||||
import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
|
@ -200,7 +212,6 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: 'now',
|
||||
type: 'query',
|
||||
threat: [],
|
||||
throttle: 'no_actions',
|
||||
exceptions_list: [],
|
||||
version: 1,
|
||||
};
|
||||
|
@ -566,5 +577,139 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(rule?.execution_summary?.last_execution.status).to.eql('partial failure');
|
||||
});
|
||||
});
|
||||
|
||||
describe('per-action frequencies', () => {
|
||||
const createSingleRule = async (rule: RuleCreateProps) => {
|
||||
const createdRule = await createRule(supertest, log, rule);
|
||||
createdRule.actions = removeUUIDFromActions(createdRule.actions);
|
||||
return createdRule;
|
||||
};
|
||||
|
||||
describe('actions without frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRule();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithoutFrequencies;
|
||||
|
||||
const createdRule = await createSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutput();
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
const rule = removeServerGeneratedProperties(createdRule);
|
||||
expect(rule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['300s', '5m', '3h', '4d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRule();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithoutFrequencies;
|
||||
|
||||
const createdRule = await createSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutput();
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: { summary: true, throttle, notifyWhen: 'onThrottleInterval' },
|
||||
}));
|
||||
|
||||
const rule = removeServerGeneratedProperties(createdRule);
|
||||
expect(rule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
[
|
||||
undefined,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'321s',
|
||||
'6m',
|
||||
'10h',
|
||||
'2d',
|
||||
].forEach((throttle) => {
|
||||
it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => {
|
||||
const actionsWithFrequencies = await getActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRule();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithFrequencies;
|
||||
|
||||
const createdRule = await createSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutput();
|
||||
expectedRule.actions = actionsWithFrequencies;
|
||||
|
||||
const rule = removeServerGeneratedProperties(createdRule);
|
||||
expect(rule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRule();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = someActionsWithFrequencies;
|
||||
|
||||
const createdRule = await createSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutput();
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
const rule = removeServerGeneratedProperties(createdRule);
|
||||
expect(rule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['430s', '7m', '1h', '8d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and overrides frequency attribute of each action`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRule();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = someActionsWithFrequencies;
|
||||
|
||||
const createdRule = await createSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutput();
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}));
|
||||
|
||||
const rule = removeServerGeneratedProperties(createdRule);
|
||||
expect(rule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,7 +10,11 @@ import expect from '@kbn/expect';
|
|||
import {
|
||||
DETECTION_ENGINE_RULES_BULK_CREATE,
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
@ -27,6 +31,12 @@ import {
|
|||
removeServerGeneratedPropertiesIncludingRuleId,
|
||||
waitForRuleSuccess,
|
||||
} from '../../utils';
|
||||
import {
|
||||
getActionsWithFrequencies,
|
||||
getActionsWithoutFrequencies,
|
||||
getSomeActionsWithFrequencies,
|
||||
} from '../../utils/get_rule_actions';
|
||||
import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
|
@ -276,6 +286,141 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('per-action frequencies', () => {
|
||||
const bulkCreateSingleRule = async (rule: RuleCreateProps) => {
|
||||
const { body } = await supertest
|
||||
.post(DETECTION_ENGINE_RULES_BULK_CREATE)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send([rule])
|
||||
.expect(200);
|
||||
|
||||
const createdRule = body[0];
|
||||
createdRule.actions = removeUUIDFromActions(createdRule.actions);
|
||||
return removeServerGeneratedPropertiesIncludingRuleId(createdRule);
|
||||
};
|
||||
|
||||
describe('actions without frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRuleWithoutRuleId();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithoutFrequencies;
|
||||
|
||||
const createdRule = await bulkCreateSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(createdRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['300s', '5m', '3h', '4d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRuleWithoutRuleId();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithoutFrequencies;
|
||||
|
||||
const createdRule = await bulkCreateSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: { summary: true, throttle, notifyWhen: 'onThrottleInterval' },
|
||||
}));
|
||||
|
||||
expect(createdRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
[
|
||||
undefined,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'321s',
|
||||
'6m',
|
||||
'10h',
|
||||
'2d',
|
||||
].forEach((throttle) => {
|
||||
it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => {
|
||||
const actionsWithFrequencies = await getActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRuleWithoutRuleId();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = actionsWithFrequencies;
|
||||
|
||||
const createdRule = await bulkCreateSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.actions = actionsWithFrequencies;
|
||||
|
||||
expect(createdRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRuleWithoutRuleId();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = someActionsWithFrequencies;
|
||||
|
||||
const createdRule = await bulkCreateSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(createdRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['430s', '7m', '1h', '8d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and overrides frequency attribute of each action`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
const simpleRule = getSimpleRuleWithoutRuleId();
|
||||
simpleRule.throttle = throttle;
|
||||
simpleRule.actions = someActionsWithFrequencies;
|
||||
|
||||
const createdRule = await bulkCreateSingleRule(simpleRule);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}));
|
||||
|
||||
expect(createdRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -176,6 +176,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -309,6 +309,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -357,6 +358,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
expect(body[1].actions).to.eql([
|
||||
|
@ -368,6 +370,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import expect from 'expect';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import {
|
||||
binaryToString,
|
||||
|
@ -208,10 +209,17 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const outputRule1: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput('rule-1'),
|
||||
actions: [
|
||||
{ ...action1, uuid: firstRule.actions[0].uuid },
|
||||
{ ...action2, uuid: firstRule.actions[1].uuid },
|
||||
{
|
||||
...action1,
|
||||
uuid: firstRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
{
|
||||
...action2,
|
||||
uuid: firstRule.actions[1].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
throttle: 'rule',
|
||||
};
|
||||
expect(firstRule).toEqual(outputRule1);
|
||||
});
|
||||
|
@ -258,13 +266,23 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
|
||||
const outputRule1: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput('rule-2'),
|
||||
actions: [{ ...action, uuid: firstRule.actions[0].uuid }],
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: firstRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
};
|
||||
const outputRule2: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput('rule-1'),
|
||||
actions: [{ ...action, uuid: secondRule.actions[0].uuid }],
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: secondRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(firstRule).toEqual(outputRule1);
|
||||
expect(secondRule).toEqual(outputRule2);
|
||||
|
@ -437,9 +455,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]);
|
||||
const firstRule = removeServerGeneratedProperties(firstRuleParsed);
|
||||
|
@ -514,6 +532,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
|
@ -523,9 +542,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]);
|
||||
const firstRule = removeServerGeneratedProperties(firstRuleParsed);
|
||||
|
@ -631,6 +650,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
|
@ -640,9 +660,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
|
||||
const outputRule2: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
|
@ -656,6 +676,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
|
@ -665,9 +686,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
const firstRuleParsed = JSON.parse(body.toString().split(/\n/)[0]);
|
||||
const secondRuleParsed = JSON.parse(body.toString().split(/\n/)[1]);
|
||||
|
@ -682,7 +703,8 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
};
|
||||
|
||||
function expectToMatchRuleSchema(obj: unknown): void {
|
||||
function expectToMatchRuleSchema(obj: RuleResponse): void {
|
||||
expect(obj.throttle).toBeUndefined();
|
||||
expect(obj).toEqual({
|
||||
id: expect.any(String),
|
||||
rule_id: expect.any(String),
|
||||
|
@ -718,7 +740,6 @@ function expectToMatchRuleSchema(obj: unknown): void {
|
|||
language: expect.any(String),
|
||||
index: expect.arrayContaining([]),
|
||||
query: expect.any(String),
|
||||
throttle: expect.any(String),
|
||||
actions: expect.arrayContaining([]),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -126,8 +126,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
|
||||
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput(),
|
||||
actions: [{ ...action, uuid: body.data[0].actions[0].uuid }],
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: body.data[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
body.data = [removeServerGeneratedProperties(body.data[0])];
|
||||
|
@ -171,8 +176,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
|
||||
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput(),
|
||||
actions: [{ ...action, uuid: body.data[0].actions[0].uuid }],
|
||||
throttle: '1h', // <-- throttle makes this a scheduled action
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: body.data[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
body.data = [removeServerGeneratedProperties(body.data[0])];
|
||||
|
@ -239,9 +249,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: hookAction.actionTypeId,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
|
||||
body.data = [removeServerGeneratedProperties(body.data[0])];
|
||||
|
|
|
@ -75,8 +75,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(sidecarActionsSOAfterMigration.hits.hits.length).to.eql(0);
|
||||
|
||||
expect(ruleSO?.alert.actions).to.eql([]);
|
||||
expect(ruleSO?.alert.throttle).to.eql('no_actions');
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(ruleSO?.alert.throttle).to.eql(null);
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
it('migrates legacy actions for rule with action run on every run', async () => {
|
||||
|
@ -122,6 +122,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: ['test@test.com'],
|
||||
},
|
||||
uuid: ruleSO?.alert.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
{
|
||||
actionRef: 'action_1',
|
||||
|
@ -133,10 +134,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: ['test@test.com'],
|
||||
},
|
||||
uuid: ruleSO?.alert.actions[1].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
]);
|
||||
expect(ruleSO?.alert.throttle).to.eql('rule');
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql('onActiveAlert');
|
||||
expect(ruleSO?.alert.throttle).to.eql(null);
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql(null);
|
||||
expect(ruleSO?.references).to.eql([
|
||||
{
|
||||
id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf',
|
||||
|
@ -197,6 +199,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
actionRef: 'action_0',
|
||||
group: 'default',
|
||||
uuid: ruleSO?.alert.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
actionTypeId: '.slack',
|
||||
|
@ -206,10 +209,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
actionRef: 'action_1',
|
||||
group: 'default',
|
||||
uuid: ruleSO?.alert.actions[1].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
expect(ruleSO?.alert.throttle).to.eql('1h');
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(ruleSO?.alert.throttle).to.eql(undefined);
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql(null);
|
||||
expect(ruleSO?.references).to.eql([
|
||||
{
|
||||
id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf',
|
||||
|
@ -269,10 +273,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: ['test@test.com'],
|
||||
},
|
||||
uuid: ruleSO?.alert.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
expect(ruleSO?.alert.throttle).to.eql('1d');
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(ruleSO?.alert.throttle).to.eql(undefined);
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql(null);
|
||||
expect(ruleSO?.references).to.eql([
|
||||
{
|
||||
id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf',
|
||||
|
@ -327,10 +332,11 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
to: ['test@test.com'],
|
||||
},
|
||||
uuid: ruleSO?.alert.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '7d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
expect(ruleSO?.alert.throttle).to.eql('7d');
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(ruleSO?.alert.throttle).to.eql(undefined);
|
||||
expect(ruleSO?.alert.notifyWhen).to.eql(null);
|
||||
expect(ruleSO?.references).to.eql([
|
||||
{
|
||||
id: 'c95cb100-b075-11ec-bb3f-1f063f8e06cf',
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import {
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import {
|
||||
|
@ -24,7 +30,14 @@ import {
|
|||
getSimpleMlRule,
|
||||
createLegacyRuleAction,
|
||||
getLegacyActionSO,
|
||||
getSimpleRuleWithoutRuleId,
|
||||
} from '../../utils';
|
||||
import {
|
||||
getActionsWithFrequencies,
|
||||
getActionsWithoutFrequencies,
|
||||
getSomeActionsWithFrequencies,
|
||||
} from '../../utils/get_rule_actions';
|
||||
import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
|
@ -377,9 +390,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: bodyToCompare.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1h';
|
||||
outputRule.revision = 1;
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
|
||||
|
@ -414,5 +427,168 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('patch per-action frequencies', () => {
|
||||
const patchSingleRule = async (
|
||||
ruleId: string,
|
||||
throttle: RuleActionThrottle | undefined,
|
||||
actions: RuleActionArray
|
||||
) => {
|
||||
const { body: patchedRule } = await supertest
|
||||
.patch(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ rule_id: ruleId, throttle, actions })
|
||||
.expect(200);
|
||||
|
||||
patchedRule.actions = removeUUIDFromActions(patchedRule.actions);
|
||||
return removeServerGeneratedPropertiesIncludingRuleId(patchedRule);
|
||||
};
|
||||
|
||||
describe('actions without frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// patch a simple rule's `throttle` and `actions`
|
||||
const patchedRule = await patchSingleRule(
|
||||
createdRule.rule_id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(patchedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['300s', '5m', '3h', '4d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// patch a simple rule's `throttle` and `actions`
|
||||
const patchedRule = await patchSingleRule(
|
||||
createdRule.rule_id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: { summary: true, throttle, notifyWhen: 'onThrottleInterval' },
|
||||
}));
|
||||
|
||||
expect(patchedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
[
|
||||
undefined,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'321s',
|
||||
'6m',
|
||||
'10h',
|
||||
'2d',
|
||||
].forEach((throttle) => {
|
||||
it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => {
|
||||
const actionsWithFrequencies = await getActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// patch a simple rule's `throttle` and `actions`
|
||||
const patchedRule = await patchSingleRule(
|
||||
createdRule.rule_id,
|
||||
throttle,
|
||||
actionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithFrequencies;
|
||||
|
||||
expect(patchedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// patch a simple rule's `throttle` and `actions`
|
||||
const patchedRule = await patchSingleRule(
|
||||
createdRule.rule_id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(patchedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['430s', '7m', '1h', '8d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and overrides frequency attribute of each action`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// patch a simple rule's `throttle` and `actions`
|
||||
const patchedRule = await patchSingleRule(
|
||||
createdRule.rule_id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}));
|
||||
|
||||
expect(patchedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -207,9 +207,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: bodyToCompare.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1h';
|
||||
outputRule.revision = 1;
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
|
|
@ -9,7 +9,6 @@ import expect from '@kbn/expect';
|
|||
import {
|
||||
DETECTION_ENGINE_RULES_BULK_ACTION,
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
|
||||
|
@ -172,7 +171,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const rule = removeServerGeneratedProperties(JSON.parse(ruleJson));
|
||||
expect(rule).to.eql({
|
||||
...getSimpleRuleOutput(),
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: '.webhook',
|
||||
|
@ -182,6 +180,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
body: '{"test":"a default action"}',
|
||||
},
|
||||
uuid: rule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -335,6 +334,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: ruleBody.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
// we want to ensure rule is executing successfully, to prevent any AAD issues related to partial update of rule SO
|
||||
|
@ -407,6 +407,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: ruleBody.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -705,6 +706,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
...(uuid ? { uuid } : {}),
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -1334,6 +1336,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: setTagsRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -1618,6 +1621,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1675,6 +1679,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1786,6 +1791,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1838,6 +1844,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1892,12 +1899,17 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.expect(200);
|
||||
|
||||
const expectedRuleActions = [
|
||||
{ ...defaultRuleAction, uuid: body.attributes.results.updated[0].actions[0].uuid },
|
||||
{
|
||||
...defaultRuleAction,
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
...webHookActionMock,
|
||||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: body.attributes.results.updated[0].actions[1].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1960,12 +1972,17 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
.expect(200);
|
||||
|
||||
const expectedRuleActions = [
|
||||
{ ...defaultRuleAction, uuid: body.attributes.results.updated[0].actions[0].uuid },
|
||||
{
|
||||
...defaultRuleAction,
|
||||
uuid: body.attributes.results.updated[0].actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
...slackConnectorMockProps,
|
||||
id: slackConnector.id,
|
||||
action_type_id: '.slack',
|
||||
uuid: body.attributes.results.updated[0].actions[1].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -2014,20 +2031,22 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(body.attributes.results.updated[0].actions).to.eql([
|
||||
{ ...defaultRuleAction, uuid: createdRule.actions[0].uuid },
|
||||
]);
|
||||
// Check that the rule is skipped and was not updated
|
||||
expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: readRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(readRule.actions).to.eql([
|
||||
{ ...defaultRuleAction, uuid: createdRule.actions[0].uuid },
|
||||
{
|
||||
...defaultRuleAction,
|
||||
uuid: createdRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should change throttle if actions list in payload is empty', async () => {
|
||||
it('should not change throttle if actions list in payload is empty', async () => {
|
||||
// create a new connector
|
||||
const webHookConnector = await createWebHookConnector();
|
||||
|
||||
|
@ -2063,13 +2082,14 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(body.attributes.results.updated[0].throttle).to.be('1h');
|
||||
// Check that the rule is skipped and was not updated
|
||||
expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: readRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(readRule.throttle).to.eql('1h');
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
expect(readRule.actions).to.eql(createdRule.actions);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2117,6 +2137,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: editedRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
// version of prebuilt rule should not change
|
||||
|
@ -2131,6 +2152,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
id: webHookConnector.id,
|
||||
action_type_id: '.webhook',
|
||||
uuid: readRule.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
]);
|
||||
expect(prebuiltRule.version).to.be(readRule.version);
|
||||
|
@ -2207,7 +2229,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
},
|
||||
];
|
||||
casesForEmptyActions.forEach(({ payloadThrottle }) => {
|
||||
it(`throttle is set to NOTIFICATION_THROTTLE_NO_ACTIONS, if payload throttle="${payloadThrottle}" and actions list is empty`, async () => {
|
||||
it(`should not update throttle, if payload throttle="${payloadThrottle}" and actions list is empty`, async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const createdRule = await createRule(supertest, log, {
|
||||
...getSimpleRule(ruleId),
|
||||
|
@ -2230,34 +2252,32 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(body.attributes.results.updated[0].throttle).to.eql(
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
);
|
||||
// Check that the rule is skipped and was not updated
|
||||
expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: rule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(rule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(rule.throttle).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
const casesForNonEmptyActions = [
|
||||
{
|
||||
payloadThrottle: NOTIFICATION_THROTTLE_RULE,
|
||||
expectedThrottle: NOTIFICATION_THROTTLE_RULE,
|
||||
expectedThrottle: undefined,
|
||||
},
|
||||
{
|
||||
payloadThrottle: '1h',
|
||||
expectedThrottle: '1h',
|
||||
expectedThrottle: undefined,
|
||||
},
|
||||
{
|
||||
payloadThrottle: '1d',
|
||||
expectedThrottle: '1d',
|
||||
expectedThrottle: undefined,
|
||||
},
|
||||
{
|
||||
payloadThrottle: '7d',
|
||||
expectedThrottle: '7d',
|
||||
expectedThrottle: undefined,
|
||||
},
|
||||
];
|
||||
[BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].forEach(
|
||||
|
@ -2295,10 +2315,23 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
// Check that the updated rule is returned with the response
|
||||
expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle);
|
||||
|
||||
const expectedActions = body.attributes.results.updated[0].actions.map(
|
||||
(action: any) => ({
|
||||
...action,
|
||||
frequency: {
|
||||
summary: true,
|
||||
throttle: payloadThrottle !== 'rule' ? payloadThrottle : null,
|
||||
notifyWhen:
|
||||
payloadThrottle !== 'rule' ? 'onThrottleInterval' : 'onActiveAlert',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: rule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(rule.throttle).to.eql(expectedThrottle);
|
||||
expect(rule.actions).to.eql(expectedActions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -2311,11 +2344,11 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
const cases = [
|
||||
{
|
||||
payload: { throttle: '1d' },
|
||||
expected: { notifyWhen: 'onThrottleInterval' },
|
||||
expected: { notifyWhen: null },
|
||||
},
|
||||
{
|
||||
payload: { throttle: NOTIFICATION_THROTTLE_RULE },
|
||||
expected: { notifyWhen: 'onActiveAlert' },
|
||||
expected: { notifyWhen: null },
|
||||
},
|
||||
];
|
||||
cases.forEach(({ payload, expected }) => {
|
||||
|
|
|
@ -135,8 +135,13 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const bodyToCompare = removeServerGeneratedProperties(body);
|
||||
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput(),
|
||||
actions: [{ ...action, uuid: bodyToCompare.actions[0].uuid }],
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: bodyToCompare.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(bodyToCompare).to.eql(ruleWithActions);
|
||||
});
|
||||
|
@ -174,8 +179,13 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const bodyToCompare = removeServerGeneratedProperties(body);
|
||||
const ruleWithActions: ReturnType<typeof getSimpleRuleOutput> = {
|
||||
...getSimpleRuleOutput(),
|
||||
actions: [{ ...action, uuid: bodyToCompare.actions[0].uuid }],
|
||||
throttle: '1h', // <-- throttle makes this a scheduled action
|
||||
actions: [
|
||||
{
|
||||
...action,
|
||||
uuid: bodyToCompare.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(bodyToCompare).to.eql(ruleWithActions);
|
||||
});
|
||||
|
@ -236,9 +246,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: hookAction.actionTypeId,
|
||||
frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
],
|
||||
throttle: '1h',
|
||||
};
|
||||
expect(bodyToCompare).to.eql(ruleWithActions);
|
||||
});
|
||||
|
|
|
@ -56,7 +56,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
describe('creating a rule', () => {
|
||||
it('When creating a new action and attaching it to a rule, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onActiveAlert"', async () => {
|
||||
it('When creating a new action and attaching it to a rule, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
// create a new action
|
||||
const { body: hookAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
|
@ -66,10 +66,16 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id));
|
||||
const {
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen, actions },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onActiveAlert');
|
||||
expect(actions.length).to.eql(1);
|
||||
expect(actions[0].frequency).to.eql({
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notify_when: 'onActiveAlert',
|
||||
});
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_NO_ACTIONS" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
|
@ -105,7 +111,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onActiveAlert"', async () => {
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
const ruleWithThrottle: RuleCreateProps = {
|
||||
...getSimpleRule(),
|
||||
throttle: NOTIFICATION_THROTTLE_RULE,
|
||||
|
@ -115,7 +121,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onActiveAlert');
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
// NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty.
|
||||
|
@ -125,10 +131,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
throttle: NOTIFICATION_THROTTLE_RULE,
|
||||
};
|
||||
const rule = await createRule(supertest, log, ruleWithThrottle);
|
||||
expect(rule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(rule.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and actions set, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onActiveAlert"', async () => {
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and actions set, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
// create a new action
|
||||
const { body: hookAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
|
@ -142,13 +148,19 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
};
|
||||
const rule = await createRule(supertest, log, ruleWithThrottle);
|
||||
const {
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen, actions },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onActiveAlert');
|
||||
expect(actions.length).to.eql(1);
|
||||
expect(actions[0].frequency).to.eql({
|
||||
summary: true,
|
||||
throttle: null,
|
||||
notify_when: 'onActiveAlert',
|
||||
});
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
it('When creating throttle with "1h" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onThrottleInterval"', async () => {
|
||||
it('When creating throttle with "1h" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
const ruleWithThrottle: RuleCreateProps = {
|
||||
...getSimpleRule(),
|
||||
throttle: '1h',
|
||||
|
@ -158,10 +170,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
it('When creating throttle with "1h" set and actions set, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onThrottleInterval"', async () => {
|
||||
it('When creating throttle with "1h" set and actions set, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to null', async () => {
|
||||
// create a new action
|
||||
const { body: hookAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
|
@ -175,10 +187,16 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
};
|
||||
const rule = await createRule(supertest, log, ruleWithThrottle);
|
||||
const {
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
body: { mute_all: muteAll, notify_when: notifyWhen, actions },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onThrottleInterval');
|
||||
expect(actions.length).to.eql(1);
|
||||
expect(actions[0].frequency).to.eql({
|
||||
summary: true,
|
||||
throttle: '1h',
|
||||
notify_when: 'onThrottleInterval',
|
||||
});
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -193,7 +211,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
|
||||
const rule = await createRule(supertest, log, getRuleWithWebHookAction(hookAction.id));
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_RULE);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('When creating throttle with "NOTIFICATION_THROTTLE_NO_ACTIONS" set and no actions, we should return "NOTIFICATION_THROTTLE_NO_ACTIONS" when doing a read', async () => {
|
||||
|
@ -203,7 +221,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
};
|
||||
const rule = await createRule(supertest, log, ruleWithThrottle);
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
// NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty.
|
||||
|
@ -214,7 +232,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
};
|
||||
const rule = await createRule(supertest, log, ruleWithThrottle);
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('When creating a new action and attaching it to a rule, if we change the alert to a "muteAll" through the kibana alerting API, we should get back "NOTIFICATION_THROTTLE_NO_ACTIONS" ', async () => {
|
||||
|
@ -232,7 +250,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
.send()
|
||||
.expect(204);
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -249,7 +267,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
await createRule(supertest, log, ruleWithWebHookAction);
|
||||
ruleWithWebHookAction.name = 'some other name';
|
||||
const updated = await updateRule(supertest, log, ruleWithWebHookAction);
|
||||
expect(updated.throttle).to.eql(NOTIFICATION_THROTTLE_RULE);
|
||||
expect(updated.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('will not change the "muteAll" or "notifyWhen" if we update some part of the rule', async () => {
|
||||
|
@ -268,7 +286,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
} = await supertest.get(`/api/alerting/rule/${updated.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onActiveAlert');
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
// NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty.
|
||||
|
@ -284,7 +302,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
await createRule(supertest, log, ruleWithWebHookAction);
|
||||
ruleWithWebHookAction.actions = [];
|
||||
const updated = await updateRule(supertest, log, ruleWithWebHookAction);
|
||||
expect(updated.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(updated.throttle).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -306,7 +324,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
.send({ rule_id: rule.rule_id, name: 'some other name' })
|
||||
.expect(200);
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_RULE);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
|
||||
it('will not change the "muteAll" or "notifyWhen" if we patch part of the rule', async () => {
|
||||
|
@ -329,7 +347,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
body: { mute_all: muteAll, notify_when: notifyWhen },
|
||||
} = await supertest.get(`/api/alerting/rule/${rule.id}`);
|
||||
expect(muteAll).to.eql(false);
|
||||
expect(notifyWhen).to.eql('onActiveAlert');
|
||||
expect(notifyWhen).to.eql(null);
|
||||
});
|
||||
|
||||
// NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty.
|
||||
|
@ -350,7 +368,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
.send({ rule_id: rule.rule_id, actions: [] })
|
||||
.expect(200);
|
||||
const readRule = await getRule(supertest, log, rule.rule_id);
|
||||
expect(readRule.throttle).to.eql(NOTIFICATION_THROTTLE_NO_ACTIONS);
|
||||
expect(readRule.throttle).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import {
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import {
|
||||
|
@ -28,7 +34,14 @@ import {
|
|||
createLegacyRuleAction,
|
||||
getThresholdRuleForSignalTesting,
|
||||
getLegacyActionSO,
|
||||
getSimpleRuleWithoutRuleId,
|
||||
} from '../../utils';
|
||||
import {
|
||||
getActionsWithFrequencies,
|
||||
getActionsWithoutFrequencies,
|
||||
getSomeActionsWithFrequencies,
|
||||
} from '../../utils/get_rule_actions';
|
||||
import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
|
@ -185,8 +198,6 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
outputRule.revision = 1;
|
||||
// Expect an empty array
|
||||
outputRule.actions = [];
|
||||
// Expect "no_actions"
|
||||
outputRule.throttle = 'no_actions';
|
||||
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
@ -221,7 +232,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
id: connector.body.id,
|
||||
action_type_id: connector.body.connector_type_id,
|
||||
params: {
|
||||
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
};
|
||||
// update a simple rule's name
|
||||
|
@ -229,7 +240,6 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
updatedRule.rule_id = createRuleBody.rule_id;
|
||||
updatedRule.name = 'some other name';
|
||||
updatedRule.actions = [action1];
|
||||
updatedRule.throttle = '1d';
|
||||
delete updatedRule.id;
|
||||
|
||||
const { body } = await supertest
|
||||
|
@ -249,13 +259,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
group: 'default',
|
||||
id: connector.body.id,
|
||||
params: {
|
||||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: bodyToCompare.actions![0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1d';
|
||||
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
|
||||
|
@ -662,6 +671,176 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(outputRule.saved_id).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('per-action frequencies', () => {
|
||||
const updateSingleRule = async (
|
||||
ruleId: string,
|
||||
throttle: RuleActionThrottle | undefined,
|
||||
actions: RuleActionArray
|
||||
) => {
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const ruleToUpdate = getSimpleRuleUpdate();
|
||||
ruleToUpdate.throttle = throttle;
|
||||
ruleToUpdate.actions = actions;
|
||||
ruleToUpdate.id = ruleId;
|
||||
delete ruleToUpdate.rule_id;
|
||||
|
||||
const { body: updatedRule } = await supertest
|
||||
.put(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(ruleToUpdate)
|
||||
.expect(200);
|
||||
|
||||
updatedRule.actions = removeUUIDFromActions(updatedRule.actions);
|
||||
return removeServerGeneratedPropertiesIncludingRuleId(updatedRule);
|
||||
};
|
||||
|
||||
describe('actions without frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await updateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['300s', '5m', '3h', '4d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await updateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: { summary: true, throttle, notifyWhen: 'onThrottleInterval' },
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
[
|
||||
undefined,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'321s',
|
||||
'6m',
|
||||
'10h',
|
||||
'2d',
|
||||
].forEach((throttle) => {
|
||||
it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => {
|
||||
const actionsWithFrequencies = await getActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await updateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithFrequencies;
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await updateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['430s', '7m', '1h', '8d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and overrides frequency attribute of each action`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await updateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,8 +11,12 @@ import { RuleResponse } from '@kbn/security-solution-plugin/common/detection_eng
|
|||
import {
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
DETECTION_ENGINE_RULES_BULK_UPDATE,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
} from '@kbn/security-solution-plugin/common/constants';
|
||||
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { RuleActionArray, RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import {
|
||||
createSignalsIndex,
|
||||
|
@ -25,7 +29,16 @@ import {
|
|||
getSimpleRule,
|
||||
createLegacyRuleAction,
|
||||
getLegacyActionSO,
|
||||
removeServerGeneratedPropertiesIncludingRuleId,
|
||||
getSimpleRuleWithoutRuleId,
|
||||
getSimpleRuleOutputWithoutRuleId,
|
||||
} from '../../utils';
|
||||
import { removeUUIDFromActions } from '../../utils/remove_uuid_from_actions';
|
||||
import {
|
||||
getActionsWithFrequencies,
|
||||
getActionsWithoutFrequencies,
|
||||
getSomeActionsWithFrequencies,
|
||||
} from '../../utils/get_rule_actions';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
|
@ -138,7 +151,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
id: connector.body.id,
|
||||
action_type_id: connector.body.connector_type_id,
|
||||
params: {
|
||||
message: 'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
};
|
||||
const [rule1, rule2] = await Promise.all([
|
||||
|
@ -160,12 +173,10 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const updatedRule1 = getSimpleRuleUpdate('rule-1');
|
||||
updatedRule1.name = 'some other name';
|
||||
updatedRule1.actions = [action1];
|
||||
updatedRule1.throttle = '1d';
|
||||
|
||||
const updatedRule2 = getSimpleRuleUpdate('rule-2');
|
||||
updatedRule2.name = 'some other name';
|
||||
updatedRule2.actions = [action1];
|
||||
updatedRule2.throttle = '1d';
|
||||
|
||||
// update both rule names
|
||||
const { body }: { body: RuleResponse[] } = await supertest
|
||||
|
@ -189,13 +200,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
group: 'default',
|
||||
id: connector.body.id,
|
||||
params: {
|
||||
message:
|
||||
'Hourly\nRule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
uuid: bodyToCompare.actions[0].uuid,
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1d';
|
||||
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
@ -247,7 +257,6 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
outputRule.name = 'some other name';
|
||||
outputRule.revision = 1;
|
||||
outputRule.actions = [];
|
||||
outputRule.throttle = 'no_actions';
|
||||
const bodyToCompare = removeServerGeneratedProperties(response);
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
@ -603,5 +612,177 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulk per-action frequencies', () => {
|
||||
const bulkUpdateSingleRule = async (
|
||||
ruleId: string,
|
||||
throttle: RuleActionThrottle | undefined,
|
||||
actions: RuleActionArray
|
||||
) => {
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const ruleToUpdate = getSimpleRuleUpdate();
|
||||
ruleToUpdate.throttle = throttle;
|
||||
ruleToUpdate.actions = actions;
|
||||
ruleToUpdate.id = ruleId;
|
||||
delete ruleToUpdate.rule_id;
|
||||
|
||||
const { body } = await supertest
|
||||
.put(DETECTION_ENGINE_RULES_BULK_UPDATE)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send([ruleToUpdate])
|
||||
.expect(200);
|
||||
|
||||
const updatedRule = body[0];
|
||||
updatedRule.actions = removeUUIDFromActions(updatedRule.actions);
|
||||
return removeServerGeneratedPropertiesIncludingRuleId(updatedRule);
|
||||
};
|
||||
|
||||
describe('actions without frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it sets each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await bulkUpdateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['300s', '5m', '3h', '4d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and sets it as a frequency of each action`, async () => {
|
||||
const actionsWithoutFrequencies = await getActionsWithoutFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await bulkUpdateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithoutFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithoutFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: { summary: true, throttle, notifyWhen: 'onThrottleInterval' },
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions with frequencies', () => {
|
||||
[
|
||||
undefined,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
NOTIFICATION_THROTTLE_RULE,
|
||||
'321s',
|
||||
'6m',
|
||||
'10h',
|
||||
'2d',
|
||||
].forEach((throttle) => {
|
||||
it(`it does not change actions frequency attributes when 'throttle' is '${throttle}'`, async () => {
|
||||
const actionsWithFrequencies = await getActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await bulkUpdateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
actionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = actionsWithFrequencies;
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('some actions with frequencies', () => {
|
||||
[undefined, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE].forEach(
|
||||
(throttle) => {
|
||||
it(`it overrides each action's frequency attribute to default value when 'throttle' is ${throttle}`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await bulkUpdateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Action throttle cannot be shorter than the schedule interval which is by default is 5m
|
||||
['430s', '7m', '1h', '8d'].forEach((throttle) => {
|
||||
it(`it correctly transforms 'throttle = ${throttle}' and overrides frequency attribute of each action`, async () => {
|
||||
const someActionsWithFrequencies = await getSomeActionsWithFrequencies(supertest);
|
||||
|
||||
// create simple rule
|
||||
const createdRule = await createRule(supertest, log, getSimpleRuleWithoutRuleId());
|
||||
|
||||
// update a simple rule's `throttle` and `actions`
|
||||
const updatedRule = await bulkUpdateSingleRule(
|
||||
createdRule.id,
|
||||
throttle,
|
||||
someActionsWithFrequencies
|
||||
);
|
||||
|
||||
const expectedRule = getSimpleRuleOutputWithoutRuleId();
|
||||
expectedRule.revision = 1;
|
||||
expectedRule.actions = someActionsWithFrequencies.map((action) => ({
|
||||
...action,
|
||||
frequency: action.frequency ?? {
|
||||
summary: true,
|
||||
throttle,
|
||||
notifyWhen: 'onThrottleInterval',
|
||||
},
|
||||
}));
|
||||
|
||||
expect(updatedRule).to.eql(expectedRule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -92,7 +92,6 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial<RuleResponse> =
|
|||
'http://www.example.com/some-article-about-attack',
|
||||
'Some plain text string here explaining why this is a valid thing to look out for',
|
||||
],
|
||||
throttle: 'no_actions',
|
||||
timeline_id: 'timeline_id',
|
||||
timeline_title: 'timeline_title',
|
||||
updated_by: 'elastic',
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 SuperTest from 'supertest';
|
||||
|
||||
import { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import { getSlackAction } from './get_slack_action';
|
||||
import { getWebHookAction } from './get_web_hook_action';
|
||||
|
||||
const createConnector = async (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>,
|
||||
payload: Record<string, unknown>
|
||||
) =>
|
||||
(await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200))
|
||||
.body;
|
||||
const createWebHookConnector = (supertest: SuperTest.SuperTest<SuperTest.Test>) =>
|
||||
createConnector(supertest, getWebHookAction());
|
||||
const createSlackConnector = (supertest: SuperTest.SuperTest<SuperTest.Test>) =>
|
||||
createConnector(supertest, getSlackAction());
|
||||
|
||||
export const getActionsWithoutFrequencies = async (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>
|
||||
): Promise<RuleActionArray> => {
|
||||
const webHookAction = await createWebHookConnector(supertest);
|
||||
const slackConnector = await createSlackConnector(supertest);
|
||||
return [
|
||||
{
|
||||
group: 'default',
|
||||
id: webHookAction.id,
|
||||
action_type_id: '.webhook',
|
||||
params: { message: 'Email message' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: slackConnector.id,
|
||||
action_type_id: '.slack',
|
||||
params: { message: 'Slack message' },
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getActionsWithFrequencies = async (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>
|
||||
): Promise<RuleActionArray> => {
|
||||
const webHookAction = await createWebHookConnector(supertest);
|
||||
const slackConnector = await createSlackConnector(supertest);
|
||||
return [
|
||||
{
|
||||
group: 'default',
|
||||
id: webHookAction.id,
|
||||
action_type_id: '.webhook',
|
||||
params: { message: 'Email message' },
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: slackConnector.id,
|
||||
action_type_id: '.slack',
|
||||
params: { message: 'Slack message' },
|
||||
frequency: { summary: false, throttle: '3d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getSomeActionsWithFrequencies = async (
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>
|
||||
): Promise<RuleActionArray> => {
|
||||
const webHookAction = await createWebHookConnector(supertest);
|
||||
const slackConnector = await createSlackConnector(supertest);
|
||||
return [
|
||||
{
|
||||
group: 'default',
|
||||
id: webHookAction.id,
|
||||
action_type_id: '.webhook',
|
||||
params: { message: 'Email message' },
|
||||
frequency: { summary: true, throttle: null, notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: slackConnector.id,
|
||||
action_type_id: '.slack',
|
||||
params: { message: 'Slack message' },
|
||||
frequency: { summary: false, throttle: '3d', notifyWhen: 'onThrottleInterval' },
|
||||
},
|
||||
{
|
||||
group: 'default',
|
||||
id: slackConnector.id,
|
||||
action_type_id: '.slack',
|
||||
params: { message: 'Slack message' },
|
||||
},
|
||||
];
|
||||
};
|
|
@ -40,7 +40,7 @@ export const getMockSharedResponseSchema = (
|
|||
tags: [],
|
||||
to: 'now',
|
||||
threat: [],
|
||||
throttle: 'no_actions',
|
||||
throttle: undefined,
|
||||
exceptions_list: [],
|
||||
version: 1,
|
||||
revision: 0,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { NOTIFICATION_DEFAULT_FREQUENCY } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { getSimpleRuleOutput } from './get_simple_rule_output';
|
||||
import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties';
|
||||
|
||||
|
@ -13,7 +14,6 @@ export const getSimpleRuleOutputWithWebHookAction = (
|
|||
uuid: string
|
||||
): RuleWithoutServerGeneratedProperties => ({
|
||||
...getSimpleRuleOutput(),
|
||||
throttle: 'rule',
|
||||
actions: [
|
||||
{
|
||||
action_type_id: '.webhook',
|
||||
|
@ -23,6 +23,7 @@ export const getSimpleRuleOutputWithWebHookAction = (
|
|||
body: '{}',
|
||||
},
|
||||
uuid,
|
||||
frequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
export const removeUUIDFromActions = (actions: RuleActionArray): RuleActionArray => {
|
||||
return actions.map(({ uuid, ...restOfAction }) => ({
|
||||
...restOfAction,
|
||||
}));
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue