From 15db4e4455fc106b80060f144809d683f30966f4 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 28 Mar 2024 21:20:31 -0400 Subject: [PATCH 01/36] exposes alerting config setting and creates form compoenent --- x-pack/plugins/alerting/public/plugin.ts | 21 ++++- x-pack/plugins/alerting/server/index.ts | 8 +- x-pack/plugins/alerting/server/plugin.ts | 1 + .../server/rules_client/rules_client.ts | 4 + .../alerting/server/rules_client/types.ts | 3 +- .../alerting/server/rules_client_factory.ts | 6 +- .../components/description_step/index.tsx | 3 + .../components/max_signals/index.tsx | 80 +++++++++++++++++++ .../components/max_signals/translations.ts | 24 ++++++ .../step_about_rule/default_value.ts | 1 + .../components/step_about_rule/index.test.tsx | 2 + .../components/step_about_rule/index.tsx | 13 +++ .../components/step_about_rule/schema.tsx | 11 +++ .../step_about_rule/translations.ts | 7 ++ .../pages/rule_creation/helpers.test.ts | 9 +++ .../pages/rule_creation/helpers.ts | 3 + .../pages/rule_editing/index.tsx | 1 - .../rule_details/rule_about_section.tsx | 17 ++++ .../components/rule_details/translations.ts | 7 ++ .../components/rules_table/__mocks__/mock.ts | 1 + .../detection_engine/rules/helpers.test.tsx | 1 + .../pages/detection_engine/rules/helpers.tsx | 2 + .../pages/detection_engine/rules/types.ts | 2 + .../pages/detection_engine/rules/utils.ts | 1 + .../plugins/security_solution/public/types.ts | 2 + .../api/rules/create_rule/route.ts | 8 +- .../api/rules/patch_rule/route.ts | 3 +- .../api/rules/update_rule/route.ts | 8 +- .../rule_management/utils/validate.ts | 21 ++++- 29 files changed, 259 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index 5d2f96680ca90..ab91aa0473d14 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -57,6 +57,7 @@ export interface PluginSetupContract { } export interface PluginStartContract { getNavigation: (ruleId: Rule['id']) => Promise; + getMaxAlertsPerRun: () => number | undefined; } export interface AlertingPluginSetup { management: ManagementSetup; @@ -69,13 +70,28 @@ export interface AlertingPluginStart { data: DataPublicPluginStart; } +export interface AlertingUIConfig { + rules: { + run: { + alerts: { + max: number; + }; + }; + }; +} + export class AlertingPublicPlugin implements Plugin { private alertNavigationRegistry?: AlertNavigationRegistry; + private config: AlertingUIConfig; + readonly maxAlertsPerRun?: number; - constructor(private readonly initContext: PluginInitializerContext) {} + constructor(private readonly initContext: PluginInitializerContext) { + this.config = this.initContext.config.get(); + this.maxAlertsPerRun = this.config.rules.run.alerts.max; + } public setup(core: CoreSetup, plugins: AlertingPluginSetup) { this.alertNavigationRegistry = new AlertNavigationRegistry(); @@ -150,6 +166,9 @@ export class AlertingPublicPlugin return rule.viewInAppRelativeUrl; } }, + getMaxAlertsPerRun: () => { + return this.maxAlertsPerRun; + }, }; } } diff --git a/x-pack/plugins/alerting/server/index.ts b/x-pack/plugins/alerting/server/index.ts index d27e6976b8b7a..3906e5dcd939d 100644 --- a/x-pack/plugins/alerting/server/index.ts +++ b/x-pack/plugins/alerting/server/index.ts @@ -7,8 +7,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import { RulesClient as RulesClientClass } from './rules_client'; -import { configSchema } from './config'; -import { AlertsConfigType } from './types'; +import { AlertingConfig, configSchema } from './config'; export type RulesClient = PublicMethodsOf; @@ -78,8 +77,11 @@ export const plugin = async (initContext: PluginInitializerContext) => { return new AlertingPlugin(initContext); }; -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { schema: configSchema, + exposeToBrowser: { + rules: { run: { alerts: { max: true } } }, + }, deprecations: ({ renameFromRoot, deprecate }) => [ renameFromRoot('xpack.alerts.healthCheck', 'xpack.alerting.healthCheck', { level: 'warning' }), renameFromRoot( diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index f5850abf5f99f..cdade7f189cf3 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -508,6 +508,7 @@ export class AlertingPlugin { getAlertIndicesAlias: createGetAlertIndicesAliasFn(this.ruleTypeRegistry!), alertsService: this.alertsService, uiSettings: core.uiSettings, + maxAlertsPerRun: this.config.rules.run.alerts.max, }); rulesSettingsClientFactory.initialize({ diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 39b4353525f7e..276b1b9b46413 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -193,6 +193,10 @@ export class RulesClient { return this.context.auditLogger; } + public getMaxAlertsPerRun() { + return this.context.maxAlertsPerRun; + } + public getTags = (params: RuleTagsParams) => getRuleTags(this.context, params); public getScheduleFrequency = () => getScheduleFrequency(this.context); diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 6b3abf6fe3a38..4f5892b815ecd 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -32,7 +32,7 @@ import { RawRuleAlertsFilter, } from '../types'; import { AlertingAuthorization } from '../authorization'; -import { AlertingRulesConfig } from '../config'; +import { ActionsConfig, AlertingRulesConfig } from '../config'; import { GetAlertIndicesAlias } from '../lib'; import { AlertsService } from '../alerts_service'; @@ -80,6 +80,7 @@ export interface RulesClientContext { readonly getAlertIndicesAlias: GetAlertIndicesAlias; readonly alertsService: AlertsService | null; readonly uiSettings: UiSettingsServiceStart; + readonly maxAlertsPerRun: ActionsConfig['max']; } export type NormalizedAlertAction = Omit; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index 66ee93a1f4aeb..e834af4b6864b 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -26,7 +26,7 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { RuleTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { RulesClient } from './rules_client'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; -import { AlertingRulesConfig } from './config'; +import { ActionsConfig, AlertingRulesConfig } from './config'; import { GetAlertIndicesAlias } from './lib'; import { AlertsService } from './alerts_service/alerts_service'; import { RULE_SAVED_OBJECT_TYPE } from './saved_objects'; @@ -50,6 +50,7 @@ export interface RulesClientFactoryOpts { getAlertIndicesAlias: GetAlertIndicesAlias; alertsService: AlertsService | null; uiSettings: CoreStart['uiSettings']; + maxAlertsPerRun: ActionsConfig['max']; } export class RulesClientFactory { @@ -73,6 +74,7 @@ export class RulesClientFactory { private getAlertIndicesAlias!: GetAlertIndicesAlias; private alertsService!: AlertsService | null; private uiSettings!: CoreStart['uiSettings']; + private maxAlertsPerRun!: ActionsConfig['max']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { @@ -98,6 +100,7 @@ export class RulesClientFactory { this.getAlertIndicesAlias = options.getAlertIndicesAlias; this.alertsService = options.alertsService; this.uiSettings = options.uiSettings; + this.maxAlertsPerRun = options.maxAlertsPerRun; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { @@ -129,6 +132,7 @@ export class RulesClientFactory { getAlertIndicesAlias: this.getAlertIndicesAlias, alertsService: this.alertsService, uiSettings: this.uiSettings, + maxAlertsPerRun: this.maxAlertsPerRun, async getUserName() { if (!securityPluginStart) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx index 9f377755c769e..0a7f74e6387d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx @@ -337,6 +337,9 @@ export const getDescriptionItem = ( return get('isBuildingBlock', data) ? [{ title: i18n.BUILDING_BLOCK_LABEL, description: i18n.BUILDING_BLOCK_DESCRIPTION }] : []; + } else if (field === 'maxSignals') { + const value: number = get(field, data); + return [{ title: label, description: value }]; } const description: string = get(field, data); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx new file mode 100644 index 0000000000000..ac4878a9d5c6b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -0,0 +1,80 @@ +/* + * 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 React, { useMemo, useCallback } from 'react'; +import type { EuiFieldNumberProps } from '@elastic/eui'; +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; + +import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import * as i18n from './translations'; +import { useKibana } from '../../../../common/lib/kibana'; + +interface MaxSignalsFieldProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + isDisabled: boolean; + placeholder?: string; +} + +export const MaxSignals: React.FC = ({ + dataTestSubj, + field, + idAria, + isDisabled, + placeholder, +}): JSX.Element => { + const { setValue, value } = field; + const { alerting } = useKibana().services; + const maxAlertsPerRun = alerting.getMaxAlertsPerRun() ?? 1000; // Defaults to 1000 in the alerting framework config + + const [isInvalid, error] = useMemo(() => { + if (typeof value === 'number' && !isNaN(value)) { + if (value <= 0) { + return [true, i18n.GREATER_THAN_ERROR]; + } else if (value > maxAlertsPerRun) { + return [true, i18n.LESS_THAN_ERROR(maxAlertsPerRun)]; + } + } + return [false]; + }, [maxAlertsPerRun, value]); + + const handleMaxSignalsChange: EuiFieldNumberProps['onChange'] = useCallback( + (e) => { + const maxSignalsValue = (e.target as HTMLInputElement).value; + // Has to handle an empty string as the field is optional + setValue(maxSignalsValue !== '' ? Number(maxSignalsValue.trim()) : ''); + }, + [setValue] + ); + + return ( + + + + ); +}; + +MaxSignals.displayName = 'MaxSignals'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts new file mode 100644 index 0000000000000..f95ddf36646a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -0,0 +1,24 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const GREATER_THAN_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxSignalsFieldGreaterThanError', + { + defaultMessage: 'Max signals must be greater than 0.', + } +); + +export const LESS_THAN_ERROR = (maxNumber: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxSignalsFieldLessThanError', + { + values: { maxNumber }, + defaultMessage: 'Max signals must be less than {maxNumber}.', + } + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts index 91057eb3ff5f8..01baad19d3d1f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts @@ -33,4 +33,5 @@ export const stepAboutDefaultValue: AboutStepRule = { timestampOverride: '', threat: threatDefault, note: '', + maxSignals: 100, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx index d654eaef9cca7..4ab411af6b523 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx @@ -281,6 +281,7 @@ describe('StepAboutRuleComponent', () => { }, ], investigationFields: [], + maxSignals: 100, }; await act(async () => { @@ -341,6 +342,7 @@ describe('StepAboutRuleComponent', () => { }, ], investigationFields: [], + maxSignals: 100, }; await act(async () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index 839618669dc06..ec0874c1caa54 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -40,6 +40,7 @@ import { useRuleIndices } from '../../../rule_management/logic/use_rule_indices' import { EsqlAutocomplete } from '../esql_autocomplete'; import { MultiSelectFieldsAutocomplete } from '../multi_select_fields'; import { useInvestigationFields } from '../../hooks/use_investigation_fields'; +import { MaxSignals } from '../max_signals'; const CommonUseField = getUseField({ component: Field }); @@ -315,6 +316,18 @@ const StepAboutRuleComponent: FC = ({ /> + + + + {isThreatMatchRuleValue && ( <> = { ), labelAppend: OptionalFieldLabel, }, + maxSignals: { + type: FIELD_TYPES.NUMBER, + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxSignalsHelpText', + { + defaultMessage: + 'The maximum number of alerts a rule will create per execution. Defaults to 100.', + } + ), + labelAppend: OptionalFieldLabel, + }, isAssociatedToEndpointList: { type: FIELD_TYPES.CHECKBOX, label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts index 007cf4d9dd4c6..e7adbb30097c4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts @@ -90,3 +90,10 @@ export const ADD_RULE_NOTE_HELP_TEXT = i18n.translate( defaultMessage: 'Add rule investigation guide...', } ); + +export const MAX_SIGNALS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.maxSignalsLabel', + { + defaultMessage: 'Max signals', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index 86bcebb72ded5..f6019fc78c10a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -556,6 +556,7 @@ describe('helpers', () => { tags: ['tag1', 'tag2'], threat: getThreatMock(), investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -637,6 +638,7 @@ describe('helpers', () => { tags: ['tag1', 'tag2'], threat: getThreatMock(), investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -662,6 +664,7 @@ describe('helpers', () => { tags: ['tag1', 'tag2'], threat: getThreatMock(), investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -706,6 +709,7 @@ describe('helpers', () => { tags: ['tag1', 'tag2'], threat: getThreatMock(), investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -759,6 +763,7 @@ describe('helpers', () => { }, ], investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -788,6 +793,7 @@ describe('helpers', () => { timestamp_override: 'event.ingest', timestamp_override_fallback_disabled: true, investigation_fields: { field_names: ['foo', 'bar'] }, + max_signals: 100, }; expect(result).toEqual(expected); @@ -818,6 +824,7 @@ describe('helpers', () => { timestamp_override_fallback_disabled: undefined, threat: getThreatMock(), investigation_fields: undefined, + max_signals: 100, }; expect(result).toEqual(expected); @@ -847,6 +854,7 @@ describe('helpers', () => { threat_indicator_path: undefined, timestamp_override: undefined, timestamp_override_fallback_disabled: undefined, + max_signals: 100, }; expect(result).toEqual(expected); @@ -876,6 +884,7 @@ describe('helpers', () => { threat_indicator_path: undefined, timestamp_override: undefined, timestamp_override_fallback_disabled: undefined, + max_signals: 100, }; expect(result).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 924dc4a62fd70..64290bf2dcb95 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -25,6 +25,7 @@ import type { Type, } from '@kbn/securitysolution-io-ts-alerting-types'; import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; import { assertUnreachable } from '../../../../../common/utility_types'; import { transformAlertToRuleAction, @@ -556,6 +557,7 @@ export const formatAboutStepData = ( threat, isAssociatedToEndpointList, isBuildingBlock, + maxSignals, note, ruleNameOverride, threatIndicatorPath, @@ -610,6 +612,7 @@ export const formatAboutStepData = ( timestamp_override: timestampOverride !== '' ? timestampOverride : undefined, timestamp_override_fallback_disabled: timestampOverrideFallbackDisabled, ...(!isEmpty(note) ? { note } : {}), + max_signals: maxSignals ? maxSignals : DEFAULT_MAX_SIGNALS, ...rest, }; return resp; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index ff96fd64f027f..2df8021ada74c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -413,7 +413,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => { rule?.exceptions_list ), ...(ruleId ? { id: ruleId } : {}), - ...(rule != null ? { max_signals: rule.max_signals } : {}), }); displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx index cc54a5bd30a87..7f52bae481160 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_about_section.tsx @@ -240,6 +240,16 @@ const TimestampOverride = ({ timestampOverride }: TimestampOverrideProps) => ( ); +interface MaxSignalsProps { + maxSignals: number; +} + +const MaxSignals = ({ maxSignals }: MaxSignalsProps) => ( + + {maxSignals} + +); + interface TagsProps { tags: string[]; } @@ -414,6 +424,13 @@ const prepareAboutSectionListItems = ( }); } + if (rule.max_signals) { + aboutSectionListItems.push({ + title: {i18n.MAX_SIGNALS_FIELD_LABEL}, + description: , + }); + } + if (rule.tags && rule.tags.length > 0) { aboutSectionListItems.push({ title: {i18n.TAGS_FIELD_LABEL}, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 9bc90aee27717..377c9e0e3ff7a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -342,3 +342,10 @@ export const FROM_FIELD_LABEL = i18n.translate( defaultMessage: 'Additional look-back time', } ); + +export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.maxSignalsFieldLabel', + { + defaultMessage: 'Max signals', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts index 4c23c14871067..caa6a1fd26aef 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts @@ -198,6 +198,7 @@ export const mockAboutStepRule = (): AboutStepRule => ({ threat: getThreatMock(), note: '# this is some markdown documentation', investigationFields: ['foo', 'bar'], + maxSignals: 100, }); export const mockActionsStepRule = (enabled = false): ActionsStepRule => ({ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index 2ffedcdc55568..d1e4ee973d6c3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -146,6 +146,7 @@ describe('rule helpers', () => { timestampOverride: 'event.ingested', timestampOverrideFallbackDisabled: false, investigationFields: [], + maxSignals: 100, }; const scheduleRuleStepData = { from: '0s', interval: '5m' }; const ruleActionsStepData = { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 96a3b17a77871..e51caa66db41d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -240,6 +240,7 @@ export const getAboutStepsData = (rule: RuleResponse, detailsView: boolean): Abo investigation_fields: investigationFields, tags, threat, + max_signals: maxSignals, } = rule; const threatIndicatorPath = 'threat_indicator_path' in rule ? rule.threat_indicator_path : undefined; @@ -272,6 +273,7 @@ export const getAboutStepsData = (rule: RuleResponse, detailsView: boolean): Abo investigationFields: investigationFields?.field_names ?? [], threat: threat as Threats, threatIndicatorPath, + maxSignals, }; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index f57184a3a490b..c441ebd3586bd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -101,6 +101,7 @@ export interface AboutStepRule { threatIndicatorPath?: string; threat: Threats; note: string; + maxSignals?: number; } export interface AboutStepRuleDetails { @@ -245,6 +246,7 @@ export interface AboutStepRuleJson { timestamp_override_fallback_disabled?: boolean; note?: string; investigation_fields?: InvestigationFields; + max_signals?: number; } export interface ScheduleStepRuleJson { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 9e54856b7b28c..d093188fdb7e7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -95,6 +95,7 @@ export const stepAboutDefaultValue: AboutStepRule = { note: '', threatIndicatorPath: undefined, timestampOverrideFallbackDisabled: undefined, + maxSignals: 100, }; const DEFAULT_INTERVAL = '5m'; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 16b7308aa73d9..296192561fe0e 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -57,6 +57,7 @@ import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public' import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; +import type { PluginStartContract } from '@kbn/alerting-plugin/public/plugin'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -140,6 +141,7 @@ export interface StartPlugins { expressions: ExpressionsStart; dataViewEditor: DataViewEditorStart; savedSearch: SavedSearchPublicPluginStart; + alerting: PluginStartContract; } export interface StartPluginsDependencies extends StartPlugins { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index e9c84c1c50f5c..45045054e2b2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -23,7 +23,11 @@ import { createRules } from '../../../logic/crud/create_rules'; import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; -import { transformValidate, validateResponseActionsPermissions } from '../../../utils/validate'; +import { + transformValidate, + validateMaxSignals, + validateResponseActionsPermissions, +} from '../../../utils/validate'; export const createRuleRoute = ( router: SecuritySolutionPluginRouter, @@ -81,6 +85,8 @@ export const createRuleRoute = ( } } + validateMaxSignals({ maxSignals: request.body.max_signals, rulesClient }); + const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, ml, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 5401e2361ca58..38beac5758659 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -24,7 +24,7 @@ import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { getIdError } from '../../../utils/utils'; -import { transformValidate } from '../../../utils/validate'; +import { transformValidate, validateMaxSignals } from '../../../utils/validate'; export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.versioned @@ -68,6 +68,7 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl // reject an unauthorized "promotion" to ML throwAuthzError(await mlAuthz.validateRuleType(params.type)); } + validateMaxSignals({ maxSignals: params.max_signals, rulesClient }); const existingRule = await readRules({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index 215a0827b015f..ec22bfadb780b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -24,7 +24,11 @@ import { updateRules } from '../../../logic/crud/update_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { getIdError } from '../../../utils/utils'; -import { transformValidate, validateResponseActionsPermissions } from '../../../utils/validate'; +import { + transformValidate, + validateMaxSignals, + validateResponseActionsPermissions, +} from '../../../utils/validate'; export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.versioned @@ -64,6 +68,8 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }); throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); + validateMaxSignals({ maxSignals: request.body.max_signals, rulesClient }); + checkDefaultRuleExceptionListReferences({ exceptionLists: request.body.exceptions_list }); await validateRuleDefaultExceptionList({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index bd66139529cb3..e452e0268d784 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PartialRule } from '@kbn/alerting-plugin/server'; +import type { PartialRule, RulesClient } from '@kbn/alerting-plugin/server'; import type { Rule } from '@kbn/alerting-plugin/common'; import { isEqual, xorWith } from 'lodash'; import { stringifyZodError } from '@kbn/zod-helpers'; @@ -123,3 +123,22 @@ function isQueryRulePayload(rule: RuleCreateProps | RuleUpdateProps): rule is Qu function isQueryRuleObject(rule?: RuleAlertType): rule is Rule { return rule != null && 'params' in rule && 'responseActions' in rule?.params; } + +export const validateMaxSignals = ({ + maxSignals, + rulesClient, +}: { + maxSignals: number | undefined; + rulesClient: RulesClient; +}) => { + if (maxSignals) { + if (maxSignals > rulesClient.getMaxAlertsPerRun()) { + throw new CustomHttpRequestError( + `max_signals value cannot be higher than ${rulesClient.getMaxAlertsPerRun()}`, + 400 + ); + } else if (maxSignals <= 0) { + throw new CustomHttpRequestError(`max_signals must be greater than 0`, 400); + } + } +}; From bb5a1d22a87cfcf35fe615e1a5e393bdb854235a Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 14:45:42 -0400 Subject: [PATCH 02/36] changes user-facing language to max alerts --- .../components/max_signals/translations.ts | 8 ++++---- .../rule_creation_ui/components/step_about_rule/index.tsx | 2 +- .../components/step_about_rule/schema.tsx | 8 +++++++- .../components/step_about_rule/translations.ts | 7 ------- .../components/rule_details/translations.ts | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts index f95ddf36646a1..0b03fae2c707c 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -8,17 +8,17 @@ import { i18n } from '@kbn/i18n'; export const GREATER_THAN_ERROR = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxSignalsFieldGreaterThanError', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxAlertsFieldGreaterThanError', { - defaultMessage: 'Max signals must be greater than 0.', + defaultMessage: 'Max alerts must be greater than 0.', } ); export const LESS_THAN_ERROR = (maxNumber: number) => i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxSignalsFieldLessThanError', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxAlertsFieldLessThanError', { values: { maxNumber }, - defaultMessage: 'Max signals must be less than {maxNumber}.', + defaultMessage: 'Max alerts must be less than {maxNumber}.', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index ec0874c1caa54..ab18fd15de557 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -316,7 +316,7 @@ const StepAboutRuleComponent: FC = ({ /> - + = { }, maxSignals: { type: FIELD_TYPES.NUMBER, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.fieldMaxAlertsLabel', + { + defaultMessage: 'Max alerts', + } + ), helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxSignalsHelpText', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxAlertsHelpText', { defaultMessage: 'The maximum number of alerts a rule will create per execution. Defaults to 100.', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts index e7adbb30097c4..007cf4d9dd4c6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/translations.ts @@ -90,10 +90,3 @@ export const ADD_RULE_NOTE_HELP_TEXT = i18n.translate( defaultMessage: 'Add rule investigation guide...', } ); - -export const MAX_SIGNALS = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.maxSignalsLabel', - { - defaultMessage: 'Max signals', - } -); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 377c9e0e3ff7a..74188a04aca93 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -344,8 +344,8 @@ export const FROM_FIELD_LABEL = i18n.translate( ); export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( - 'xpack.securitySolution.detectionEngine.ruleDetails.maxSignalsFieldLabel', + 'xpack.securitySolution.detectionEngine.ruleDetails.maxAlertsFieldLabel', { - defaultMessage: 'Max signals', + defaultMessage: 'Max alerts', } ); From 8015b8487469eefe8c0f791d239c064ad663c14b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 17:38:08 -0400 Subject: [PATCH 03/36] adds tests --- .../alerting/server/rules_client.mock.ts | 1 + .../server/rules_client_factory.test.ts | 2 + .../description_step/index.test.tsx | 17 +++- .../pages/rule_creation/helpers.test.ts | 30 ++++++++ .../rule_management/utils/validate.test.ts | 33 +++++++- .../rule_management/utils/validate.ts | 2 +- .../create_rules.ts | 56 ++++++++++++++ .../patch_rules.ts | 74 ++++++++++++++++++ .../update_rules.ts | 77 +++++++++++++++++++ 9 files changed, 289 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 0b4122e221ca5..629db9f954df4 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -54,6 +54,7 @@ const createRulesClientMock = () => { getAlertFromRaw: jest.fn(), getScheduleFrequency: jest.fn(), bulkUntrackAlerts: jest.fn(), + getMaxAlertsPerRun: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index b02321c5da1d1..8134aa1c72dc8 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -29,6 +29,7 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { mockRouter } from '@kbn/core-http-router-server-mocks'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from './saved_objects'; +import { DEFAULT_MAX_ALERTS } from './config'; jest.mock('./rules_client'); jest.mock('./authorization/alerting_authorization'); @@ -62,6 +63,7 @@ const rulesClientFactoryParams: jest.Mocked = { uiSettings: uiSettingsServiceMock.createStartContract(), getAlertIndicesAlias: jest.fn(), alertsService: null, + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const actionsAuthorization = actionsAuthorizationMock.create(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index c4770a1640704..bbb8f41375ea7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -263,7 +263,7 @@ describe('description_step', () => { mockLicenseService ); - expect(result.length).toEqual(12); + expect(result.length).toEqual(13); }); }); @@ -753,6 +753,21 @@ describe('description_step', () => { }); }); }); + + describe('maxSignals', () => { + test('returns default "max signals" description', () => { + const result: ListItems[] = getDescriptionItem( + 'maxSignals', + 'Max alerts', + mockAboutStep, + mockFilterManager, + mockLicenseService + ); + + expect(result[0].title).toEqual('Max alerts'); + expect(result[0].description).toEqual(100); + }); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index f6019fc78c10a..5bd07317686c9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -889,6 +889,36 @@ describe('helpers', () => { expect(result).toEqual(expected); }); + + test('returns formatted object with default max_signals value if maxSignals is undefined', () => { + const mockStepData: AboutStepRule = { + ...mockData, + maxSignals: undefined, + }; + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { + author: ['Elastic'], + description: '24/7', + false_positives: ['test'], + license: 'Elastic License', + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + risk_score_mapping: [], + severity: 'low', + severity_mapping: [], + tags: ['tag1', 'tag2'], + threat: getThreatMock(), + investigation_fields: { field_names: ['foo', 'bar'] }, + threat_indicator_path: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + max_signals: 100, + }; + + expect(result).toEqual(expected); + }); }); describe('formatActionsStepData', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index 8fa275c7ba59f..db717abec2acc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -5,13 +5,15 @@ * 2.0. */ -import { transformValidate, transformValidateBulkError } from './validate'; +import { transformValidate, transformValidateBulkError, validateMaxSignals } from './validate'; import type { BulkError } from '../../routes/utils'; import { getRuleMock } from '../../routes/__mocks__/request_responses'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../../rule_schema/mocks'; import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema'; +import { requestContextMock } from '../../routes/__mocks__'; +import { CustomHttpRequestError } from '../../../../utils/custom_http_request_error'; export const ruleOutput = (): RuleResponse => ({ actions: [], @@ -82,6 +84,7 @@ export const ruleOutput = (): RuleResponse => ({ }); describe('validate', () => { + let { clients } = requestContextMock.createTools(); describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); @@ -136,4 +139,32 @@ describe('validate', () => { expect(validatedOrError).toEqual(expected); }); }); + + describe('validateMaxSignals', () => { + beforeEach(() => { + ({ clients } = requestContextMock.createTools()); + clients.rulesClient.getMaxAlertsPerRun.mockReturnValue(1000); + }); + + test('it should throw an error when max_signals has a value less than 1', () => { + const validation = () => { + validateMaxSignals({ maxSignals: 0, rulesClient: clients.rulesClient }); + }; + expect(validation).toThrow(CustomHttpRequestError); + }); + + test('it should throw an error when max_signals is greater than alerting defined max alerts', () => { + const validation = () => { + validateMaxSignals({ maxSignals: 1001, rulesClient: clients.rulesClient }); + }; + expect(validation).toThrow(CustomHttpRequestError); + }); + + test('it should not throw an error when max_signals is undefiend', () => { + const validation = () => { + validateMaxSignals({ maxSignals: undefined, rulesClient: clients.rulesClient }); + }; + expect(validation).not.toThrow(CustomHttpRequestError); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index e452e0268d784..c06c8dc4fa41e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -131,7 +131,7 @@ export const validateMaxSignals = ({ maxSignals: number | undefined; rulesClient: RulesClient; }) => { - if (maxSignals) { + if (maxSignals !== undefined) { if (maxSignals > rulesClient.getMaxAlertsPerRun()) { throw new CustomHttpRequestError( `max_signals value cannot be higher than ${rulesClient.getMaxAlertsPerRun()}`, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts index 19b3188a4b8ad..7a6974836ae91 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts @@ -482,6 +482,62 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('max_signals', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('creates a rule with max_signals', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: getCustomQueryRuleParams({ + max_signals: 200, + }), + }) + .expect(200); + + expect(body.max_signals).toEqual(200); + }); + + it('creates a rule with max_signals defaulted to 100 when not present', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: getCustomQueryRuleParams(), + }) + .expect(200); + + expect(body.max_signals).toEqual(100); + }); + + it('does NOT create a rule when max_signals is less than 1', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: { + ...getCustomQueryRuleParams(), + max_signals: 0, + }, + }) + .expect(400); + + expect(body.message).toBe( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + + it('does NOT create a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: { + ...getCustomQueryRuleParams(), + max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 + }, + }) + .expect(400); + + expect(body.message).toBe('max_signals value cannot be higher than 1000'); + }); + }); + describe('@brokenInServerless missing timestamps', () => { beforeEach(async () => { await es.indices.delete({ index: 'myfakeindex-1', ignore_unavailable: true }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts index edd84f6c86650..a27102a81ff8f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts @@ -41,6 +41,7 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); const log = getService('log'); const es = getService('es'); // TODO: add a new service for pulling kibana username, similar to getService('es') @@ -656,5 +657,78 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('max signals', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should overwrite max_signals field on patch', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const rulePatch = { + rule_id: 'rule-1', + max_signals: 200, + }; + + const { body } = await securitySolutionApi + .patchRule({ + body: rulePatch, + }) + .expect(200); + + expect(body.max_signals).to.eql(200); + }); + + it('does NOT patch a rule when max_signals is less than 1', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const rulePatch = { + rule_id: 'rule-1', + max_signals: 0, + }; + + const { body } = await securitySolutionApi + .patchRule({ + body: rulePatch, + }) + .expect(400); + + expect(body.message).to.be( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + + it('does NOT create a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const rulePatch = { + rule_id: 'rule-1', + max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 + }; + + const { body } = await securitySolutionApi + .patchRule({ + body: rulePatch, + }) + .expect(400); + + expect(body.message).to.be('max_signals value cannot be higher than 1000'); + }); + }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts index fe59a0127bb82..1f90620373d36 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts @@ -757,6 +757,83 @@ export default ({ getService }: FtrProviderContext) => { expect(body.investigation_fields).to.eql(undefined); }); }); + + describe('max signals', () => { + beforeEach(async () => { + await createAlertsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteAllAlerts(supertest, log, es); + await deleteAllRules(supertest, log); + }); + + it('should overwrite max_signals field on update', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const ruleUpdate = { + ...getSimpleRuleUpdate('rule-1'), + max_signals: 200, + }; + + const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(200); + + expect(body.max_signals).to.eql(200); + }); + + it('should reset max_signals field to default value on update when not present', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 200, + }); + + const ruleUpdate = { + ...getSimpleRuleUpdate('rule-1'), + max_signals: undefined, + }; + + const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(200); + + expect(body.max_signals).to.eql(100); + }); + + it('does NOT update a rule when max_signals is less than 1', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const ruleUpdate = { + ...getSimpleRuleUpdate('rule-1'), + max_signals: 0, + }; + + const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(400); + + expect(body.message).to.be( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + + it('does NOT update a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + max_signals: 100, + }); + + const ruleUpdate = { + ...getSimpleRuleUpdate('rule-1'), + max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 + }; + + const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(400); + + expect(body.message).to.be('max_signals value cannot be higher than 1000'); + }); + }); }); }); }; From 264cdb6bf09d8ee589e54aa290ca0317c2715961 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 17:46:26 -0400 Subject: [PATCH 04/36] updates language --- .../rule_creation_ui/components/step_about_rule/schema.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx index 6b9a93261f685..b8b23b9c72dad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx @@ -112,7 +112,7 @@ export const schema: FormSchema = { 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxAlertsHelpText', { defaultMessage: - 'The maximum number of alerts a rule will create per execution. Defaults to 100.', + 'The maximum number of alerts the rule will create each time it runs. Default is 100.', } ), labelAppend: OptionalFieldLabel, From 69dc9367a83e75d0525dca244df6a38acd0be6cd Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 18:37:48 -0400 Subject: [PATCH 05/36] adds param to one million mock constructors --- x-pack/plugins/alerting/public/mocks.ts | 1 + .../application/rule/methods/aggregate/aggregate_rules.test.ts | 2 ++ .../rule/methods/bulk_delete/bulk_delete_rules.test.ts | 2 ++ .../rule/methods/bulk_disable/bulk_disable_rules.test.ts | 2 ++ .../application/rule/methods/bulk_edit/bulk_edit_rules.test.ts | 2 ++ .../rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts | 2 ++ .../server/application/rule/methods/create/create_rule.test.ts | 2 ++ .../get_schedule_frequency/get_schedule_frequency.test.ts | 2 ++ .../server/application/rule/methods/resolve/resolve.test.ts | 2 ++ .../server/application/rule/methods/tags/get_rule_tags.test.ts | 2 ++ .../server/rules_client/lib/add_generated_action_values.test.ts | 2 ++ .../server/rules_client/lib/create_new_api_key_set.test.ts | 2 ++ .../alerting/server/rules_client/tests/bulk_enable.test.ts | 2 ++ .../server/rules_client/tests/clear_expired_snoozes.test.ts | 2 ++ x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/delete.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/disable.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/enable.test.ts | 2 ++ x-pack/plugins/alerting/server/rules_client/tests/find.test.ts | 2 ++ x-pack/plugins/alerting/server/rules_client/tests/get.test.ts | 2 ++ .../server/rules_client/tests/get_action_error_log.test.ts | 2 ++ .../alerting/server/rules_client/tests/get_alert_state.test.ts | 2 ++ .../server/rules_client/tests/get_alert_summary.test.ts | 2 ++ .../server/rules_client/tests/get_execution_log.test.ts | 2 ++ .../alerting/server/rules_client/tests/list_rule_types.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/mute_all.test.ts | 2 ++ .../alerting/server/rules_client/tests/mute_instance.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/resolve.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/run_soon.test.ts | 2 ++ .../alerting/server/rules_client/tests/unmute_all.test.ts | 2 ++ .../alerting/server/rules_client/tests/unmute_instance.test.ts | 2 ++ .../plugins/alerting/server/rules_client/tests/update.test.ts | 2 ++ .../alerting/server/rules_client/tests/update_api_key.test.ts | 2 ++ .../alerting/server/rules_client_conflict_retries.test.ts | 2 ++ 34 files changed, 67 insertions(+) diff --git a/x-pack/plugins/alerting/public/mocks.ts b/x-pack/plugins/alerting/public/mocks.ts index 8ed08c7e57404..977447f29f365 100644 --- a/x-pack/plugins/alerting/public/mocks.ts +++ b/x-pack/plugins/alerting/public/mocks.ts @@ -17,6 +17,7 @@ const createSetupContract = (): Setup => ({ const createStartContract = (): Start => ({ getNavigation: jest.fn(), + getMaxAlertsPerRun: jest.fn(), }); export const alertingPluginMock = { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts index 858bc6ded1cb3..f1fddb274bbc4 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts @@ -29,6 +29,7 @@ import { DefaultRuleAggregationResult } from '../../../../routes/rule/apis/aggre import { defaultRuleAggregationFactory } from '.'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -66,6 +67,7 @@ const rulesClientParams: jest.Mocked = { internalSavedObjectsRepository, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts index 6ccd32879a171..35e9031a274f6 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts @@ -41,6 +41,7 @@ import { import { migrateLegacyActions } from '../../../../rules_client/lib'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -95,6 +96,7 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const getBulkOperationStatusErrorResponse = (statusCode: number) => ({ diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts index be64257c86f8c..fc42b4164849b 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts @@ -46,6 +46,7 @@ import { migrateLegacyActions } from '../../../../rules_client/lib'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { ActionsClient } from '@kbn/actions-plugin/server'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -107,6 +108,7 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index bc8ca1606e43e..da9dc5dc31fe5 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -41,6 +41,7 @@ import { RuleAttributes } from '../../../../data/rule/types'; import { SavedObject } from '@kbn/core/server'; import { bulkEditOperationsSchema } from './schemas'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -113,6 +114,7 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const paramsModifier = jest.fn(); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts index f2de0ed7840dd..87e341d597679 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts @@ -23,6 +23,7 @@ import { alertsServiceMock } from '../../../../alerts_service/alerts_service.moc import { ALERT_RULE_UUID, ALERT_UUID } from '@kbn/rule-data-utils'; import { ConcreteTaskInstance, TaskStatus } from '@kbn/task-manager-plugin/server'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -64,6 +65,7 @@ const rulesClientParams: jest.Mocked = { uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), connectorAdapterRegistry: new ConnectorAdapterRegistry(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; describe('bulkUntrackAlerts()', () => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index 7d1fe07d8a7a4..9f3d499ab5c75 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -33,6 +33,7 @@ import { ConnectorAdapter } from '../../../../connector_adapters/types'; import { RuleDomain } from '../../types'; import { RuleSystemAction } from '../../../../types'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -94,6 +95,7 @@ const rulesClientParams: jest.Mocked = { connectorAdapterRegistry, isSystemAction: jest.fn(), uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts index a0b44abde3c54..959641930ed6b 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts @@ -22,6 +22,7 @@ import { AlertingAuthorization } from '../../../../authorization/alerting_author import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,6 +61,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const getMockAggregationResult = ( diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts index b611f5f2d3ef5..4ff06ed5450e4 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts @@ -23,6 +23,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; import { getBeforeSetup } from '../../../../rules_client/tests/lib'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; describe('resolve', () => { const taskManager = taskManagerMock.createStart(); @@ -65,6 +66,7 @@ describe('resolve', () => { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; let rulesClient: RulesClient; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts index e6c99f779909d..610e6648f1518 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts @@ -25,6 +25,7 @@ import { RecoveredActionGroup } from '../../../../../common'; import { RegistryRuleType } from '../../../../rule_type_registry'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -62,6 +63,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const listedTypes = new Set([ diff --git a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts index 871debb9b4e9c..7fbad3ae936cf 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts @@ -22,6 +22,7 @@ import { alertingAuthorizationMock } from '../../authorization/alerting_authoriz import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ConstructorOptions } from '../rules_client'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('uuid', () => ({ v4: () => '111-222', @@ -65,6 +66,7 @@ describe('addGeneratedActionValues()', () => { uiSettings: uiSettingsServiceMock.createStartContract(), connectorAdapterRegistry: new ConnectorAdapterRegistry(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const mockAction: RuleAction = { diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts index 93b43545b9747..cb6fbb57ffebb 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -23,6 +23,7 @@ import { getBeforeSetup, mockedDateString } from '../tests/lib'; import { createNewAPIKeySet } from './create_new_api_key_set'; import { RulesClientContext } from '../types'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -59,6 +60,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const username = 'test'; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index 64a243403c406..8639c46d38255 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -46,6 +46,7 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { migrateLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -99,6 +100,7 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts index a5b691894b77c..174f7798b13bc 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts @@ -28,6 +28,7 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { RuleSnooze } from '../../types'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -77,6 +78,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; describe('clearExpiredSnoozes()', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts index a23d9b159d79b..a144d28a067a0 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts @@ -24,6 +24,7 @@ import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; import { getBeforeSetup } from './lib'; import { RuleDomain } from '../../application/rule/types'; import { ConstructorOptions, RulesClient } from '../rules_client'; +import { DEFAULT_MAX_ALERTS } from '../../config'; describe('clone', () => { const taskManager = taskManagerMock.createStart(); @@ -66,6 +67,7 @@ describe('clone', () => { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; let rulesClient: RulesClient; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index 20ea58cad66ab..9ac71ee08da31 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -27,6 +27,7 @@ import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_key import { migrateLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -79,6 +80,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 9d21fa3477fb9..767880508459d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -28,6 +28,7 @@ import { migrateLegacyActions } from '../lib'; import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -81,6 +82,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index dd48bbe5412c7..8100e209de3b4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -27,6 +27,7 @@ import { migrateLegacyActions } from '../lib'; import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -78,6 +79,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 70e9ef57b2e6a..c546cd0b022d4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -29,6 +29,7 @@ import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -71,6 +72,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn().mockImplementation((id) => id === 'system_action-id'), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 1f136a0c181b3..4559639c59d4e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -26,6 +26,7 @@ import { RecoveredActionGroup } from '../../../common'; import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -68,6 +69,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index 0cf207500fdb7..e6b052784e40b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -28,6 +28,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, mockedDateString, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -67,6 +68,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts index 94db9bd1c9629..a644af78c04d2 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts @@ -24,6 +24,7 @@ import { getBeforeSetup } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,6 +61,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index 60769bc20cd6d..db9d8a227a37c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -28,6 +28,7 @@ import { RawRule } from '../../types'; import { getBeforeSetup, mockedDateString, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -65,6 +66,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index f90f5835dbcd4..24f3665a6dc4b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -29,6 +29,7 @@ import { getExecutionLogAggregation } from '../../lib/get_execution_log_aggregat import { fromKueryExpression } from '@kbn/es-query'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -68,6 +69,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts index e6f3978987c53..63b280c188eb7 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts @@ -26,6 +26,7 @@ import { getBeforeSetup } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -62,6 +63,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 7911735644403..5f9150733d1d5 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -23,6 +23,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -59,6 +60,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index 36bba7e734748..476cbe8ea5e4d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -23,6 +23,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -59,6 +60,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index bab353225b28b..6cd0c7c71263a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -26,6 +26,7 @@ import { RecoveredActionGroup } from '../../../common'; import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -68,6 +69,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index 653e7dd807c8a..d2fab3568e208 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -24,6 +24,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -61,6 +62,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index 20eae2a147dda..eb357a64097a9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -23,6 +23,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -59,6 +60,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index 8275e0a88d8ba..0ef132471d0b6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -23,6 +23,7 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -59,6 +60,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index d66b17b5ff6b6..b7ad179385f42 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -32,6 +32,7 @@ import { migrateLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; import { RuleDomain } from '../../application/rule/types'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -99,6 +100,7 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index 66881241021ef..bd42cdd0c9316 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -24,6 +24,7 @@ import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; +import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -66,6 +67,7 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index 2df24f879e982..5c9ac46e5ac1e 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -27,6 +27,7 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server/task'; import { RecoveredActionGroup } from '../common'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from './saved_objects'; +import { DEFAULT_MAX_ALERTS } from './config'; jest.mock('./application/rule/methods/get_schedule_frequency', () => ({ validateScheduleLimit: jest.fn(), @@ -74,6 +75,7 @@ const rulesClientParams: jest.Mocked = { connectorAdapterRegistry: new ConnectorAdapterRegistry(), isSystemAction: jest.fn(), uiSettings: uiSettingsServiceMock.createStartContract(), + maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; // this suite consists of two suites running tests against mutable RulesClient APIs: From ca8b4626fc48ed36c5ba34a56b66087c93694a55 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 19:31:19 -0400 Subject: [PATCH 06/36] updates types --- .../public/common/lib/kibana/kibana_react.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts index 4965ca0a0f73d..0c9143bf977ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts @@ -43,6 +43,7 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => { getNavigation: jest.fn(async (id) => id === 'alert-with-nav' ? { path: '/alert' } : undefined ), + getMaxAlertsPerRun: jest.fn(), }, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), From d396a127666a8aa87687a73a1ef1a377b52c2e5c Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 20:24:21 -0400 Subject: [PATCH 07/36] updates tests and mocks --- test/plugin_functional/test_suites/core_plugins/rendering.ts | 1 + .../public/common/lib/kibana/kibana_react.mock.ts | 3 +++ .../components/step_about_rule/index.test.tsx | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index ca06f8a6a731d..e662a9a69ee4e 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -328,6 +328,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.stack_connectors.enableExperimental (array)', 'xpack.trigger_actions_ui.enableExperimental (array)', 'xpack.trigger_actions_ui.enableGeoTrackingThresholdAlert (boolean)', + 'xpack.alerting.rules.run.alerts.max', 'xpack.upgrade_assistant.featureSet.migrateSystemIndices (boolean)', 'xpack.upgrade_assistant.featureSet.mlSnapshots (boolean)', 'xpack.upgrade_assistant.featureSet.reindexCorrectiveActions (boolean)', diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index f950d53d1212f..42b954146c668 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -58,6 +58,7 @@ import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks'; import { indexPatternFieldEditorPluginMock } from '@kbn/data-view-field-editor-plugin/public/mocks'; import { UpsellingService } from '@kbn/security-solution-upselling/service'; import { calculateBounds } from '@kbn/data-plugin/common'; +import { alertingPluginMock } from '@kbn/alerting-plugin/public/mocks'; const mockUiSettings: Record = { [DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' }, @@ -128,6 +129,7 @@ export const createStartServicesMock = ( const cloud = cloudMock.createStart(); const mockSetHeaderActionMenu = jest.fn(); const mockTimelineFilterManager = createFilterManagerMock(); + const alerting = alertingPluginMock.createStartContract(); /* * Below mocks are needed by unified field list @@ -250,6 +252,7 @@ export const createStartServicesMock = ( dataViewFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), upselling: new UpsellingService(), timelineFilterManager: mockTimelineFilterManager, + alerting, } as unknown as StartServices; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx index 4ab411af6b523..c5d1115b4a8dc 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.test.tsx @@ -34,6 +34,8 @@ import { stepDefineDefaultValue, } from '../../../../detections/pages/detection_engine/rules/utils'; import type { FormHook } from '../../../../shared_imports'; +import { useKibana as mockUseKibana } from '../../../../common/lib/kibana/__mocks__'; +import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/containers/source'); @@ -50,6 +52,7 @@ jest.mock('@elastic/eui', () => { }, }; }); +const mockedUseKibana = mockUseKibana(); export const stepDefineStepMLRule: DefineStepRule = { ruleType: 'machine_learning', @@ -118,6 +121,7 @@ describe('StepAboutRuleComponent', () => { indexPatterns: stubIndexPattern, }, ]); + (useKibana as jest.Mock).mockReturnValue(mockedUseKibana); useGetInstalledJobMock = (useGetInstalledJob as jest.Mock).mockImplementation(() => ({ jobs: [], })); From 07927d12cb837992d0bc0f3dcc54f1b422ba51e9 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 20:26:12 -0400 Subject: [PATCH 08/36] adds type --- test/plugin_functional/test_suites/core_plugins/rendering.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index e662a9a69ee4e..fb790591b3201 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -328,7 +328,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.stack_connectors.enableExperimental (array)', 'xpack.trigger_actions_ui.enableExperimental (array)', 'xpack.trigger_actions_ui.enableGeoTrackingThresholdAlert (boolean)', - 'xpack.alerting.rules.run.alerts.max', + 'xpack.alerting.rules.run.alerts.max (number)', 'xpack.upgrade_assistant.featureSet.migrateSystemIndices (boolean)', 'xpack.upgrade_assistant.featureSet.mlSnapshots (boolean)', 'xpack.upgrade_assistant.featureSet.reindexCorrectiveActions (boolean)', From 3522cb77b9d617e280af7e03e206808cbde4b589 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 2 Apr 2024 22:04:05 -0400 Subject: [PATCH 09/36] updates tests --- x-pack/plugins/alerting/public/plugin.test.ts | 14 ++++++++++++-- .../alerting/server/rules_client_factory.test.ts | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/alerting/public/plugin.test.ts b/x-pack/plugins/alerting/public/plugin.test.ts index 3d6165cf18f6e..87b7e4573c79f 100644 --- a/x-pack/plugins/alerting/public/plugin.test.ts +++ b/x-pack/plugins/alerting/public/plugin.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertingPublicPlugin } from './plugin'; +import { AlertingPublicPlugin, AlertingUIConfig } from './plugin'; import { coreMock } from '@kbn/core/public/mocks'; import { createManagementSectionMock, @@ -17,7 +17,17 @@ jest.mock('./services/rule_api', () => ({ loadRuleType: jest.fn(), })); -const mockInitializerContext = coreMock.createPluginInitializerContext(); +const mockAlertingUIConfig: AlertingUIConfig = { + rules: { + run: { + alerts: { + max: 1000, + }, + }, + }, +}; + +const mockInitializerContext = coreMock.createPluginInitializerContext(mockAlertingUIConfig); const management = managementPluginMock.createSetupContract(); const mockSection = createManagementSectionMock(); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index 8134aa1c72dc8..663068201df36 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -128,6 +128,7 @@ test('creates a rules client with proper constructor arguments when security is getAlertIndicesAlias: expect.any(Function), alertsService: null, uiSettings: rulesClientFactoryParams.uiSettings, + maxAlertsPerRun: 1000, }); }); @@ -175,6 +176,7 @@ test('creates a rules client with proper constructor arguments', async () => { getAlertIndicesAlias: expect.any(Function), alertsService: null, uiSettings: rulesClientFactoryParams.uiSettings, + maxAlertsPerRun: 1000, }); }); From 0319c949136f15a166625838dca976a406b5c45d Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 8 Apr 2024 22:41:17 -0400 Subject: [PATCH 10/36] changes logic for max validations --- .../alerting/server/rules_client/types.ts | 4 +-- .../alerting/server/rules_client_factory.ts | 6 ++-- .../request_schema_validation.test.ts | 18 ++++++++++ .../create_rule/request_schema_validation.ts | 9 +++++ .../request_schema_validation.test.ts | 18 ++++++++++ .../patch_rule/request_schema_validation.ts | 9 +++++ .../request_schema_validation.test.ts | 18 ++++++++++ .../update_rule/request_schema_validation.ts | 9 +++++ .../description_step/index.test.tsx | 4 +-- .../components/max_signals/index.tsx | 18 ++++++++-- .../components/max_signals/translations.ts | 10 ++++++ .../components/step_about_rule/index.tsx | 3 +- .../components/step_about_rule/schema.tsx | 9 +---- .../components/rule_details/translations.ts | 2 +- .../api/rules/create_rule/route.ts | 2 +- .../api/rules/patch_rule/route.ts | 2 +- .../api/rules/update_rule/route.ts | 2 +- .../rule_management/utils/validate.test.ts | 33 +------------------ .../rule_management/utils/validate.ts | 21 +++--------- .../create_rules.ts | 13 -------- .../patch_rules.ts | 20 ----------- .../update_rules.ts | 16 --------- 22 files changed, 125 insertions(+), 121 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 2bb6d8cdeb6c8..7580c49ce6d59 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -34,7 +34,7 @@ import { RuleAction, } from '../types'; import { AlertingAuthorization } from '../authorization'; -import { ActionsConfig, AlertingRulesConfig } from '../config'; +import { AlertingConfig, AlertingRulesConfig } from '../config'; import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry'; import { GetAlertIndicesAlias } from '../lib'; import { AlertsService } from '../alerts_service'; @@ -85,7 +85,7 @@ export interface RulesClientContext { readonly alertsService: AlertsService | null; readonly isSystemAction: (actionId: string) => boolean; readonly uiSettings: UiSettingsServiceStart; - readonly maxAlertsPerRun: ActionsConfig['max']; + readonly maxAlertsPerRun: AlertingConfig['rules']['run']['alerts']['max']; } export type NormalizedAlertAction = DistributiveOmit; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index d7eab42437d8d..17122822b324d 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -26,7 +26,7 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { RuleTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { RulesClient } from './rules_client'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; -import { ActionsConfig, AlertingRulesConfig } from './config'; +import { AlertingConfig, AlertingRulesConfig } from './config'; import { GetAlertIndicesAlias } from './lib'; import { AlertsService } from './alerts_service/alerts_service'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; @@ -52,7 +52,7 @@ export interface RulesClientFactoryOpts { alertsService: AlertsService | null; connectorAdapterRegistry: ConnectorAdapterRegistry; uiSettings: CoreStart['uiSettings']; - maxAlertsPerRun: ActionsConfig['max']; + maxAlertsPerRun: AlertingConfig['rules']['run']['alerts']['max']; } export class RulesClientFactory { @@ -77,7 +77,7 @@ export class RulesClientFactory { private alertsService!: AlertsService | null; private connectorAdapterRegistry!: ConnectorAdapterRegistry; private uiSettings!: CoreStart['uiSettings']; - private maxAlertsPerRun!: ActionsConfig['max']; + private maxAlertsPerRun!: AlertingConfig['rules']['run']['alerts']['max']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts index 733645dcb3aa9..9614ecf94b36c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts @@ -84,4 +84,22 @@ describe('Create rule request schema, additional validation', () => { 'when "concurrent_searches" exists, "items_per_search" must also exist', ]); }); + + test('does NOT validate when max_signals has a value less than 1', () => { + const schema: RuleCreateProps = { + ...getCreateRulesSchemaMock(), + max_signals: 0, + }; + const errors = validateCreateRuleProps(schema); + expect(errors).toEqual(['max_signals must be greater than 0']); + }); + + test('does validate when max_signals is undefiend', () => { + const schema: RuleCreateProps = { + ...getCreateRulesSchemaMock(), + max_signals: undefined, + }; + const errors = validateCreateRuleProps(schema); + expect(errors).toEqual([]); + }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts index 519ef874c422e..9006cbcf6d7fc 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts @@ -16,6 +16,7 @@ export const validateCreateRuleProps = (props: RuleCreateProps): string[] => { ...validateTimelineTitle(props), ...validateThreatMapping(props), ...validateThreshold(props), + ...validateMaxSignals(props), ]; }; @@ -77,3 +78,11 @@ const validateThreshold = (props: RuleCreateProps): string[] => { } return errors; }; + +const validateMaxSignals = (props: RuleCreateProps): string[] => { + const errors: string[] = []; + if (props.max_signals !== undefined && props.max_signals <= 0) { + errors.push('max_signals must be greater than 0'); + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts index 5a60a3331a2ae..7f0d86737776e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts @@ -113,5 +113,23 @@ describe('Patch rule request schema, additional validation', () => { const errors = validatePatchRuleRequestBody(schema); expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); }); + + test('does NOT validate when max_signals has a value less than 1', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + max_signals: 0, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['max_signals must be greater than 0']); + }); + + test('does validate when max_signals is undefiend', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + max_signals: undefined, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual([]); + }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts index cb34c7fa8ecb5..4c747b190cb64 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts @@ -16,6 +16,7 @@ export const validatePatchRuleRequestBody = (rule: PatchRuleRequestBody): string ...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule), + ...validateMaxSignals(rule), ]; }; @@ -77,3 +78,11 @@ const validateThreshold = (rule: PatchRuleRequestBody): string[] => { } return errors; }; + +const validateMaxSignals = (rule: PatchRuleRequestBody): string[] => { + const errors: string[] = []; + if (rule.max_signals !== undefined && rule.max_signals <= 0) { + errors.push('max_signals must be greater than 0'); + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts index 6bec7b1de3aba..5583872c10fef 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts @@ -69,4 +69,22 @@ describe('Update rule request schema, additional validation', () => { const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); + + test('does NOT validate when max_signals has a value less than 1', () => { + const schema: RuleUpdateProps = { + ...getUpdateRulesSchemaMock(), + max_signals: 0, + }; + const errors = validateUpdateRuleProps(schema); + expect(errors).toEqual(['max_signals must be greater than 0']); + }); + + test('does validate when max_signals is undefiend', () => { + const schema: RuleUpdateProps = { + ...getUpdateRulesSchemaMock(), + max_signals: undefined, + }; + const errors = validateUpdateRuleProps(schema); + expect(errors).toEqual([]); + }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts index d58bbdc4bee05..16ba9f132ccf5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts @@ -16,6 +16,7 @@ export const validateUpdateRuleProps = (props: RuleUpdateProps): string[] => { ...validateTimelineId(props), ...validateTimelineTitle(props), ...validateThreshold(props), + ...validateMaxSignals(props), ]; }; @@ -74,3 +75,11 @@ const validateThreshold = (props: RuleUpdateProps): string[] => { } return errors; }; + +const validateMaxSignals = (props: RuleUpdateProps): string[] => { + const errors: string[] = []; + if (props.max_signals !== undefined && props.max_signals <= 0) { + errors.push('max_signals must be greater than 0'); + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index c7fb3c42b2022..bf4df9743bed3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -773,13 +773,13 @@ describe('description_step', () => { test('returns default "max signals" description', () => { const result: ListItems[] = getDescriptionItem( 'maxSignals', - 'Max alerts', + 'Max alerts per run', mockAboutStep, mockFilterManager, mockLicenseService ); - expect(result[0].title).toEqual('Max alerts'); + expect(result[0].title).toEqual('Max alerts per run'); expect(result[0].description).toEqual(100); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index ac4878a9d5c6b..b6be955ecf4c2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -10,6 +10,8 @@ import type { EuiFieldNumberProps } from '@elastic/eui'; import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { css } from '@emotion/css'; +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; import * as i18n from './translations'; import { useKibana } from '../../../../common/lib/kibana'; @@ -21,6 +23,8 @@ interface MaxSignalsFieldProps { placeholder?: string; } +const MAX_SIGNALS_FIELD_WIDTH = 200; + export const MaxSignals: React.FC = ({ dataTestSubj, field, @@ -52,12 +56,23 @@ export const MaxSignals: React.FC = ({ [setValue] ); + const helpText = useMemo(() => { + const defaultToNumber = + maxAlertsPerRun < DEFAULT_MAX_SIGNALS ? maxAlertsPerRun : DEFAULT_MAX_SIGNALS; + return i18n.MAX_SIGNALS_HELP_TEXT(defaultToNumber); + }, [maxAlertsPerRun]); + return ( = ({ value={value as EuiFieldNumberProps['value']} onChange={handleMaxSignalsChange} isLoading={field.isValidating} - fullWidth data-test-subj="input" placeholder={placeholder} disabled={isDisabled} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts index 0b03fae2c707c..e1848d21489b5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -22,3 +22,13 @@ export const LESS_THAN_ERROR = (maxNumber: number) => defaultMessage: 'Max alerts must be less than {maxNumber}.', } ); + +export const MAX_SIGNALS_HELP_TEXT = (defaultNumber: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxAlertsHelpText', + { + values: { defaultNumber }, + defaultMessage: + 'The maximum number of alerts the rule will create each time it runs. Default is {defaultNumber}.', + } + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index da2cb1af60126..3fadc2fc6e6f3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -329,7 +329,7 @@ const StepAboutRuleComponent: FC = ({ - = ({ }} /> - {isThreatMatchRuleValue && ( <> = { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.fieldMaxAlertsLabel', { - defaultMessage: 'Max alerts', - } - ), - helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMaxAlertsHelpText', - { - defaultMessage: - 'The maximum number of alerts the rule will create each time it runs. Default is 100.', + defaultMessage: 'Max alerts per run', } ), labelAppend: OptionalFieldLabel, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 74188a04aca93..9025b184af1d3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -346,6 +346,6 @@ export const FROM_FIELD_LABEL = i18n.translate( export const MAX_SIGNALS_FIELD_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.maxAlertsFieldLabel', { - defaultMessage: 'Max alerts', + defaultMessage: 'Max alerts per run', } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 45045054e2b2c..7a5b380189215 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -85,7 +85,7 @@ export const createRuleRoute = ( } } - validateMaxSignals({ maxSignals: request.body.max_signals, rulesClient }); + validateMaxSignals(request.body.max_signals); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 38beac5758659..20d819b61c64d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -68,7 +68,7 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl // reject an unauthorized "promotion" to ML throwAuthzError(await mlAuthz.validateRuleType(params.type)); } - validateMaxSignals({ maxSignals: params.max_signals, rulesClient }); + validateMaxSignals(params.max_signals); const existingRule = await readRules({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index ec22bfadb780b..d5110ad85ec95 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -68,7 +68,7 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }); throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); - validateMaxSignals({ maxSignals: request.body.max_signals, rulesClient }); + validateMaxSignals(request.body.max_signals); checkDefaultRuleExceptionListReferences({ exceptionLists: request.body.exceptions_list }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index db717abec2acc..8fa275c7ba59f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -5,15 +5,13 @@ * 2.0. */ -import { transformValidate, transformValidateBulkError, validateMaxSignals } from './validate'; +import { transformValidate, transformValidateBulkError } from './validate'; import type { BulkError } from '../../routes/utils'; import { getRuleMock } from '../../routes/__mocks__/request_responses'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../../rule_schema/mocks'; import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema'; -import { requestContextMock } from '../../routes/__mocks__'; -import { CustomHttpRequestError } from '../../../../utils/custom_http_request_error'; export const ruleOutput = (): RuleResponse => ({ actions: [], @@ -84,7 +82,6 @@ export const ruleOutput = (): RuleResponse => ({ }); describe('validate', () => { - let { clients } = requestContextMock.createTools(); describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); @@ -139,32 +136,4 @@ describe('validate', () => { expect(validatedOrError).toEqual(expected); }); }); - - describe('validateMaxSignals', () => { - beforeEach(() => { - ({ clients } = requestContextMock.createTools()); - clients.rulesClient.getMaxAlertsPerRun.mockReturnValue(1000); - }); - - test('it should throw an error when max_signals has a value less than 1', () => { - const validation = () => { - validateMaxSignals({ maxSignals: 0, rulesClient: clients.rulesClient }); - }; - expect(validation).toThrow(CustomHttpRequestError); - }); - - test('it should throw an error when max_signals is greater than alerting defined max alerts', () => { - const validation = () => { - validateMaxSignals({ maxSignals: 1001, rulesClient: clients.rulesClient }); - }; - expect(validation).toThrow(CustomHttpRequestError); - }); - - test('it should not throw an error when max_signals is undefiend', () => { - const validation = () => { - validateMaxSignals({ maxSignals: undefined, rulesClient: clients.rulesClient }); - }; - expect(validation).not.toThrow(CustomHttpRequestError); - }); - }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index c06c8dc4fa41e..e5dbcc94fce2c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PartialRule, RulesClient } from '@kbn/alerting-plugin/server'; +import type { PartialRule } from '@kbn/alerting-plugin/server'; import type { Rule } from '@kbn/alerting-plugin/common'; import { isEqual, xorWith } from 'lodash'; import { stringifyZodError } from '@kbn/zod-helpers'; @@ -124,21 +124,8 @@ function isQueryRuleObject(rule?: RuleAlertType): rule is Rule { - if (maxSignals !== undefined) { - if (maxSignals > rulesClient.getMaxAlertsPerRun()) { - throw new CustomHttpRequestError( - `max_signals value cannot be higher than ${rulesClient.getMaxAlertsPerRun()}`, - 400 - ); - } else if (maxSignals <= 0) { - throw new CustomHttpRequestError(`max_signals must be greater than 0`, 400); - } +export const validateMaxSignals = (maxSignals?: number) => { + if (maxSignals !== undefined && maxSignals <= 0) { + throw new CustomHttpRequestError(`max_signals must be greater than 0`, 400); } }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts index 21e3055c98e11..60515854f9ab3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts @@ -523,19 +523,6 @@ export default ({ getService }: FtrProviderContext) => { '[request body]: max_signals: Number must be greater than or equal to 1' ); }); - - it('does NOT create a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { - const { body } = await securitySolutionApi - .createRule({ - body: { - ...getCustomQueryRuleParams(), - max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 - }, - }) - .expect(400); - - expect(body.message).toBe('max_signals value cannot be higher than 1000'); - }); }); describe('@brokenInServerless missing timestamps', () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts index aca8f90c8a96f..aabdb3dac9ab1 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts @@ -709,26 +709,6 @@ export default ({ getService }: FtrProviderContext) => { '[request body]: max_signals: Number must be greater than or equal to 1' ); }); - - it('does NOT create a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const rulePatch = { - rule_id: 'rule-1', - max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 - }; - - const { body } = await securitySolutionApi - .patchRule({ - body: rulePatch, - }) - .expect(400); - - expect(body.message).to.be('max_signals value cannot be higher than 1000'); - }); }); describe('setup guide', () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts index b1dc8d6251e5f..0e3c233a42aa0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts @@ -817,22 +817,6 @@ export default ({ getService }: FtrProviderContext) => { '[request body]: max_signals: Number must be greater than or equal to 1' ); }); - - it('does NOT update a rule when max_signals is greater than xpack.alerting.rules.run.alerts.max', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const ruleUpdate = { - ...getSimpleRuleUpdate('rule-1'), - max_signals: 5000, // xpack.alerting.rules.run.alerts.max defaults to 1000 - }; - - const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(400); - - expect(body.message).to.be('max_signals value cannot be higher than 1000'); - }); }); describe('setup guide', () => { From 17496310a01f85196111b4b4cc2a6c4e804ec5f9 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 9 Apr 2024 00:39:44 -0400 Subject: [PATCH 11/36] updates tests --- .../rule_creation_ui/components/description_step/index.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index bf4df9743bed3..1d73e86c97e4f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -263,7 +263,7 @@ describe('description_step', () => { mockLicenseService ); - expect(result.length).toEqual(13); + expect(result.length).toEqual(14); }); }); From 19ebcf08da67d9072ceb1854c46e04c77a6d3ccc Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 9 Apr 2024 20:57:58 -0400 Subject: [PATCH 12/36] adds warning state --- x-pack/plugins/alerting/server/config.ts | 2 +- .../request_schema_validation.test.ts | 2 +- .../request_schema_validation.test.ts | 2 +- .../request_schema_validation.test.ts | 2 +- .../components/max_signals/index.tsx | 36 +++++++++++++------ .../components/max_signals/translations.ts | 7 ++-- .../pages/rule_creation/index.tsx | 16 +++++++-- 7 files changed, 47 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index a49c393da1e95..ef97b6b4a9fa7 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { validateDurationSchema, parseDuration } from './lib'; -export const DEFAULT_MAX_ALERTS = 1000; +export const DEFAULT_MAX_ALERTS = 10; const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; const ruleTypeSchema = schema.object({ id: schema.string(), diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts index 9614ecf94b36c..6171cb4788172 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts @@ -94,7 +94,7 @@ describe('Create rule request schema, additional validation', () => { expect(errors).toEqual(['max_signals must be greater than 0']); }); - test('does validate when max_signals is undefiend', () => { + test('does validate when max_signals is undefined', () => { const schema: RuleCreateProps = { ...getCreateRulesSchemaMock(), max_signals: undefined, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts index 7f0d86737776e..1f4d32bba56ff 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts @@ -123,7 +123,7 @@ describe('Patch rule request schema, additional validation', () => { expect(errors).toEqual(['max_signals must be greater than 0']); }); - test('does validate when max_signals is undefiend', () => { + test('does validate when max_signals is undefined', () => { const schema: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), max_signals: undefined, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts index 5583872c10fef..c1641da33ab15 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts @@ -79,7 +79,7 @@ describe('Update rule request schema, additional validation', () => { expect(errors).toEqual(['max_signals must be greater than 0']); }); - test('does validate when max_signals is undefiend', () => { + test('does validate when max_signals is undefined', () => { const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), max_signals: undefined, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index b6be955ecf4c2..9d2227913a5eb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -7,8 +7,7 @@ import React, { useMemo, useCallback } from 'react'; import type { EuiFieldNumberProps } from '@elastic/eui'; -import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; - +import { EuiTextColor, EuiFormRow, EuiFieldNumber } from '@elastic/eui'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { css } from '@emotion/css'; import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; @@ -37,15 +36,16 @@ export const MaxSignals: React.FC = ({ const maxAlertsPerRun = alerting.getMaxAlertsPerRun() ?? 1000; // Defaults to 1000 in the alerting framework config const [isInvalid, error] = useMemo(() => { - if (typeof value === 'number' && !isNaN(value)) { - if (value <= 0) { - return [true, i18n.GREATER_THAN_ERROR]; - } else if (value > maxAlertsPerRun) { - return [true, i18n.LESS_THAN_ERROR(maxAlertsPerRun)]; - } + if (typeof value === 'number' && !isNaN(value) && value <= 0) { + return [true, i18n.GREATER_THAN_ERROR]; } return [false]; - }, [maxAlertsPerRun, value]); + }, [value]); + + const hasWarning = useMemo( + () => typeof value === 'number' && !isNaN(value) && value > maxAlertsPerRun, + [maxAlertsPerRun, value] + ); const handleMaxSignalsChange: EuiFieldNumberProps['onChange'] = useCallback( (e) => { @@ -57,10 +57,17 @@ export const MaxSignals: React.FC = ({ ); const helpText = useMemo(() => { + const textToRender = []; + if (hasWarning) { + textToRender.push( + {i18n.LESS_THAN_WARNING(maxAlertsPerRun)} + ); + } const defaultToNumber = maxAlertsPerRun < DEFAULT_MAX_SIGNALS ? maxAlertsPerRun : DEFAULT_MAX_SIGNALS; - return i18n.MAX_SIGNALS_HELP_TEXT(defaultToNumber); - }, [maxAlertsPerRun]); + textToRender.push(i18n.MAX_SIGNALS_HELP_TEXT(defaultToNumber)); + return textToRender; + }, [hasWarning, maxAlertsPerRun]); return ( = ({ error={error} > = ({ data-test-subj="input" placeholder={placeholder} disabled={isDisabled} + // @ts-ignore This is need until https://github.com/elastic/eui/pull/7666 gets merged in and kibana is updated to the new version of Eui + icon={hasWarning ? { side: 'right', type: 'warning', color: 'warning' } : undefined} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts index e1848d21489b5..14887be5c1000 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -14,12 +14,13 @@ export const GREATER_THAN_ERROR = i18n.translate( } ); -export const LESS_THAN_ERROR = (maxNumber: number) => +export const LESS_THAN_WARNING = (maxNumber: number) => i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxAlertsFieldLessThanError', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.maxAlertsFieldLessThanWarning', { values: { maxNumber }, - defaultMessage: 'Max alerts must be less than {maxNumber}.', + defaultMessage: + 'Rule will only generate a maximum of {maxNumber} {maxNumber, plural, =1 {alert} other {alerts}}', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index 2922e26da6b4a..4c0384b26f5d7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -73,6 +73,7 @@ import { APP_UI_ID, DEFAULT_INDEX_KEY, DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, DEFAULT_THREAT_INDEX_KEY, } from '../../../../../common/constants'; import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; @@ -125,6 +126,7 @@ const CreateRulePageComponent: React.FC = () => { const { application, data: { dataViews }, + alerting: { getMaxAlertsPerRun }, } = useKibana().services; const loading = userInfoLoading || listsConfigLoading; const [activeStep, setActiveStep] = useState(RuleStep.defineRule); @@ -141,7 +143,7 @@ const CreateRulePageComponent: React.FC = () => { const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); const [threatIndicesConfig] = useUiSetting$(DEFAULT_THREAT_INDEX_KEY); - + const maxAlertsPerRun = useMemo(() => getMaxAlertsPerRun(), [getMaxAlertsPerRun]); const defineStepDefault = useMemo( () => ({ ...stepDefineDefaultValue, @@ -150,6 +152,16 @@ const CreateRulePageComponent: React.FC = () => { }), [indicesConfig, threatIndicesConfig] ); + const aboutStepDefault = useMemo(() => { + const defaultMaxSignals = + maxAlertsPerRun !== undefined && maxAlertsPerRun < DEFAULT_MAX_SIGNALS + ? maxAlertsPerRun + : DEFAULT_MAX_SIGNALS; + return { + ...stepAboutDefaultValue, + maxSignals: defaultMaxSignals, + }; + }, [maxAlertsPerRun]); const kibanaAbsoluteUrl = useMemo( () => application.getUrlForApp(`${APP_UI_ID}`, { @@ -178,7 +190,7 @@ const CreateRulePageComponent: React.FC = () => { setEqlOptionsSelected, } = useRuleForms({ defineStepDefault, - aboutStepDefault: stepAboutDefaultValue, + aboutStepDefault, scheduleStepDefault: defaultSchedule, actionsStepDefault, }); From 813c8070749d53788be546a021e13adb920f5f39 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 9 Apr 2024 23:11:58 -0400 Subject: [PATCH 13/36] reset config value --- x-pack/plugins/alerting/server/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index ef97b6b4a9fa7..a49c393da1e95 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { validateDurationSchema, parseDuration } from './lib'; -export const DEFAULT_MAX_ALERTS = 10; +export const DEFAULT_MAX_ALERTS = 1000; const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; const ruleTypeSchema = schema.object({ id: schema.string(), From cd4eb47f3365331d458af5733937ce5c41b72be1 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 12 Apr 2024 01:39:45 -0400 Subject: [PATCH 14/36] adds max_signals rule execution logic --- x-pack/plugins/alerting/server/config.ts | 2 +- x-pack/plugins/alerting/server/plugin.ts | 2 +- .../alerting/server/rules_client/types.ts | 4 +-- .../alerting/server/rules_client_factory.ts | 6 ++-- .../rule_types/__mocks__/rule_type.ts | 2 ++ .../create_security_rule_type_wrapper.ts | 2 ++ .../query/create_query_alert_type.test.ts | 1 + .../lib/detection_engine/rule_types/types.ts | 1 + .../rule_types/utils/utils.test.ts | 30 +++++++++++++++++++ .../rule_types/utils/utils.ts | 16 ++++++++-- .../security_solution/server/plugin.ts | 1 + 11 files changed, 58 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/alerting/server/config.ts b/x-pack/plugins/alerting/server/config.ts index a49c393da1e95..6dd96667f9553 100644 --- a/x-pack/plugins/alerting/server/config.ts +++ b/x-pack/plugins/alerting/server/config.ts @@ -80,7 +80,7 @@ export type AlertingConfig = TypeOf; export type RulesConfig = TypeOf; export type AlertingRulesConfig = Pick< AlertingConfig['rules'], - 'minimumScheduleInterval' | 'maxScheduledPerMinute' + 'minimumScheduleInterval' | 'maxScheduledPerMinute' | 'run' > & { isUsingSecurity: boolean; }; diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 6a2230e90a731..a9c5d2d176eb4 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -442,7 +442,7 @@ export class AlertingPlugin { }, getConfig: () => { return { - ...pick(this.config.rules, ['minimumScheduleInterval', 'maxScheduledPerMinute']), + ...pick(this.config.rules, ['minimumScheduleInterval', 'maxScheduledPerMinute', 'run']), isUsingSecurity: this.licenseState ? !!this.licenseState.getIsSecurityEnabled() : false, }; }, diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 7580c49ce6d59..c5696acc75a07 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -34,7 +34,7 @@ import { RuleAction, } from '../types'; import { AlertingAuthorization } from '../authorization'; -import { AlertingConfig, AlertingRulesConfig } from '../config'; +import { AlertingRulesConfig } from '../config'; import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry'; import { GetAlertIndicesAlias } from '../lib'; import { AlertsService } from '../alerts_service'; @@ -85,7 +85,7 @@ export interface RulesClientContext { readonly alertsService: AlertsService | null; readonly isSystemAction: (actionId: string) => boolean; readonly uiSettings: UiSettingsServiceStart; - readonly maxAlertsPerRun: AlertingConfig['rules']['run']['alerts']['max']; + readonly maxAlertsPerRun: AlertingRulesConfig['run']['alerts']['max']; } export type NormalizedAlertAction = DistributiveOmit; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index 17122822b324d..bf11ee1dca9e6 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -26,7 +26,7 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { RuleTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { RulesClient } from './rules_client'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; -import { AlertingConfig, AlertingRulesConfig } from './config'; +import { AlertingRulesConfig } from './config'; import { GetAlertIndicesAlias } from './lib'; import { AlertsService } from './alerts_service/alerts_service'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; @@ -52,7 +52,7 @@ export interface RulesClientFactoryOpts { alertsService: AlertsService | null; connectorAdapterRegistry: ConnectorAdapterRegistry; uiSettings: CoreStart['uiSettings']; - maxAlertsPerRun: AlertingConfig['rules']['run']['alerts']['max']; + maxAlertsPerRun: AlertingRulesConfig['run']['alerts']['max']; } export class RulesClientFactory { @@ -77,7 +77,7 @@ export class RulesClientFactory { private alertsService!: AlertsService | null; private connectorAdapterRegistry!: ConnectorAdapterRegistry; private uiSettings!: CoreStart['uiSettings']; - private maxAlertsPerRun!: AlertingConfig['rules']['run']['alerts']['max']; + private maxAlertsPerRun!: AlertingRulesConfig['run']['alerts']['max']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index 228cb67122a26..556a86c7c1f2b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -24,6 +24,7 @@ import type { QueryRuleParams, RuleParams } from '../../rule_schema'; // this is only used in tests import { createDefaultAlertExecutorOptions } from '@kbn/rule-registry-plugin/server/utils/rule_executor.test_helpers'; import { getCompleteRuleMock } from '../../rule_schema/mocks'; +import { DEFAULT_MAX_ALERTS } from '@kbn/alerting-plugin/server/config'; export const createRuleTypeMocks = ( ruleType: string = 'query', @@ -45,6 +46,7 @@ export const createRuleTypeMocks = ( registerType: ({ executor }) => { alertExecutor = executor; }, + getConfig: () => ({ run: { alerts: { max: DEFAULT_MAX_ALERTS } } }), } as AlertingPluginSetupContract; const scheduleActions = jest.fn(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 4577b83540e5b..8715bd9de74ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -75,6 +75,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = version, isPreview, experimentalFeatures, + alerting, }) => (type) => { const { alertIgnoreFields: ignoreFields, alertMergeStrategy: mergeStrategy } = config; @@ -313,6 +314,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = interval, maxSignals: maxSignals ?? DEFAULT_MAX_SIGNALS, ruleExecutionLogger, + alerting, }); if (remainingGap.asMilliseconds() > 0) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index f91f806073ff1..7ad3c148b8840 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -46,6 +46,7 @@ describe('Custom Query Alerts', () => { ruleExecutionLoggerFactory: () => Promise.resolve(ruleExecutionLogMock.forExecutors.create()), version: '8.3', publicBaseUrl, + alerting, }); const eventsTelemetry = createMockTelemetryEventsSender(true); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index ad7e11f72217f..b0b89935d28bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -137,6 +137,7 @@ export interface CreateSecurityRuleTypeWrapperProps { version: string; isPreview?: boolean; experimentalFeatures?: ExperimentalFeatures; + alerting: SetupPlugins['alerting']; } export type CreateSecurityRuleTypeWrapper = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index abf17f9f81b0e..c33d93231982f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -65,6 +65,7 @@ import type { ShardError } from '../../../types'; import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; import type { GenericBulkCreateResponse } from '../factories'; import type { BaseFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; +import type { PluginSetupContract } from '@kbn/alerting-plugin/server'; describe('utils', () => { const anchor = '2020-01-01T06:06:06.666Z'; @@ -442,6 +443,13 @@ describe('utils', () => { }); describe('getRuleRangeTuples', () => { + let alerting: PluginSetupContract; + + beforeEach(() => { + alerting = alertsMock.createSetup(); + alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 1000 } } }); + }); + test('should return a single tuple if no gap', () => { const { tuples, remainingGap } = getRuleRangeTuples({ previousStartedAt: moment().subtract(30, 's').toDate(), @@ -451,6 +459,7 @@ describe('utils', () => { to: 'now', maxSignals: 20, ruleExecutionLogger, + alerting, }); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); @@ -467,6 +476,7 @@ describe('utils', () => { to: 'now', maxSignals: 20, ruleExecutionLogger, + alerting, }); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); @@ -483,6 +493,7 @@ describe('utils', () => { to: 'now', maxSignals: 20, ruleExecutionLogger, + alerting, }); const someTuple = tuples[1]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(55); @@ -498,6 +509,7 @@ describe('utils', () => { to: 'now', maxSignals: 20, ruleExecutionLogger, + alerting, }); expect(tuples.length).toEqual(5); tuples.forEach((item, index) => { @@ -520,12 +532,30 @@ describe('utils', () => { to: 'now', maxSignals: 20, ruleExecutionLogger, + alerting, }); expect(tuples.length).toEqual(1); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(13); expect(remainingGap.asMilliseconds()).toEqual(0); }); + + test('should use alerting framework max alerts value if maxSignals is greater than limit', () => { + alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 10 } } }); + const { tuples } = getRuleRangeTuples({ + previousStartedAt: moment().subtract(30, 's').toDate(), + startedAt: moment().subtract(30, 's').toDate(), + interval: '30s', + from: 'now-30s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); + const someTuple = tuples[0]; + expect(someTuple.maxSignals).toEqual(10); + expect(tuples.length).toEqual(1); + }); }); describe('getMaxCatchupRatio', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index a3b640f0017dd..2e837b3d8f822 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -26,6 +26,7 @@ import type { import type { AlertInstanceContext, AlertInstanceState, + PluginSetupContract, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; import { parseDuration } from '@kbn/alerting-plugin/server'; @@ -418,6 +419,7 @@ export const getRuleRangeTuples = ({ interval, maxSignals, ruleExecutionLogger, + alerting, }: { startedAt: Date; previousStartedAt: Date | null | undefined; @@ -426,6 +428,7 @@ export const getRuleRangeTuples = ({ interval: string; maxSignals: number; ruleExecutionLogger: IRuleExecutionLogForExecutors; + alerting: PluginSetupContract; }) => { const originalFrom = dateMath.parse(from, { forceNow: startedAt }); const originalTo = dateMath.parse(to, { forceNow: startedAt }); @@ -433,11 +436,20 @@ export const getRuleRangeTuples = ({ throw new Error('Failed to parse date math of rule.from or rule.to'); } + const maxAlertsAllowed = alerting.getConfig().run.alerts.max; + let maxSignalsToUse = maxSignals; + if (maxSignals > maxAlertsAllowed) { + maxSignalsToUse = maxAlertsAllowed; + ruleExecutionLogger.warn( + `max_signals value set above limit set in xpack.rule.run.alerts.max, will only write a maximum of ${maxAlertsAllowed} per rule execution` + ); + } + const tuples = [ { to: originalTo, from: originalFrom, - maxSignals, + maxSignals: maxSignalsToUse, }, ]; @@ -464,7 +476,7 @@ export const getRuleRangeTuples = ({ const catchupTuples = getCatchupTuples({ originalTo, originalFrom, - ruleParamsMaxSignals: maxSignals, + ruleParamsMaxSignals: maxSignalsToUse, catchup, intervalDuration, }); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index f541c152a568b..f499b44612dc3 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -307,6 +307,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.ruleMonitoringService.createRuleExecutionLogClientForExecutors, version: pluginContext.env.packageInfo.version, experimentalFeatures: config.experimentalFeatures, + alerting: plugins.alerting, }; const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = { From 5ad1b65ccba3ad76d3e224f8a7970047ad1f8906 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 12 Apr 2024 13:51:59 -0400 Subject: [PATCH 15/36] updates tests and types --- x-pack/plugins/alerting/server/plugin.test.ts | 1 + .../rule_types/utils/search_after_bulk_create.test.ts | 5 +++++ .../plugins/triggers_actions_ui/server/routes/config.test.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index acbad4a27ffe8..37175ac960412 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -163,6 +163,7 @@ describe('Alerting Plugin', () => { maxScheduledPerMinute: 10000, isUsingSecurity: false, minimumScheduleInterval: { value: '1m', enforce: false }, + run: { alerts: { max: 1000 }, actions: { max: 1000 } }, }); expect(setupContract.frameworkAlerts.enabled()).toEqual(false); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts index 31c1e38b08f91..99cd1955923f4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts @@ -49,6 +49,7 @@ import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; import type { BuildReasonMessage } from './reason_formatters'; import type { QueryRuleParams } from '../../rule_schema'; import { SERVER_APP_ID } from '../../../../../common/constants'; +import type { PluginSetupContract } from '@kbn/alerting-plugin/server'; describe('searchAfterAndBulkCreate', () => { let mockService: RuleExecutorServicesMock; @@ -58,6 +59,7 @@ describe('searchAfterAndBulkCreate', () => { let wrapHits: WrapHits; let inputIndexPattern: string[] = []; let listClient = listMock.getListClient(); + let alerting: PluginSetupContract; const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create(); const someGuids = Array.from({ length: 13 }).map(() => uuidv4()); const sampleParams = getQueryRuleParams(); @@ -89,6 +91,8 @@ describe('searchAfterAndBulkCreate', () => { listClient.searchListItemByValues = jest.fn().mockResolvedValue([]); inputIndexPattern = ['auditbeat-*']; mockService = alertsMock.createRuleExecutorServices(); + alerting = alertsMock.createSetup(); + alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 1000 } } }); tuple = getRuleRangeTuples({ previousStartedAt: new Date(), startedAt: new Date(), @@ -97,6 +101,7 @@ describe('searchAfterAndBulkCreate', () => { interval: '5m', maxSignals: sampleParams.maxSignals, ruleExecutionLogger, + alerting, }).tuples[0]; mockPersistenceServices = createPersistenceServicesMock(); bulkCreate = bulkCreateFactory( diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts index 8918e77795520..a4966fa05e6b4 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts @@ -54,6 +54,7 @@ describe('createConfigRoute', () => { isUsingSecurity: true, maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, + run: { alerts: { max: 1000 }, actions: { max: 100000 } }, }), getRulesClientWithRequest: () => mockRulesClient, }); @@ -70,6 +71,7 @@ describe('createConfigRoute', () => { isUsingSecurity: true, maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, + run: { alerts: { max: 1000 }, actions: { max: 100000 } }, }, }); }); @@ -88,6 +90,7 @@ describe('createConfigRoute', () => { isUsingSecurity: true, maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, + run: { alerts: { max: 1000 }, actions: { max: 100000 } }, }), getRulesClientWithRequest: () => mockRulesClient, }); From ff4b232e739986be91fd3dcc27dfc69f65156900 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 12 Apr 2024 14:33:43 -0400 Subject: [PATCH 16/36] adds cypress tests --- .../cypress/data/detection_engine.ts | 3 +++ .../detection_engine/rule_creation/common_flows.cy.ts | 5 ++++- .../cypress/screens/create_new_rule.ts | 2 ++ .../cypress/screens/rule_details.ts | 2 ++ .../cypress/tasks/create_new_rule.ts | 8 ++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/data/detection_engine.ts b/x-pack/test/security_solution_cypress/cypress/data/detection_engine.ts index f50f2478537f4..12bbd1df14075 100644 --- a/x-pack/test/security_solution_cypress/cypress/data/detection_engine.ts +++ b/x-pack/test/security_solution_cypress/cypress/data/detection_engine.ts @@ -25,6 +25,7 @@ import type { RuleName, RuleReferenceArray, RuleTagArray, + MaxSignals, } from '@kbn/security-solution-plugin/common/api/detection_engine'; interface RuleFields { @@ -44,6 +45,7 @@ interface RuleFields { threat: Threat; threatSubtechnique: ThreatSubtechnique; threatTechnique: ThreatTechnique; + maxSignals: MaxSignals; } export const ruleFields: RuleFields = { @@ -90,4 +92,5 @@ export const ruleFields: RuleFields = { name: 'OS Credential Dumping', reference: 'https://attack.mitre.org/techniques/T1003', }, + maxSignals: 100, }; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts index c9d83ae4d67e1..7c3865abbeb4d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows.cy.ts @@ -15,7 +15,7 @@ import { RULE_NAME_INPUT, SCHEDULE_CONTINUE_BUTTON, } from '../../../../screens/create_new_rule'; -import { RULE_NAME_HEADER } from '../../../../screens/rule_details'; +import { MAX_SIGNALS_DETAILS, RULE_NAME_HEADER } from '../../../../screens/rule_details'; import { createTimeline } from '../../../../tasks/api_calls/timelines'; import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { @@ -25,6 +25,7 @@ import { fillDescription, fillFalsePositiveExamples, fillFrom, + fillMaxSignals, fillNote, fillReferenceUrls, fillRiskScore, @@ -74,6 +75,7 @@ describe('Common rule creation flows', { tags: ['@ess', '@serverless'] }, () => fillThreatTechnique(); fillThreatSubtechnique(); fillCustomInvestigationFields(); + fillMaxSignals(); fillNote(); cy.get(ABOUT_CONTINUE_BTN).click(); @@ -95,5 +97,6 @@ describe('Common rule creation flows', { tags: ['@ess', '@serverless'] }, () => // UI redirects to rule creation page of a created rule cy.get(RULE_NAME_HEADER).should('contain', ruleFields.ruleName); + cy.get(MAX_SIGNALS_DETAILS).should('contain', ruleFields.maxSignals); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts index ac474a56bd5b3..d2b02d8e4e4b9 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts @@ -129,6 +129,8 @@ export const INDICATOR_MATCH_TYPE = '[data-test-subj="threatMatchRuleType"]'; export const INPUT = '[data-test-subj="input"]'; +export const MAX_SIGNALS_INPUT = '[data-test-subj="detectionEngineStepAboutRuleMaxSignals"] input'; + export const INVESTIGATION_NOTES_TEXTAREA = '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/rule_details.ts b/x-pack/test/security_solution_cypress/cypress/screens/rule_details.ts index b8c524b0084ce..2e175e530d326 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/rule_details.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/rule_details.ts @@ -153,3 +153,5 @@ export const ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON = export const HIGHLIGHTED_ROWS_IN_TABLE = '[data-test-subj="euiDataGridBody"] .alertsTableHighlightedRow'; + +export const MAX_SIGNALS_DETAILS = '[data-test-subj="maxSignalsPropertyValue"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts index b3a5a39dbb186..e691291c69b78 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts @@ -125,6 +125,7 @@ import { ALERTS_INDEX_BUTTON, INVESTIGATIONS_INPUT, QUERY_BAR_ADD_FILTER, + MAX_SIGNALS_INPUT, } from '../screens/create_new_rule'; import { INDEX_SELECTOR, @@ -196,6 +197,13 @@ export const expandAdvancedSettings = () => { cy.get(ADVANCED_SETTINGS_BTN).click({ force: true }); }; +export const fillMaxSignals = (maxSignals: number = ruleFields.maxSignals) => { + cy.get(MAX_SIGNALS_INPUT).clear({ force: true }); + cy.get(MAX_SIGNALS_INPUT).type(maxSignals.toString()); + + return maxSignals; +}; + export const fillNote = (note: string = ruleFields.investigationGuide) => { cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }); cy.get(INVESTIGATION_NOTES_TEXTAREA).type(note, { force: true }); From bec6bf7031243435fd16f9b59deb998bec584828 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 15 Apr 2024 11:38:17 -0400 Subject: [PATCH 17/36] updates test attributes --- .../rule_creation_ui/components/max_signals/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index 9d2227913a5eb..a830810444500 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -76,7 +76,6 @@ export const MaxSignals: React.FC = ({ width: ${MAX_SIGNALS_FIELD_WIDTH}px; } `} - data-test-subj={dataTestSubj} describedByIds={idAria ? [idAria] : undefined} fullWidth helpText={helpText} @@ -92,6 +91,7 @@ export const MaxSignals: React.FC = ({ padding-left: 12px; `} isInvalid={isInvalid} + dataTestSubj={dataTestSubj} value={value as EuiFieldNumberProps['value']} onChange={handleMaxSignalsChange} isLoading={field.isValidating} From 34588b2c96cb4ce19fb8d40745f6a045212ed909 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 15 Apr 2024 23:21:56 -0400 Subject: [PATCH 18/36] updates warning design --- .../components/max_signals/index.tsx | 13 +++---------- .../components/step_about_rule/index.tsx | 2 +- .../cypress/screens/create_new_rule.ts | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index a830810444500..5e8607b272407 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -7,7 +7,7 @@ import React, { useMemo, useCallback } from 'react'; import type { EuiFieldNumberProps } from '@elastic/eui'; -import { EuiTextColor, EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { EuiTextColor, EuiFormRow, EuiFieldNumber, EuiIcon } from '@elastic/eui'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { css } from '@emotion/css'; import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; @@ -85,21 +85,14 @@ export const MaxSignals: React.FC = ({ error={error} > : undefined} /> ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index 3fadc2fc6e6f3..f27a07009fa2e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -334,7 +334,7 @@ const StepAboutRuleComponent: FC = ({ component={MaxSignals} componentProps={{ idAria: 'detectionEngineStepAboutRuleMaxSignals', - 'data-test-subj': 'detectionEngineStepAboutRuleMaxSignals', + dataTestSubj: 'detectionEngineStepAboutRuleMaxSignals', isDisabled: isLoading, }} /> diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts index d2b02d8e4e4b9..963f9cc2e8668 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts @@ -129,7 +129,7 @@ export const INDICATOR_MATCH_TYPE = '[data-test-subj="threatMatchRuleType"]'; export const INPUT = '[data-test-subj="input"]'; -export const MAX_SIGNALS_INPUT = '[data-test-subj="detectionEngineStepAboutRuleMaxSignals"] input'; +export const MAX_SIGNALS_INPUT = '[data-test-subj="detectionEngineStepAboutRuleMaxSignals"]'; export const INVESTIGATION_NOTES_TEXTAREA = '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea'; From 3e749f6a6df0989fe0559aa6d921753577eb0ffc Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 16 Apr 2024 14:59:37 -0400 Subject: [PATCH 19/36] updates language --- .../rule_creation_ui/components/max_signals/translations.ts | 2 +- .../server/lib/detection_engine/rule_types/utils/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts index 14887be5c1000..700419aa380cd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -20,7 +20,7 @@ export const LESS_THAN_WARNING = (maxNumber: number) => { values: { maxNumber }, defaultMessage: - 'Rule will only generate a maximum of {maxNumber} {maxNumber, plural, =1 {alert} other {alerts}}', + 'Kibana alerting only allows a maximum of {maxNumber} {maxNumber, plural, =1 {alert} other {alerts}} per rule run.', } ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index 2e837b3d8f822..cc960bbf9b340 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -441,7 +441,7 @@ export const getRuleRangeTuples = ({ if (maxSignals > maxAlertsAllowed) { maxSignalsToUse = maxAlertsAllowed; ruleExecutionLogger.warn( - `max_signals value set above limit set in xpack.rule.run.alerts.max, will only write a maximum of ${maxAlertsAllowed} per rule execution` + `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit set in xpack.alerting.rules.run.alerts.max. The rule will only write a maximum of ${maxAlertsAllowed} per rule run.` ); } From bd01d55f742eb460484c0cd0934639bf2a1d9d1b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 16 Apr 2024 15:24:14 -0400 Subject: [PATCH 20/36] updates attribute --- .../rule_creation_ui/components/max_signals/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index 5e8607b272407..23ba1f43132c4 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -92,7 +92,7 @@ export const MaxSignals: React.FC = ({ placeholder={placeholder} data-test-subj={dataTestSubj} disabled={isDisabled} - prepend={hasWarning ? : undefined} + append={hasWarning ? : undefined} /> ); From 25c05e39854e59f7d87afd003dbdde322ea2ba1e Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 22 Apr 2024 11:18:43 -0400 Subject: [PATCH 21/36] addresses comments --- .../create_security_rule_type_wrapper.ts | 7 ++++- .../utils/search_after_bulk_create.test.ts | 24 ++++++++------- .../rule_types/utils/utils.test.ts | 30 +++++++++++-------- .../rule_types/utils/utils.ts | 15 ++++++---- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 8715bd9de74ff..a33c61c984dea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -306,7 +306,11 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = wroteWarningStatus = true; } - const { tuples, remainingGap } = getRuleRangeTuples({ + const { + tuples, + remainingGap, + wroteWarningStatus: rangeTuplesWarningStatus, + } = await getRuleRangeTuples({ startedAt, previousStartedAt, from, @@ -316,6 +320,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = ruleExecutionLogger, alerting, }); + wroteWarningStatus = rangeTuplesWarningStatus; if (remainingGap.asMilliseconds() > 0) { hasError = true; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts index 99cd1955923f4..4d414d71cfadf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.test.ts @@ -84,7 +84,7 @@ describe('searchAfterAndBulkCreate', () => { sampleParams.maxSignals = 30; let tuple: RuleRangeTuple; - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); buildReasonMessage = jest.fn().mockResolvedValue('some alert reason message'); listClient = listMock.getListClient(); @@ -93,16 +93,18 @@ describe('searchAfterAndBulkCreate', () => { mockService = alertsMock.createRuleExecutorServices(); alerting = alertsMock.createSetup(); alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 1000 } } }); - tuple = getRuleRangeTuples({ - previousStartedAt: new Date(), - startedAt: new Date(), - from: sampleParams.from, - to: sampleParams.to, - interval: '5m', - maxSignals: sampleParams.maxSignals, - ruleExecutionLogger, - alerting, - }).tuples[0]; + tuple = ( + await getRuleRangeTuples({ + previousStartedAt: new Date(), + startedAt: new Date(), + from: sampleParams.from, + to: sampleParams.to, + interval: '5m', + maxSignals: sampleParams.maxSignals, + ruleExecutionLogger, + alerting, + }) + ).tuples[0]; mockPersistenceServices = createPersistenceServicesMock(); bulkCreate = bulkCreateFactory( mockPersistenceServices.alertWithPersistence, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index c33d93231982f..cc4c507e24d75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -450,8 +450,8 @@ describe('utils', () => { alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 1000 } } }); }); - test('should return a single tuple if no gap', () => { - const { tuples, remainingGap } = getRuleRangeTuples({ + test('should return a single tuple if no gap', async () => { + const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(30, 's').toDate(), startedAt: moment().subtract(30, 's').toDate(), interval: '30s', @@ -465,10 +465,11 @@ describe('utils', () => { expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); expect(tuples.length).toEqual(1); expect(remainingGap.asMilliseconds()).toEqual(0); + expect(wroteWarningStatus).toEqual(false); }); - test('should return a single tuple if malformed interval prevents gap calculation', () => { - const { tuples, remainingGap } = getRuleRangeTuples({ + test('should return a single tuple if malformed interval prevents gap calculation', async () => { + const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(30, 's').toDate(), startedAt: moment().subtract(30, 's').toDate(), interval: 'invalid', @@ -482,10 +483,11 @@ describe('utils', () => { expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); expect(tuples.length).toEqual(1); expect(remainingGap.asMilliseconds()).toEqual(0); + expect(wroteWarningStatus).toEqual(false); }); - test('should return two tuples if gap and previouslyStartedAt', () => { - const { tuples, remainingGap } = getRuleRangeTuples({ + test('should return two tuples if gap and previouslyStartedAt', async () => { + const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(65, 's').toDate(), startedAt: moment().toDate(), interval: '50s', @@ -498,10 +500,11 @@ describe('utils', () => { const someTuple = tuples[1]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(55); expect(remainingGap.asMilliseconds()).toEqual(0); + expect(wroteWarningStatus).toEqual(false); }); - test('should return five tuples when give long gap', () => { - const { tuples, remainingGap } = getRuleRangeTuples({ + test('should return five tuples when give long gap', async () => { + const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(65, 's').toDate(), // 64 is 5 times the interval + lookback, which will trigger max lookback startedAt: moment().toDate(), interval: '10s', @@ -521,10 +524,11 @@ describe('utils', () => { expect(item.from.diff(tuples[index - 1].from, 's')).toEqual(10); }); expect(remainingGap.asMilliseconds()).toEqual(12000); + expect(wroteWarningStatus).toEqual(false); }); - test('should return a single tuple when give a negative gap (rule ran sooner than expected)', () => { - const { tuples, remainingGap } = getRuleRangeTuples({ + test('should return a single tuple when give a negative gap (rule ran sooner than expected)', async () => { + const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(-15, 's').toDate(), startedAt: moment().subtract(-15, 's').toDate(), interval: '10s', @@ -538,11 +542,12 @@ describe('utils', () => { const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(13); expect(remainingGap.asMilliseconds()).toEqual(0); + expect(wroteWarningStatus).toEqual(false); }); - test('should use alerting framework max alerts value if maxSignals is greater than limit', () => { + test('should use alerting framework max alerts value if maxSignals is greater than limit', async () => { alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 10 } } }); - const { tuples } = getRuleRangeTuples({ + const { tuples, wroteWarningStatus } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(30, 's').toDate(), startedAt: moment().subtract(30, 's').toDate(), interval: '30s', @@ -555,6 +560,7 @@ describe('utils', () => { const someTuple = tuples[0]; expect(someTuple.maxSignals).toEqual(10); expect(tuples.length).toEqual(1); + expect(wroteWarningStatus).toEqual(true); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index cc960bbf9b340..93f9d5f23763b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -411,7 +411,7 @@ export const errorAggregator = ( }, Object.create(null)); }; -export const getRuleRangeTuples = ({ +export const getRuleRangeTuples = async ({ startedAt, previousStartedAt, from, @@ -432,6 +432,7 @@ export const getRuleRangeTuples = ({ }) => { const originalFrom = dateMath.parse(from, { forceNow: startedAt }); const originalTo = dateMath.parse(to, { forceNow: startedAt }); + let wroteWarningStatus = false; if (originalFrom == null || originalTo == null) { throw new Error('Failed to parse date math of rule.from or rule.to'); } @@ -440,9 +441,12 @@ export const getRuleRangeTuples = ({ let maxSignalsToUse = maxSignals; if (maxSignals > maxAlertsAllowed) { maxSignalsToUse = maxAlertsAllowed; - ruleExecutionLogger.warn( - `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit set in xpack.alerting.rules.run.alerts.max. The rule will only write a maximum of ${maxAlertsAllowed} per rule run.` - ); + const warningStatusMessage = `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit set in xpack.alerting.rules.run.alerts.max (${maxAlertsAllowed}). The rule will only write a maximum of ${maxAlertsAllowed} per rule run.`; + await ruleExecutionLogger.logStatusChange({ + newStatus: RuleExecutionStatusEnum['partial failure'], + message: warningStatusMessage, + }); + wroteWarningStatus = true; } const tuples = [ @@ -460,7 +464,7 @@ export const getRuleRangeTuples = ({ interval )}"` ); - return { tuples, remainingGap: moment.duration(0) }; + return { tuples, remainingGap: moment.duration(0), wroteWarningStatus }; } const gap = getGapBetweenRuns({ @@ -492,6 +496,7 @@ export const getRuleRangeTuples = ({ return { tuples: tuples.reverse(), remainingGap: moment.duration(remainingGapMilliseconds), + wroteWarningStatus, }; }; From e70972291a4cf08d26fa6eb0a520bb71e2a294ac Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 22 Apr 2024 13:03:15 -0400 Subject: [PATCH 22/36] fixes execution logic --- .../rule_types/create_security_rule_type_wrapper.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index a33c61c984dea..b2559636f95c7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -320,7 +320,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = ruleExecutionLogger, alerting, }); - wroteWarningStatus = rangeTuplesWarningStatus; + if (rangeTuplesWarningStatus) { + wroteWarningStatus = rangeTuplesWarningStatus; + } if (remainingGap.asMilliseconds() > 0) { hasError = true; From 98869132b291c12c705096b98d116fe0d49c149f Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 22 Apr 2024 16:04:53 -0400 Subject: [PATCH 23/36] strips out no longer needed rulesClient param --- .../rule/methods/aggregate/aggregate_rules.test.ts | 2 -- .../rule/methods/bulk_delete/bulk_delete_rules.test.ts | 2 -- .../rule/methods/bulk_disable/bulk_disable_rules.test.ts | 2 -- .../rule/methods/bulk_edit/bulk_edit_rules.test.ts | 2 -- .../rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts | 2 -- .../application/rule/methods/create/create_rule.test.ts | 2 -- .../get_schedule_frequency/get_schedule_frequency.test.ts | 2 -- .../server/application/rule/methods/resolve/resolve.test.ts | 2 -- .../application/rule/methods/tags/get_rule_tags.test.ts | 2 -- .../application/rule/methods/update/update_rule.test.ts | 2 -- x-pack/plugins/alerting/server/plugin.ts | 1 - x-pack/plugins/alerting/server/rules_client.mock.ts | 1 - .../rules_client/lib/add_generated_action_values.test.ts | 2 -- .../server/rules_client/lib/create_new_api_key_set.test.ts | 2 -- x-pack/plugins/alerting/server/rules_client/rules_client.ts | 4 ---- .../alerting/server/rules_client/tests/bulk_enable.test.ts | 2 -- .../server/rules_client/tests/clear_expired_snoozes.test.ts | 2 -- .../plugins/alerting/server/rules_client/tests/clone.test.ts | 2 -- .../plugins/alerting/server/rules_client/tests/delete.test.ts | 2 -- .../alerting/server/rules_client/tests/disable.test.ts | 2 -- .../plugins/alerting/server/rules_client/tests/enable.test.ts | 2 -- .../plugins/alerting/server/rules_client/tests/find.test.ts | 2 -- x-pack/plugins/alerting/server/rules_client/tests/get.test.ts | 2 -- .../server/rules_client/tests/get_action_error_log.test.ts | 2 -- .../server/rules_client/tests/get_alert_state.test.ts | 2 -- .../server/rules_client/tests/get_alert_summary.test.ts | 2 -- .../server/rules_client/tests/get_execution_log.test.ts | 2 -- .../server/rules_client/tests/list_rule_types.test.ts | 2 -- .../alerting/server/rules_client/tests/mute_all.test.ts | 2 -- .../alerting/server/rules_client/tests/mute_instance.test.ts | 2 -- .../alerting/server/rules_client/tests/resolve.test.ts | 2 -- .../alerting/server/rules_client/tests/run_soon.test.ts | 2 -- .../alerting/server/rules_client/tests/unmute_all.test.ts | 2 -- .../server/rules_client/tests/unmute_instance.test.ts | 2 -- .../alerting/server/rules_client/tests/update_api_key.test.ts | 2 -- x-pack/plugins/alerting/server/rules_client/types.ts | 1 - .../alerting/server/rules_client_conflict_retries.test.ts | 2 -- x-pack/plugins/alerting/server/rules_client_factory.test.ts | 4 ---- x-pack/plugins/alerting/server/rules_client_factory.ts | 4 ---- 39 files changed, 81 deletions(-) diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts index f1fddb274bbc4..858bc6ded1cb3 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts @@ -29,7 +29,6 @@ import { DefaultRuleAggregationResult } from '../../../../routes/rule/apis/aggre import { defaultRuleAggregationFactory } from '.'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -67,7 +66,6 @@ const rulesClientParams: jest.Mocked = { internalSavedObjectsRepository, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts index 35e9031a274f6..6ccd32879a171 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_delete/bulk_delete_rules.test.ts @@ -41,7 +41,6 @@ import { import { migrateLegacyActions } from '../../../../rules_client/lib'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -96,7 +95,6 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const getBulkOperationStatusErrorResponse = (statusCode: number) => ({ diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts index fc42b4164849b..be64257c86f8c 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_disable/bulk_disable_rules.test.ts @@ -46,7 +46,6 @@ import { migrateLegacyActions } from '../../../../rules_client/lib'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { ActionsClient } from '@kbn/actions-plugin/server'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -108,7 +107,6 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index a4259dc4c91e0..771254755431c 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -41,7 +41,6 @@ import { RuleAttributes } from '../../../../data/rule/types'; import { SavedObject } from '@kbn/core/server'; import { bulkEditOperationsSchema } from './schemas'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -114,7 +113,6 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const paramsModifier = jest.fn(); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts index 87e341d597679..f2de0ed7840dd 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_untrack/bulk_untrack_alerts.test.ts @@ -23,7 +23,6 @@ import { alertsServiceMock } from '../../../../alerts_service/alerts_service.moc import { ALERT_RULE_UUID, ALERT_UUID } from '@kbn/rule-data-utils'; import { ConcreteTaskInstance, TaskStatus } from '@kbn/task-manager-plugin/server'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -65,7 +64,6 @@ const rulesClientParams: jest.Mocked = { uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), connectorAdapterRegistry: new ConnectorAdapterRegistry(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; describe('bulkUntrackAlerts()', () => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index 1b471872ef12a..50f5abb5d3d73 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -33,7 +33,6 @@ import { ConnectorAdapter } from '../../../../connector_adapters/types'; import { RuleDomain } from '../../types'; import { RuleSystemAction } from '../../../../types'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -95,7 +94,6 @@ const rulesClientParams: jest.Mocked = { connectorAdapterRegistry, isSystemAction: jest.fn(), uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts index 959641930ed6b..a0b44abde3c54 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/get_schedule_frequency/get_schedule_frequency.test.ts @@ -22,7 +22,6 @@ import { AlertingAuthorization } from '../../../../authorization/alerting_author import { ActionsAuthorization } from '@kbn/actions-plugin/server'; import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -61,7 +60,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const getMockAggregationResult = ( diff --git a/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts index 4ff06ed5450e4..b611f5f2d3ef5 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/resolve/resolve.test.ts @@ -23,7 +23,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; import { getBeforeSetup } from '../../../../rules_client/tests/lib'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; describe('resolve', () => { const taskManager = taskManagerMock.createStart(); @@ -66,7 +65,6 @@ describe('resolve', () => { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; let rulesClient: RulesClient; diff --git a/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts index 610e6648f1518..e6c99f779909d 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/tags/get_rule_tags.test.ts @@ -25,7 +25,6 @@ import { RecoveredActionGroup } from '../../../../../common'; import { RegistryRuleType } from '../../../../rule_type_registry'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -63,7 +62,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const listedTypes = new Set([ diff --git a/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts index a2fd50cfb644d..eb36b28c40875 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/update/update_rule.test.ts @@ -32,7 +32,6 @@ import { migrateLegacyActions } from '../../../../rules_client/lib'; import { RULE_SAVED_OBJECT_TYPE } from '../../../../saved_objects'; import { ConnectorAdapterRegistry } from '../../../../connector_adapters/connector_adapter_registry'; import { RuleDomain } from '../../types'; -import { DEFAULT_MAX_ALERTS } from '../../../../config'; jest.mock('../../../../rules_client/lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -100,7 +99,6 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index a9c5d2d176eb4..6060782a86d55 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -526,7 +526,6 @@ export class AlertingPlugin { alertsService: this.alertsService, connectorAdapterRegistry: this.connectorAdapterRegistry, uiSettings: core.uiSettings, - maxAlertsPerRun: this.config.rules.run.alerts.max, }); rulesSettingsClientFactory.initialize({ diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 629db9f954df4..0b4122e221ca5 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -54,7 +54,6 @@ const createRulesClientMock = () => { getAlertFromRaw: jest.fn(), getScheduleFrequency: jest.fn(), bulkUntrackAlerts: jest.fn(), - getMaxAlertsPerRun: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts index 7fbad3ae936cf..871debb9b4e9c 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/add_generated_action_values.test.ts @@ -22,7 +22,6 @@ import { alertingAuthorizationMock } from '../../authorization/alerting_authoriz import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ConstructorOptions } from '../rules_client'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('uuid', () => ({ v4: () => '111-222', @@ -66,7 +65,6 @@ describe('addGeneratedActionValues()', () => { uiSettings: uiSettingsServiceMock.createStartContract(), connectorAdapterRegistry: new ConnectorAdapterRegistry(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const mockAction: RuleAction = { diff --git a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts index cb6fbb57ffebb..93b43545b9747 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts @@ -23,7 +23,6 @@ import { getBeforeSetup, mockedDateString } from '../tests/lib'; import { createNewAPIKeySet } from './create_new_api_key_set'; import { RulesClientContext } from '../types'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,7 +59,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const username = 'test'; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 01d4fb258ccce..1e1e57c429f07 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -194,10 +194,6 @@ export class RulesClient { return this.context.auditLogger; } - public getMaxAlertsPerRun() { - return this.context.maxAlertsPerRun; - } - public getTags = (params: RuleTagsParams) => getRuleTags(this.context, params); public getScheduleFrequency = () => getScheduleFrequency(this.context); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts index 8639c46d38255..64a243403c406 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_enable.test.ts @@ -46,7 +46,6 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { migrateLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -100,7 +99,6 @@ const rulesClientParams: jest.Mocked = { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts index 174f7798b13bc..a5b691894b77c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clear_expired_snoozes.test.ts @@ -28,7 +28,6 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server'; import { RuleSnooze } from '../../types'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -78,7 +77,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; describe('clearExpiredSnoozes()', () => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts index a144d28a067a0..a23d9b159d79b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/clone.test.ts @@ -24,7 +24,6 @@ import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; import { getBeforeSetup } from './lib'; import { RuleDomain } from '../../application/rule/types'; import { ConstructorOptions, RulesClient } from '../rules_client'; -import { DEFAULT_MAX_ALERTS } from '../../config'; describe('clone', () => { const taskManager = taskManagerMock.createStart(); @@ -67,7 +66,6 @@ describe('clone', () => { getAlertIndicesAlias: jest.fn(), alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; let rulesClient: RulesClient; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index 9ac71ee08da31..20ea58cad66ab 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -27,7 +27,6 @@ import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_key import { migrateLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -80,7 +79,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 767880508459d..9d21fa3477fb9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -28,7 +28,6 @@ import { migrateLegacyActions } from '../lib'; import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -82,7 +81,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index 8100e209de3b4..dd48bbe5412c7 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -27,7 +27,6 @@ import { migrateLegacyActions } from '../lib'; import { migrateLegacyActionsMock } from '../lib/siem_legacy_actions/retrieve_migrated_legacy_actions.mock'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/migrate_legacy_actions', () => { return { @@ -79,7 +78,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index c546cd0b022d4..70e9ef57b2e6a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -29,7 +29,6 @@ import { enabledRule1, enabledRule2, siemRule1, siemRule2 } from './test_helpers import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -72,7 +71,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn().mockImplementation((id) => id === 'system_action-id'), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index 4559639c59d4e..1f136a0c181b3 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -26,7 +26,6 @@ import { RecoveredActionGroup } from '../../../common'; import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -69,7 +68,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts index e6b052784e40b..0cf207500fdb7 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_action_error_log.test.ts @@ -28,7 +28,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, mockedDateString, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -68,7 +67,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts index a644af78c04d2..94db9bd1c9629 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_state.test.ts @@ -24,7 +24,6 @@ import { getBeforeSetup } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -61,7 +60,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index db9d8a227a37c..60769bc20cd6d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -28,7 +28,6 @@ import { RawRule } from '../../types'; import { getBeforeSetup, mockedDateString, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -66,7 +65,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 24f3665a6dc4b..f90f5835dbcd4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -29,7 +29,6 @@ import { getExecutionLogAggregation } from '../../lib/get_execution_log_aggregat import { fromKueryExpression } from '@kbn/es-query'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -69,7 +68,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts index 63b280c188eb7..e6f3978987c53 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts @@ -26,7 +26,6 @@ import { getBeforeSetup } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -63,7 +62,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 5f9150733d1d5..7911735644403 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -23,7 +23,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,7 +59,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index 476cbe8ea5e4d..36bba7e734748 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -23,7 +23,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,7 +59,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 6cd0c7c71263a..bab353225b28b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -26,7 +26,6 @@ import { RecoveredActionGroup } from '../../../common'; import { formatLegacyActions } from '../lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../lib/siem_legacy_actions/format_legacy_actions', () => { return { @@ -69,7 +68,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index d2fab3568e208..653e7dd807c8a 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -24,7 +24,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -62,7 +61,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; setGlobalDate(); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index eb357a64097a9..20eae2a147dda 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -23,7 +23,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,7 +59,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index 0ef132471d0b6..8275e0a88d8ba 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -23,7 +23,6 @@ import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; const taskManager = taskManagerMock.createStart(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -60,7 +59,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index bd42cdd0c9316..66881241021ef 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -24,7 +24,6 @@ import { getBeforeSetup, setGlobalDate } from './lib'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ConnectorAdapterRegistry } from '../../connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects'; -import { DEFAULT_MAX_ALERTS } from '../../config'; jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ bulkMarkApiKeysForInvalidation: jest.fn(), @@ -67,7 +66,6 @@ const rulesClientParams: jest.Mocked = { alertsService: null, uiSettings: uiSettingsServiceMock.createStartContract(), isSystemAction: jest.fn(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/rules_client/types.ts b/x-pack/plugins/alerting/server/rules_client/types.ts index 22bdb660f0ac1..555843d1e38c4 100644 --- a/x-pack/plugins/alerting/server/rules_client/types.ts +++ b/x-pack/plugins/alerting/server/rules_client/types.ts @@ -84,7 +84,6 @@ export interface RulesClientContext { readonly alertsService: AlertsService | null; readonly isSystemAction: (actionId: string) => boolean; readonly uiSettings: UiSettingsServiceStart; - readonly maxAlertsPerRun: AlertingRulesConfig['run']['alerts']['max']; } export type NormalizedAlertAction = DistributiveOmit; diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index 6eab09d181c59..e8d92335f8ee4 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -27,7 +27,6 @@ import { TaskStatus } from '@kbn/task-manager-plugin/server/task'; import { RecoveredActionGroup } from '../common'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from './saved_objects'; -import { DEFAULT_MAX_ALERTS } from './config'; jest.mock('./application/rule/methods/get_schedule_frequency', () => ({ validateScheduleLimit: jest.fn(), @@ -75,7 +74,6 @@ const rulesClientParams: jest.Mocked = { connectorAdapterRegistry: new ConnectorAdapterRegistry(), isSystemAction: jest.fn(), uiSettings: uiSettingsServiceMock.createStartContract(), - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; // this suite consists of two suites running tests against mutable RulesClient APIs: diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index f5b1ca72ae265..adb6e0c88d30f 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -29,7 +29,6 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { mockRouter } from '@kbn/core-http-router-server-mocks'; import { ConnectorAdapterRegistry } from './connector_adapters/connector_adapter_registry'; import { RULE_SAVED_OBJECT_TYPE } from './saved_objects'; -import { DEFAULT_MAX_ALERTS } from './config'; jest.mock('./rules_client'); jest.mock('./authorization/alerting_authorization'); @@ -63,7 +62,6 @@ const rulesClientFactoryParams: jest.Mocked = { uiSettings: uiSettingsServiceMock.createStartContract(), getAlertIndicesAlias: jest.fn(), alertsService: null, - maxAlertsPerRun: DEFAULT_MAX_ALERTS, }; const actionsAuthorization = actionsAuthorizationMock.create(); @@ -128,7 +126,6 @@ test('creates a rules client with proper constructor arguments when security is getAlertIndicesAlias: expect.any(Function), alertsService: null, uiSettings: rulesClientFactoryParams.uiSettings, - maxAlertsPerRun: 1000, }); }); @@ -176,7 +173,6 @@ test('creates a rules client with proper constructor arguments', async () => { getAlertIndicesAlias: expect.any(Function), alertsService: null, uiSettings: rulesClientFactoryParams.uiSettings, - maxAlertsPerRun: 1000, }); }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index bf11ee1dca9e6..ad3035791bde6 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -52,7 +52,6 @@ export interface RulesClientFactoryOpts { alertsService: AlertsService | null; connectorAdapterRegistry: ConnectorAdapterRegistry; uiSettings: CoreStart['uiSettings']; - maxAlertsPerRun: AlertingRulesConfig['run']['alerts']['max']; } export class RulesClientFactory { @@ -77,7 +76,6 @@ export class RulesClientFactory { private alertsService!: AlertsService | null; private connectorAdapterRegistry!: ConnectorAdapterRegistry; private uiSettings!: CoreStart['uiSettings']; - private maxAlertsPerRun!: AlertingRulesConfig['run']['alerts']['max']; public initialize(options: RulesClientFactoryOpts) { if (this.isInitialized) { @@ -104,7 +102,6 @@ export class RulesClientFactory { this.alertsService = options.alertsService; this.connectorAdapterRegistry = options.connectorAdapterRegistry; this.uiSettings = options.uiSettings; - this.maxAlertsPerRun = options.maxAlertsPerRun; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): RulesClient { @@ -137,7 +134,6 @@ export class RulesClientFactory { alertsService: this.alertsService, connectorAdapterRegistry: this.connectorAdapterRegistry, uiSettings: this.uiSettings, - maxAlertsPerRun: this.maxAlertsPerRun, async getUserName() { if (!securityPluginStart) { From 89bd1e7f91aadae94a9f0139ccb5ab8f3ed82bac Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Mon, 22 Apr 2024 23:48:09 -0400 Subject: [PATCH 24/36] adds defaultable import export tests --- .../export_rules.ts | 21 +++++++++++++++ .../import_rules.ts | 26 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts index f355e9ed61fc4..b3c73965c5e27 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts @@ -8,6 +8,7 @@ import expect from 'expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { binaryToString, getCustomQueryRuleParams } from '../../../utils'; import { @@ -16,10 +17,12 @@ import { deleteAllRules, deleteAllAlerts, } from '../../../../../../common/utils/security_solution'; + export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const securitySolutionApi = getService('securitySolutionApi'); describe('@ess @serverless export_rules', () => { describe('exporting rules', () => { @@ -63,6 +66,24 @@ export default ({ getService }: FtrProviderContext): void => { expect(exportedRule).toMatchObject(ruleToExport); }); + it('should export defaultable fields when values are set', async () => { + const defaultableFields: BaseDefaultableFields = { + max_signals: 100, + }; + const ruleToExport = getCustomQueryRuleParams(defaultableFields); + + await securitySolutionApi.createRule({ body: ruleToExport }); + + const { body } = await securitySolutionApi + .exportRules({ query: {}, body: null }) + .expect(200) + .parse(binaryToString); + + const exportedRule = JSON.parse(body.toString().split(/\n/)[0]); + + expect(exportedRule).toMatchObject(defaultableFields); + }); + it('should have export summary reflecting a number of rules', async () => { await createRule(supertest, log, getCustomQueryRuleParams()); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts index d1b2fb041f4bc..ad7220c4c35be 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/import_rules.ts @@ -8,6 +8,7 @@ import expect from 'expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; +import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { getCustomQueryRuleParams, combineToNdJson, fetchRule } from '../../../utils'; import { @@ -21,6 +22,7 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); const es = getService('es'); + const securitySolutionApi = getService('securitySolutionApi'); describe('@ess @serverless import_rules', () => { describe('importing rules with an index', () => { @@ -135,6 +137,30 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.errors[0].error.message).toBe('from: Failed to parse date-math expression'); }); + it('should be able to import rules with defaultable fields', async () => { + const defaultableFields: BaseDefaultableFields = { + max_signals: 100, + }; + const ruleToImport = getCustomQueryRuleParams({ + ...defaultableFields, + rule_id: 'rule-1', + }); + const ndjson = combineToNdJson(ruleToImport); + + await securitySolutionApi + .importRules({ query: {} }) + .attach('file', Buffer.from(ndjson), 'rules.ndjson') + .expect(200); + + const { body: importedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(importedRule).toMatchObject(ruleToImport); + }); + it('should be able to import two rules', async () => { const ndjson = combineToNdJson( getCustomQueryRuleParams({ From db88782f5a5b9a0fc11e154ccba45d22cf5d28c1 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 24 Apr 2024 15:00:17 -0400 Subject: [PATCH 25/36] addresses comments --- .../request_schema_validation.test.ts | 18 --- .../create_rule/request_schema_validation.ts | 9 -- .../request_schema_validation.test.ts | 18 --- .../patch_rule/request_schema_validation.ts | 9 -- .../request_schema_validation.test.ts | 18 --- .../update_rule/request_schema_validation.ts | 9 -- .../api/rules/create_rule/route.ts | 8 +- .../api/rules/patch_rule/route.ts | 3 +- .../api/rules/update_rule/route.ts | 8 +- .../rule_management/utils/validate.ts | 6 - .../create_security_rule_type_wrapper.ts | 2 + .../rule_types/utils/utils.test.ts | 115 ++++++++++-------- .../rule_types/utils/utils.ts | 6 +- .../export_rules.ts | 2 +- 14 files changed, 74 insertions(+), 157 deletions(-) diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts index 6171cb4788172..733645dcb3aa9 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.test.ts @@ -84,22 +84,4 @@ describe('Create rule request schema, additional validation', () => { 'when "concurrent_searches" exists, "items_per_search" must also exist', ]); }); - - test('does NOT validate when max_signals has a value less than 1', () => { - const schema: RuleCreateProps = { - ...getCreateRulesSchemaMock(), - max_signals: 0, - }; - const errors = validateCreateRuleProps(schema); - expect(errors).toEqual(['max_signals must be greater than 0']); - }); - - test('does validate when max_signals is undefined', () => { - const schema: RuleCreateProps = { - ...getCreateRulesSchemaMock(), - max_signals: undefined, - }; - const errors = validateCreateRuleProps(schema); - expect(errors).toEqual([]); - }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts index 9006cbcf6d7fc..519ef874c422e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/request_schema_validation.ts @@ -16,7 +16,6 @@ export const validateCreateRuleProps = (props: RuleCreateProps): string[] => { ...validateTimelineTitle(props), ...validateThreatMapping(props), ...validateThreshold(props), - ...validateMaxSignals(props), ]; }; @@ -78,11 +77,3 @@ const validateThreshold = (props: RuleCreateProps): string[] => { } return errors; }; - -const validateMaxSignals = (props: RuleCreateProps): string[] => { - const errors: string[] = []; - if (props.max_signals !== undefined && props.max_signals <= 0) { - errors.push('max_signals must be greater than 0'); - } - return errors; -}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts index 1f4d32bba56ff..5a60a3331a2ae 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.test.ts @@ -113,23 +113,5 @@ describe('Patch rule request schema, additional validation', () => { const errors = validatePatchRuleRequestBody(schema); expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); }); - - test('does NOT validate when max_signals has a value less than 1', () => { - const schema: PatchRuleRequestBody = { - ...getPatchRulesSchemaMock(), - max_signals: 0, - }; - const errors = validatePatchRuleRequestBody(schema); - expect(errors).toEqual(['max_signals must be greater than 0']); - }); - - test('does validate when max_signals is undefined', () => { - const schema: PatchRuleRequestBody = { - ...getPatchRulesSchemaMock(), - max_signals: undefined, - }; - const errors = validatePatchRuleRequestBody(schema); - expect(errors).toEqual([]); - }); }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts index 4c747b190cb64..cb34c7fa8ecb5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/request_schema_validation.ts @@ -16,7 +16,6 @@ export const validatePatchRuleRequestBody = (rule: PatchRuleRequestBody): string ...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule), - ...validateMaxSignals(rule), ]; }; @@ -78,11 +77,3 @@ const validateThreshold = (rule: PatchRuleRequestBody): string[] => { } return errors; }; - -const validateMaxSignals = (rule: PatchRuleRequestBody): string[] => { - const errors: string[] = []; - if (rule.max_signals !== undefined && rule.max_signals <= 0) { - errors.push('max_signals must be greater than 0'); - } - return errors; -}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts index c1641da33ab15..6bec7b1de3aba 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.test.ts @@ -69,22 +69,4 @@ describe('Update rule request schema, additional validation', () => { const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); - - test('does NOT validate when max_signals has a value less than 1', () => { - const schema: RuleUpdateProps = { - ...getUpdateRulesSchemaMock(), - max_signals: 0, - }; - const errors = validateUpdateRuleProps(schema); - expect(errors).toEqual(['max_signals must be greater than 0']); - }); - - test('does validate when max_signals is undefined', () => { - const schema: RuleUpdateProps = { - ...getUpdateRulesSchemaMock(), - max_signals: undefined, - }; - const errors = validateUpdateRuleProps(schema); - expect(errors).toEqual([]); - }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts index 16ba9f132ccf5..d58bbdc4bee05 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/request_schema_validation.ts @@ -16,7 +16,6 @@ export const validateUpdateRuleProps = (props: RuleUpdateProps): string[] => { ...validateTimelineId(props), ...validateTimelineTitle(props), ...validateThreshold(props), - ...validateMaxSignals(props), ]; }; @@ -75,11 +74,3 @@ const validateThreshold = (props: RuleUpdateProps): string[] => { } return errors; }; - -const validateMaxSignals = (props: RuleUpdateProps): string[] => { - const errors: string[] = []; - if (props.max_signals !== undefined && props.max_signals <= 0) { - errors.push('max_signals must be greater than 0'); - } - return errors; -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 7a5b380189215..e9c84c1c50f5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -23,11 +23,7 @@ import { createRules } from '../../../logic/crud/create_rules'; import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; -import { - transformValidate, - validateMaxSignals, - validateResponseActionsPermissions, -} from '../../../utils/validate'; +import { transformValidate, validateResponseActionsPermissions } from '../../../utils/validate'; export const createRuleRoute = ( router: SecuritySolutionPluginRouter, @@ -85,8 +81,6 @@ export const createRuleRoute = ( } } - validateMaxSignals(request.body.max_signals); - const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, ml, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 20d819b61c64d..5401e2361ca58 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -24,7 +24,7 @@ import { readRules } from '../../../logic/crud/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { getIdError } from '../../../utils/utils'; -import { transformValidate, validateMaxSignals } from '../../../utils/validate'; +import { transformValidate } from '../../../utils/validate'; export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.versioned @@ -68,7 +68,6 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPl // reject an unauthorized "promotion" to ML throwAuthzError(await mlAuthz.validateRuleType(params.type)); } - validateMaxSignals(params.max_signals); const existingRule = await readRules({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index d5110ad85ec95..215a0827b015f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -24,11 +24,7 @@ import { updateRules } from '../../../logic/crud/update_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { getIdError } from '../../../utils/utils'; -import { - transformValidate, - validateMaxSignals, - validateResponseActionsPermissions, -} from '../../../utils/validate'; +import { transformValidate, validateResponseActionsPermissions } from '../../../utils/validate'; export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.versioned @@ -68,8 +64,6 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }); throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); - validateMaxSignals(request.body.max_signals); - checkDefaultRuleExceptionListReferences({ exceptionLists: request.body.exceptions_list }); await validateRuleDefaultExceptionList({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index e5dbcc94fce2c..bd66139529cb3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -123,9 +123,3 @@ function isQueryRulePayload(rule: RuleCreateProps | RuleUpdateProps): rule is Qu function isQueryRuleObject(rule?: RuleAlertType): rule is Rule { return rule != null && 'params' in rule && 'responseActions' in rule?.params; } - -export const validateMaxSignals = (maxSignals?: number) => { - if (maxSignals !== undefined && maxSignals <= 0) { - throw new CustomHttpRequestError(`max_signals must be greater than 0`, 400); - } -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index b2559636f95c7..31f481cec126a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -310,6 +310,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = tuples, remainingGap, wroteWarningStatus: rangeTuplesWarningStatus, + warningStatusMessage: rangeTuplesWarningMessage, } = await getRuleRangeTuples({ startedAt, previousStartedAt, @@ -322,6 +323,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }); if (rangeTuplesWarningStatus) { wroteWarningStatus = rangeTuplesWarningStatus; + warningMessage = rangeTuplesWarningMessage; } if (remainingGap.asMilliseconds() > 0) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index cc4c507e24d75..146a5cd69047c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -451,69 +451,76 @@ describe('utils', () => { }); test('should return a single tuple if no gap', async () => { - const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ - previousStartedAt: moment().subtract(30, 's').toDate(), - startedAt: moment().subtract(30, 's').toDate(), - interval: '30s', - from: 'now-30s', - to: 'now', - maxSignals: 20, - ruleExecutionLogger, - alerting, - }); + const { tuples, remainingGap, wroteWarningStatus, warningStatusMessage } = + await getRuleRangeTuples({ + previousStartedAt: moment().subtract(30, 's').toDate(), + startedAt: moment().subtract(30, 's').toDate(), + interval: '30s', + from: 'now-30s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); expect(tuples.length).toEqual(1); expect(remainingGap.asMilliseconds()).toEqual(0); expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); }); test('should return a single tuple if malformed interval prevents gap calculation', async () => { - const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ - previousStartedAt: moment().subtract(30, 's').toDate(), - startedAt: moment().subtract(30, 's').toDate(), - interval: 'invalid', - from: 'now-30s', - to: 'now', - maxSignals: 20, - ruleExecutionLogger, - alerting, - }); + const { tuples, remainingGap, wroteWarningStatus, warningStatusMessage } = + await getRuleRangeTuples({ + previousStartedAt: moment().subtract(30, 's').toDate(), + startedAt: moment().subtract(30, 's').toDate(), + interval: 'invalid', + from: 'now-30s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(30); expect(tuples.length).toEqual(1); expect(remainingGap.asMilliseconds()).toEqual(0); expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); }); test('should return two tuples if gap and previouslyStartedAt', async () => { - const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ - previousStartedAt: moment().subtract(65, 's').toDate(), - startedAt: moment().toDate(), - interval: '50s', - from: 'now-55s', - to: 'now', - maxSignals: 20, - ruleExecutionLogger, - alerting, - }); + const { tuples, remainingGap, wroteWarningStatus, warningStatusMessage } = + await getRuleRangeTuples({ + previousStartedAt: moment().subtract(65, 's').toDate(), + startedAt: moment().toDate(), + interval: '50s', + from: 'now-55s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); const someTuple = tuples[1]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(55); expect(remainingGap.asMilliseconds()).toEqual(0); expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); }); test('should return five tuples when give long gap', async () => { - const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ - previousStartedAt: moment().subtract(65, 's').toDate(), // 64 is 5 times the interval + lookback, which will trigger max lookback - startedAt: moment().toDate(), - interval: '10s', - from: 'now-13s', - to: 'now', - maxSignals: 20, - ruleExecutionLogger, - alerting, - }); + const { tuples, remainingGap, wroteWarningStatus, warningStatusMessage } = + await getRuleRangeTuples({ + previousStartedAt: moment().subtract(65, 's').toDate(), // 64 is 5 times the interval + lookback, which will trigger max lookback + startedAt: moment().toDate(), + interval: '10s', + from: 'now-13s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); expect(tuples.length).toEqual(5); tuples.forEach((item, index) => { if (index === 0) { @@ -525,29 +532,32 @@ describe('utils', () => { }); expect(remainingGap.asMilliseconds()).toEqual(12000); expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); }); test('should return a single tuple when give a negative gap (rule ran sooner than expected)', async () => { - const { tuples, remainingGap, wroteWarningStatus } = await getRuleRangeTuples({ - previousStartedAt: moment().subtract(-15, 's').toDate(), - startedAt: moment().subtract(-15, 's').toDate(), - interval: '10s', - from: 'now-13s', - to: 'now', - maxSignals: 20, - ruleExecutionLogger, - alerting, - }); + const { tuples, remainingGap, wroteWarningStatus, warningStatusMessage } = + await getRuleRangeTuples({ + previousStartedAt: moment().subtract(-15, 's').toDate(), + startedAt: moment().subtract(-15, 's').toDate(), + interval: '10s', + from: 'now-13s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); expect(tuples.length).toEqual(1); const someTuple = tuples[0]; expect(moment(someTuple.to).diff(moment(someTuple.from), 's')).toEqual(13); expect(remainingGap.asMilliseconds()).toEqual(0); expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); }); test('should use alerting framework max alerts value if maxSignals is greater than limit', async () => { alerting.getConfig = jest.fn().mockReturnValue({ run: { alerts: { max: 10 } } }); - const { tuples, wroteWarningStatus } = await getRuleRangeTuples({ + const { tuples, wroteWarningStatus, warningStatusMessage } = await getRuleRangeTuples({ previousStartedAt: moment().subtract(30, 's').toDate(), startedAt: moment().subtract(30, 's').toDate(), interval: '30s', @@ -561,6 +571,9 @@ describe('utils', () => { expect(someTuple.maxSignals).toEqual(10); expect(tuples.length).toEqual(1); expect(wroteWarningStatus).toEqual(true); + expect(warningStatusMessage).toEqual( + "The rule's max_signals value (20) is greater than the Kibana alerting limit (10). The rule will only write a maximum of 10 per rule run." + ); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index 93f9d5f23763b..99b5c2caafe28 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -433,6 +433,7 @@ export const getRuleRangeTuples = async ({ const originalFrom = dateMath.parse(from, { forceNow: startedAt }); const originalTo = dateMath.parse(to, { forceNow: startedAt }); let wroteWarningStatus = false; + let warningStatusMessage; if (originalFrom == null || originalTo == null) { throw new Error('Failed to parse date math of rule.from or rule.to'); } @@ -441,7 +442,7 @@ export const getRuleRangeTuples = async ({ let maxSignalsToUse = maxSignals; if (maxSignals > maxAlertsAllowed) { maxSignalsToUse = maxAlertsAllowed; - const warningStatusMessage = `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit set in xpack.alerting.rules.run.alerts.max (${maxAlertsAllowed}). The rule will only write a maximum of ${maxAlertsAllowed} per rule run.`; + warningStatusMessage = `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit (${maxAlertsAllowed}). The rule will only write a maximum of ${maxAlertsAllowed} per rule run.`; await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatusEnum['partial failure'], message: warningStatusMessage, @@ -464,7 +465,7 @@ export const getRuleRangeTuples = async ({ interval )}"` ); - return { tuples, remainingGap: moment.duration(0), wroteWarningStatus }; + return { tuples, remainingGap: moment.duration(0), wroteWarningStatus, warningStatusMessage }; } const gap = getGapBetweenRuns({ @@ -497,6 +498,7 @@ export const getRuleRangeTuples = async ({ tuples: tuples.reverse(), remainingGap: moment.duration(remainingGapMilliseconds), wroteWarningStatus, + warningStatusMessage, }; }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts index b3c73965c5e27..0077ea7c281f5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_import_export/basic_license_essentials_tier/export_rules.ts @@ -68,7 +68,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should export defaultable fields when values are set', async () => { const defaultableFields: BaseDefaultableFields = { - max_signals: 100, + max_signals: 200, }; const ruleToExport = getCustomQueryRuleParams(defaultableFields); From dd0a87ee59ee366cfd74b0a6c96e5c9324fbaf4b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 24 Apr 2024 15:05:17 -0400 Subject: [PATCH 26/36] updates tests --- .../rule_patch/trial_license_complete_tier/patch_rules.ts | 5 ----- .../rule_update/trial_license_complete_tier/update_rules.ts | 5 ----- 2 files changed, 10 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts index 99547ec568c5f..95dbb33f3310a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts @@ -659,12 +659,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('max signals', () => { - beforeEach(async () => { - await createAlertsIndex(supertest, log); - }); - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts index 99815b0f992f3..697b5a43806ca 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts @@ -759,12 +759,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('max signals', () => { - beforeEach(async () => { - await createAlertsIndex(supertest, log); - }); - afterEach(async () => { - await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); }); From f400f99e2cdd00b0631442b7fd82760913245a17 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 25 Apr 2024 16:40:18 -0400 Subject: [PATCH 27/36] changes defaulting logic one last time --- .../components/max_signals/index.tsx | 4 +-- .../pages/rule_creation/helpers.test.ts | 31 ------------------- .../pages/rule_creation/helpers.ts | 3 +- .../pages/rule_creation/index.tsx | 16 ++-------- .../pages/detection_engine/rules/utils.ts | 4 +-- 5 files changed, 6 insertions(+), 52 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index 23ba1f43132c4..d266f38f345af 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -63,9 +63,7 @@ export const MaxSignals: React.FC = ({ {i18n.LESS_THAN_WARNING(maxAlertsPerRun)} ); } - const defaultToNumber = - maxAlertsPerRun < DEFAULT_MAX_SIGNALS ? maxAlertsPerRun : DEFAULT_MAX_SIGNALS; - textToRender.push(i18n.MAX_SIGNALS_HELP_TEXT(defaultToNumber)); + textToRender.push(i18n.MAX_SIGNALS_HELP_TEXT(DEFAULT_MAX_SIGNALS)); return textToRender; }, [hasWarning, maxAlertsPerRun]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index 3aa3719738b97..14dfbabf20d53 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -967,37 +967,6 @@ describe('helpers', () => { expect(result).toEqual(expected); }); - - test('returns formatted object with default max_signals value if maxSignals is undefined', () => { - const mockStepData: AboutStepRule = { - ...mockData, - maxSignals: undefined, - }; - const result = formatAboutStepData(mockStepData); - const expected: AboutStepRuleJson = { - author: ['Elastic'], - description: '24/7', - false_positives: ['test'], - license: 'Elastic License', - name: 'Query with rule-id', - note: '# this is some markdown documentation', - references: ['www.test.co'], - risk_score: 21, - risk_score_mapping: [], - severity: 'low', - severity_mapping: [], - tags: ['tag1', 'tag2'], - threat: getThreatMock(), - investigation_fields: { field_names: ['foo', 'bar'] }, - threat_indicator_path: undefined, - timestamp_override: undefined, - timestamp_override_fallback_disabled: undefined, - max_signals: 100, - setup: '# this is some setup documentation', - }; - - expect(result).toEqual(expected); - }); }); describe('formatActionsStepData', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 620f6c2762594..6399850524409 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -25,7 +25,6 @@ import type { Type, } from '@kbn/securitysolution-io-ts-alerting-types'; import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; -import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; import { assertUnreachable } from '../../../../../common/utility_types'; import { transformAlertToRuleAction, @@ -614,7 +613,7 @@ export const formatAboutStepData = ( timestamp_override: timestampOverride !== '' ? timestampOverride : undefined, timestamp_override_fallback_disabled: timestampOverrideFallbackDisabled, ...(!isEmpty(note) ? { note } : {}), - max_signals: maxSignals ? maxSignals : DEFAULT_MAX_SIGNALS, + max_signals: maxSignals, ...rest, }; return resp; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index 4c0384b26f5d7..d57198522b388 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -73,7 +73,6 @@ import { APP_UI_ID, DEFAULT_INDEX_KEY, DEFAULT_INDICATOR_SOURCE_PATH, - DEFAULT_MAX_SIGNALS, DEFAULT_THREAT_INDEX_KEY, } from '../../../../../common/constants'; import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; @@ -126,7 +125,6 @@ const CreateRulePageComponent: React.FC = () => { const { application, data: { dataViews }, - alerting: { getMaxAlertsPerRun }, } = useKibana().services; const loading = userInfoLoading || listsConfigLoading; const [activeStep, setActiveStep] = useState(RuleStep.defineRule); @@ -143,7 +141,6 @@ const CreateRulePageComponent: React.FC = () => { const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); const [threatIndicesConfig] = useUiSetting$(DEFAULT_THREAT_INDEX_KEY); - const maxAlertsPerRun = useMemo(() => getMaxAlertsPerRun(), [getMaxAlertsPerRun]); const defineStepDefault = useMemo( () => ({ ...stepDefineDefaultValue, @@ -152,16 +149,7 @@ const CreateRulePageComponent: React.FC = () => { }), [indicesConfig, threatIndicesConfig] ); - const aboutStepDefault = useMemo(() => { - const defaultMaxSignals = - maxAlertsPerRun !== undefined && maxAlertsPerRun < DEFAULT_MAX_SIGNALS - ? maxAlertsPerRun - : DEFAULT_MAX_SIGNALS; - return { - ...stepAboutDefaultValue, - maxSignals: defaultMaxSignals, - }; - }, [maxAlertsPerRun]); + const kibanaAbsoluteUrl = useMemo( () => application.getUrlForApp(`${APP_UI_ID}`, { @@ -190,7 +178,7 @@ const CreateRulePageComponent: React.FC = () => { setEqlOptionsSelected, } = useRuleForms({ defineStepDefault, - aboutStepDefault, + aboutStepDefault: stepAboutDefaultValue, scheduleStepDefault: defaultSchedule, actionsStepDefault, }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 39a8a7ad9d464..4f40eee5d6978 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -8,7 +8,7 @@ import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; -import { DEFAULT_THREAT_MATCH_QUERY } from '../../../../../common/constants'; +import { DEFAULT_MAX_SIGNALS, DEFAULT_THREAT_MATCH_QUERY } from '../../../../../common/constants'; import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; import type { AboutStepRule, DefineStepRule, RuleStepsOrder, ScheduleStepRule } from './types'; import { DataSourceType, GroupByOptions, RuleStep } from './types'; @@ -96,7 +96,7 @@ export const stepAboutDefaultValue: AboutStepRule = { setup: '', threatIndicatorPath: undefined, timestampOverrideFallbackDisabled: undefined, - maxSignals: 100, + maxSignals: DEFAULT_MAX_SIGNALS, }; const DEFAULT_INTERVAL = '5m'; From 8add1095ab4192f6e51f2e4babd509fd0f96e2c4 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 25 Apr 2024 19:53:23 -0400 Subject: [PATCH 28/36] updates integration tests to match unified method --- .../perform_bulk_action.ts | 427 ++++++++++-------- .../create_rules.ts | 63 ++- .../create_rules_bulk.ts | 32 +- .../create_rules.ts | 43 -- .../patch_rules.ts | 76 +++- .../patch_rules_bulk.ts | 63 ++- .../patch_rules.ts | 49 -- .../update_rules.ts | 93 +++- .../update_rules_bulk.ts | 56 ++- .../update_rules.ts | 56 --- 10 files changed, 568 insertions(+), 390 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts index 8b59070202b08..79413e64e86aa 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, @@ -19,9 +19,11 @@ import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/com import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants'; +import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { binaryToString, getSimpleMlRule, + getCustomQueryRuleParams, getSimpleRule, getSimpleRuleOutput, getSlackAction, @@ -42,6 +44,7 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const securitySolutionApi = getService('securitySolutionApi'); const es = getService('es'); const log = getService('log'); const esArchiver = getService('esArchiver'); @@ -111,10 +114,10 @@ export default ({ getService }: FtrProviderContext): void => { const rule = removeServerGeneratedProperties(JSON.parse(ruleJson)); const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - expect(rule).to.eql(expectedRule); + expect(rule).toEqual(expectedRule); const exportDetails = JSON.parse(exportDetailsJson); - expect(exportDetails).to.eql({ + expect(exportDetails).toEqual({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, exported_count: 1, @@ -132,6 +135,32 @@ export default ({ getService }: FtrProviderContext): void => { missing_action_connections: [], }); }); + + it('should export rules with defaultbale fields when values are set', async () => { + const defaultableFields: BaseDefaultableFields = { + max_signals: 100, + }; + const mockRule = getCustomQueryRuleParams(defaultableFields); + + await securitySolutionApi.createRule({ body: mockRule }); + + const { body } = await securitySolutionApi + .performBulkAction({ + query: {}, + body: { + action: BulkActionTypeEnum.export, + }, + }) + .expect(200) + .expect('Content-Type', 'application/ndjson') + .expect('Content-Disposition', 'attachment; filename="rules_export.ndjson"') + .parse(binaryToString); + + const [ruleJson] = body.toString().split(/\n/); + + expect(JSON.parse(ruleJson)).toMatchObject(defaultableFields); + }); + it('should export rules with actions connectors', async () => { // create new actions const webHookAction = await createWebHookConnector(); @@ -184,7 +213,7 @@ export default ({ getService }: FtrProviderContext): void => { const rule = removeServerGeneratedProperties(JSON.parse(ruleJson)); const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - expect(rule).to.eql({ + expect(rule).toEqual({ ...expectedRule, actions: [ { @@ -200,14 +229,14 @@ export default ({ getService }: FtrProviderContext): void => { ], }); const { attributes, id, type } = JSON.parse(connectorsJson); - expect(attributes.actionTypeId).to.eql(exportedConnectors.attributes.actionTypeId); - expect(id).to.eql(exportedConnectors.id); - expect(type).to.eql(exportedConnectors.type); - expect(attributes.name).to.eql(exportedConnectors.attributes.name); - expect(attributes.secrets).to.eql(exportedConnectors.attributes.secrets); - expect(attributes.isMissingSecrets).to.eql(exportedConnectors.attributes.isMissingSecrets); + expect(attributes.actionTypeId).toEqual(exportedConnectors.attributes.actionTypeId); + expect(id).toEqual(exportedConnectors.id); + expect(type).toEqual(exportedConnectors.type); + expect(attributes.name).toEqual(exportedConnectors.attributes.name); + expect(attributes.secrets).toEqual(exportedConnectors.attributes.secrets); + expect(attributes.isMissingSecrets).toEqual(exportedConnectors.attributes.isMissingSecrets); const exportDetails = JSON.parse(exportDetailsJson); - expect(exportDetails).to.eql({ + expect(exportDetails).toEqual({ exported_exception_list_count: 0, exported_exception_list_item_count: 0, exported_count: 2, @@ -235,10 +264,10 @@ export default ({ getService }: FtrProviderContext): void => { .send({ query: '', action: BulkActionTypeEnum.delete }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the deleted rule is returned with the response - expect(body.attributes.results.deleted[0].name).to.eql(testRule.name); + expect(body.attributes.results.deleted[0].name).toEqual(testRule.name); // Check that the updates have been persisted await fetchRule(ruleId).expect(404); @@ -252,14 +281,14 @@ export default ({ getService }: FtrProviderContext): void => { .send({ query: '', action: BulkActionTypeEnum.enable }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(true); + expect(body.attributes.results.updated[0].enabled).toEqual(true); // Check that the updates have been persisted const { body: ruleBody } = await fetchRule(ruleId).expect(200); - expect(ruleBody.enabled).to.eql(true); + expect(ruleBody.enabled).toEqual(true); }); it('should disable rules', async () => { @@ -270,20 +299,24 @@ export default ({ getService }: FtrProviderContext): void => { .send({ query: '', action: BulkActionTypeEnum.disable }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].enabled).to.eql(false); + expect(body.attributes.results.updated[0].enabled).toEqual(false); // Check that the updates have been persisted const { body: ruleBody } = await fetchRule(ruleId).expect(200); - expect(ruleBody.enabled).to.eql(false); + expect(ruleBody.enabled).toEqual(false); }); it('should duplicate rules', async () => { const ruleId = 'ruleId'; - const ruleToDuplicate = getSimpleRule(ruleId); - await createRule(supertest, log, ruleToDuplicate); + const ruleToDuplicate = getCustomQueryRuleParams({ + rule_id: ruleId, + max_signals: 100, + }); + + await securitySolutionApi.createRule({ body: ruleToDuplicate }); const { body } = await postBulkAction() .send({ @@ -293,10 +326,12 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the duplicated rule is returned with the response - expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + expect(body.attributes.results.created[0].name).toEqual( + `${ruleToDuplicate.name} [Duplicate]` + ); // Check that the updates have been persisted const { body: rulesResponse } = await supertest @@ -305,7 +340,7 @@ export default ({ getService }: FtrProviderContext): void => { .set('elastic-api-version', '2023-10-31') .expect(200); - expect(rulesResponse.total).to.eql(2); + expect(rulesResponse.total).toEqual(2); }); it('should duplicate rules with exceptions - expired exceptions included', async () => { @@ -381,15 +416,17 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Item should have been duplicated, even if expired - expect(foundItems.total).to.eql(1); + expect(foundItems.total).toEqual(1); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the duplicated rule is returned with the response - expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + expect(body.attributes.results.created[0].name).toEqual( + `${ruleToDuplicate.name} [Duplicate]` + ); // Check that the exceptions are duplicated - expect(body.attributes.results.created[0].exceptions_list).to.eql([ + expect(body.attributes.results.created[0].exceptions_list).toEqual([ { type: exceptionList.type, list_id: exceptionList.list_id, @@ -411,7 +448,7 @@ export default ({ getService }: FtrProviderContext): void => { .set('elastic-api-version', '2023-10-31') .expect(200); - expect(rulesResponse.total).to.eql(2); + expect(rulesResponse.total).toEqual(2); }); it('should duplicate rules with exceptions - expired exceptions excluded', async () => { @@ -487,15 +524,17 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Item should NOT have been duplicated, since it is expired - expect(foundItems.total).to.eql(0); + expect(foundItems.total).toEqual(0); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the duplicated rule is returned with the response - expect(body.attributes.results.created[0].name).to.eql(`${ruleToDuplicate.name} [Duplicate]`); + expect(body.attributes.results.created[0].name).toEqual( + `${ruleToDuplicate.name} [Duplicate]` + ); // Check that the exceptions are duplicted - expect(body.attributes.results.created[0].exceptions_list).to.eql([ + expect(body.attributes.results.created[0].exceptions_list).toEqual([ { type: exceptionList.type, list_id: exceptionList.list_id, @@ -517,7 +556,7 @@ export default ({ getService }: FtrProviderContext): void => { .set('kbn-xsrf', 'true') .expect(200); - expect(rulesResponse.total).to.eql(2); + expect(rulesResponse.total).toEqual(2); }); describe('edit action', () => { @@ -575,7 +614,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -583,12 +622,12 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags); + expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.tags).to.eql(resultingTags); + expect(updatedRule.tags).toEqual(resultingTags); }); }); @@ -632,7 +671,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -640,12 +679,12 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags); + expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.tags).to.eql(resultingTags); + expect(updatedRule.tags).toEqual(resultingTags); }); }); @@ -688,7 +727,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -696,12 +735,12 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags); + expect(bulkEditResponse.attributes.results.updated[0].tags).toEqual(resultingTags); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.tags).to.eql(resultingTags); + expect(updatedRule.tags).toEqual(resultingTags); }); }); @@ -765,7 +804,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, @@ -773,14 +812,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the rules is returned as skipped with expected skip reason - expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).to.eql( + expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).toEqual( 'RULE_NOT_MODIFIED' ); // Check that the no changes have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.tags).to.eql(resultingTags); + expect(updatedRule.tags).toEqual(resultingTags); }); } ); @@ -804,7 +843,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -812,12 +851,12 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(['initial-index-*']); + expect(bulkEditResponse.attributes.results.updated[0].index).toEqual(['initial-index-*']); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(['initial-index-*']); + expect(updatedRule.index).toEqual(['initial-index-*']); }); it('should add index patterns to rules', async () => { @@ -839,7 +878,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -847,14 +886,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].index).to.eql( + expect(bulkEditResponse.attributes.results.updated[0].index).toEqual( resultingIndexPatterns ); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(resultingIndexPatterns); + expect(updatedRule.index).toEqual(resultingIndexPatterns); }); it('should delete index patterns from rules', async () => { @@ -876,7 +915,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -884,14 +923,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(bulkEditResponse.attributes.results.updated[0].index).to.eql( + expect(bulkEditResponse.attributes.results.updated[0].index).toEqual( resultingIndexPatterns ); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(resultingIndexPatterns); + expect(updatedRule.index).toEqual(resultingIndexPatterns); }); it('should return error if index patterns action is applied to machine learning rule', async () => { @@ -910,8 +949,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ + failed: 1, + skipped: 0, + succeeded: 0, + total: 1, + }); + expect(body.attributes.errors[0]).toEqual({ message: "Index patterns can't be added. Machine learning rule doesn't have index patterns property", status_code: 500, @@ -943,8 +987,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ + failed: 1, + skipped: 0, + succeeded: 0, + total: 1, + }); + expect(body.attributes.errors[0]).toEqual({ message: "Mutated params invalid: Index patterns can't be empty", status_code: 500, rules: [ @@ -976,8 +1025,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ + failed: 1, + skipped: 0, + succeeded: 0, + total: 1, + }); + expect(body.attributes.errors[0]).toEqual({ message: "Mutated params invalid: Index patterns can't be empty", status_code: 500, rules: [ @@ -991,7 +1045,7 @@ export default ({ getService }: FtrProviderContext): void => { // Check that the rule hasn't been updated const { body: reFetchedRule } = await fetchRule(ruleId).expect(200); - expect(reFetchedRule.index).to.eql(['simple-index-*']); + expect(reFetchedRule.index).toEqual(['simple-index-*']); }); const skipIndexPatternsUpdateCases = [ @@ -1063,7 +1117,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(bulkEditResponse.attributes.summary).to.eql({ + expect(bulkEditResponse.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, @@ -1071,14 +1125,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the rules is returned as skipped with expected skip reason - expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).to.eql( + expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).toEqual( 'RULE_NOT_MODIFIED' ); // Check that the no changes have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(resultingIndexPatterns); + expect(updatedRule.index).toEqual(resultingIndexPatterns); }); } ); @@ -1106,17 +1160,17 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId); - expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle); + expect(body.attributes.results.updated[0].timeline_id).toEqual(timelineId); + expect(body.attributes.results.updated[0].timeline_title).toEqual(timelineTitle); // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.timeline_id).to.eql(timelineId); - expect(rule.timeline_title).to.eql(timelineTitle); + expect(rule.timeline_id).toEqual(timelineId); + expect(rule.timeline_title).toEqual(timelineTitle); }); it('should correctly remove timeline template', async () => { @@ -1130,8 +1184,8 @@ export default ({ getService }: FtrProviderContext): void => { }); // ensure rule has been created with timeline properties - expect(createdRule.timeline_id).to.be(timelineId); - expect(createdRule.timeline_title).to.be(timelineTitle); + expect(createdRule.timeline_id).toBe(timelineId); + expect(createdRule.timeline_title).toBe(timelineTitle); const { body } = await postBulkAction() .send({ @@ -1149,17 +1203,17 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].timeline_id).to.be(undefined); - expect(body.attributes.results.updated[0].timeline_title).to.be(undefined); + expect(body.attributes.results.updated[0].timeline_id).toBe(undefined); + expect(body.attributes.results.updated[0].timeline_title).toBe(undefined); // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.timeline_id).to.be(undefined); - expect(rule.timeline_title).to.be(undefined); + expect(rule.timeline_id).toBe(undefined); + expect(rule.timeline_title).toBe(undefined); }); it('should return error if index patterns action is applied to machine learning rule', async () => { @@ -1178,8 +1232,8 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).toEqual({ message: "Index patterns can't be added. Machine learning rule doesn't have index patterns property", status_code: 500, @@ -1211,8 +1265,8 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).toEqual({ message: "Mutated params invalid: Index patterns can't be empty", status_code: 500, rules: [ @@ -1240,12 +1294,12 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.results.updated[0].version).to.be(rule.version + 1); + expect(body.attributes.results.updated[0].version).toBe(rule.version + 1); // Check that the updates have been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.version).to.be(rule.version + 1); + expect(updatedRule.version).toBe(rule.version + 1); }); describe('prebuilt rules', () => { @@ -1301,13 +1355,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1, }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.errors[0]).toEqual({ message: "Elastic rule can't be edited", status_code: 500, rules: [ @@ -1369,12 +1423,12 @@ export default ({ getService }: FtrProviderContext): void => { ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); }); it('should set action correctly to existing non empty actions list', async () => { @@ -1427,12 +1481,12 @@ export default ({ getService }: FtrProviderContext): void => { ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); }); it('should set actions to empty list, actions payload is empty list', async () => { @@ -1472,12 +1526,12 @@ 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([]); + expect(body.attributes.results.updated[0].actions).toEqual([]); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql([]); + expect(readRule.actions).toEqual([]); }); }); @@ -1521,12 +1575,12 @@ export default ({ getService }: FtrProviderContext): void => { ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); }); it('should add action correctly to non empty actions list of the same type', async () => { @@ -1586,12 +1640,12 @@ export default ({ getService }: FtrProviderContext): void => { ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); }); it('should add action correctly to non empty actions list of a different type', async () => { @@ -1659,12 +1713,12 @@ export default ({ getService }: FtrProviderContext): void => { ]; // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + expect(body.attributes.results.updated[0].actions).toEqual(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql(expectedRuleActions); + expect(readRule.actions).toEqual(expectedRuleActions); }); it('should not change actions of rule if empty list of actions added', async () => { @@ -1704,12 +1758,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Check that the rule is skipped and was not updated - expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id); + expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql([ + expect(readRule.actions).toEqual([ { ...defaultRuleAction, uuid: createdRule.actions[0].uuid, @@ -1755,13 +1809,13 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Check that the rule is skipped and was not updated - expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id); + expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.throttle).to.eql(undefined); - expect(readRule.actions).to.eql(createdRule.actions); + expect(readRule.throttle).toEqual(undefined); + expect(readRule.actions).toEqual(createdRule.actions); }); }); @@ -1803,7 +1857,7 @@ export default ({ getService }: FtrProviderContext): void => { const editedRule = body.attributes.results.updated[0]; // Check that the updated rule is returned with the response - expect(editedRule.actions).to.eql([ + expect(editedRule.actions).toEqual([ { ...webHookActionMock, id: webHookConnector.id, @@ -1813,12 +1867,12 @@ export default ({ getService }: FtrProviderContext): void => { }, ]); // version of prebuilt rule should not change - expect(editedRule.version).to.be(prebuiltRule.version); + expect(editedRule.version).toBe(prebuiltRule.version); // Check that the updates have been persisted const { body: readRule } = await fetchRule(prebuiltRule.rule_id).expect(200); - expect(readRule.actions).to.eql([ + expect(readRule.actions).toEqual([ { ...webHookActionMock, id: webHookConnector.id, @@ -1827,7 +1881,7 @@ export default ({ getService }: FtrProviderContext): void => { frequency: { summary: true, throttle: '1h', notifyWhen: 'onThrottleInterval' }, }, ]); - expect(prebuiltRule.version).to.be(readRule.version); + expect(prebuiltRule.version).toBe(readRule.version); }); }); @@ -1863,13 +1917,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1, }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.errors[0]).toEqual({ message: "Elastic rule can't be edited", status_code: 500, rules: [ @@ -1883,9 +1937,9 @@ export default ({ getService }: FtrProviderContext): void => { // Check that the updates were not made const { body: readRule } = await fetchRule(prebuiltRule.rule_id).expect(200); - expect(readRule.actions).to.eql(prebuiltRule.actions); - expect(readRule.tags).to.eql(prebuiltRule.tags); - expect(readRule.version).to.be(prebuiltRule.version); + expect(readRule.actions).toEqual(prebuiltRule.actions); + expect(readRule.tags).toEqual(prebuiltRule.tags); + expect(readRule.version).toBe(prebuiltRule.version); }); }); @@ -1925,12 +1979,12 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); // Check that the rule is skipped and was not updated - expect(body.attributes.results.skipped[0].id).to.eql(createdRule.id); + expect(body.attributes.results.skipped[0].id).toEqual(createdRule.id); // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.throttle).to.eql(undefined); + expect(rule.throttle).toEqual(undefined); }); }); @@ -1987,7 +2041,7 @@ 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(expectedThrottle); + expect(body.attributes.results.updated[0].throttle).toEqual(expectedThrottle); const expectedActions = body.attributes.results.updated[0].actions.map( (action: any) => ({ @@ -2004,8 +2058,8 @@ export default ({ getService }: FtrProviderContext): void => { // 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); + expect(rule.throttle).toEqual(expectedThrottle); + expect(rule.actions).toEqual(expectedActions); }); }); }); @@ -2047,7 +2101,7 @@ export default ({ getService }: FtrProviderContext): void => { // Check whether notifyWhen set correctly const { body: rule } = await fetchRuleByAlertApi(createdRule.id).expect(200); - expect(rule.notify_when).to.eql(expected.notifyWhen); + expect(rule.notify_when).toEqual(expected.notifyWhen); }); }); }); @@ -2078,10 +2132,10 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(400); - expect(body.statusCode).to.eql(400); - expect(body.error).to.eql('Bad Request'); - expect(body.message).to.contain('edit.0.value.interval: Invalid'); - expect(body.message).to.contain('edit.0.value.lookback: Invalid'); + expect(body.statusCode).toEqual(400); + expect(body.error).toEqual('Bad Request'); + expect(body.message).toContain('edit.0.value.interval: Invalid'); + expect(body.message).toContain('edit.0.value.lookback: Invalid'); }); it('should update schedule values in rules with a valid payload', async () => { @@ -2108,11 +2162,16 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ + failed: 0, + skipped: 0, + succeeded: 1, + total: 1, + }); - expect(body.attributes.results.updated[0].interval).to.eql(interval); - expect(body.attributes.results.updated[0].meta).to.eql({ from: `${lookbackMinutes}m` }); - expect(body.attributes.results.updated[0].from).to.eql( + expect(body.attributes.results.updated[0].interval).toEqual(interval); + expect(body.attributes.results.updated[0].meta).toEqual({ from: `${lookbackMinutes}m` }); + expect(body.attributes.results.updated[0].from).toEqual( `now-${(intervalMinutes + lookbackMinutes) * 60}s` ); }); @@ -2144,7 +2203,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setIndexBody.attributes.summary).to.eql({ + expect(setIndexBody.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -2152,13 +2211,13 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']); - expect(setIndexBody.attributes.results.updated[0].data_view_id).to.eql(undefined); + expect(setIndexBody.attributes.results.updated[0].index).toEqual(['initial-index-*']); + expect(setIndexBody.attributes.results.updated[0].data_view_id).toEqual(undefined); // Check that the updates have been persisted const { body: setIndexRule } = await fetchRule(ruleId).expect(200); - expect(setIndexRule.index).to.eql(['initial-index-*']); + expect(setIndexRule.index).toEqual(['initial-index-*']); }); it('should return skipped rule and NOT add an index pattern to a rule or overwrite the data view when overwrite_data_views is false', async () => { @@ -2185,25 +2244,25 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setIndexBody.attributes.summary).to.eql({ + expect(setIndexBody.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1, }); - expect(setIndexBody.attributes.errors).to.be(undefined); + expect(setIndexBody.attributes.errors).toBe(undefined); // Check that the skipped rule is returned with the response - expect(setIndexBody.attributes.results.skipped[0].id).to.eql(simpleRule.id); - expect(setIndexBody.attributes.results.skipped[0].name).to.eql(simpleRule.name); - expect(setIndexBody.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED'); + expect(setIndexBody.attributes.results.skipped[0].id).toEqual(simpleRule.id); + expect(setIndexBody.attributes.results.skipped[0].name).toEqual(simpleRule.name); + expect(setIndexBody.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED'); // Check that the rule has not been updated const { body: setIndexRule } = await fetchRule(ruleId).expect(200); - expect(setIndexRule.index).to.eql(undefined); - expect(setIndexRule.data_view_id).to.eql(dataViewId); + expect(setIndexRule.index).toEqual(undefined); + expect(setIndexRule.data_view_id).toEqual(dataViewId); }); it('should set an index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => { @@ -2230,7 +2289,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setIndexBody.attributes.summary).to.eql({ + expect(setIndexBody.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -2238,14 +2297,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']); - expect(setIndexBody.attributes.results.updated[0].data_view_id).to.eql(undefined); + expect(setIndexBody.attributes.results.updated[0].index).toEqual(['initial-index-*']); + expect(setIndexBody.attributes.results.updated[0].data_view_id).toEqual(undefined); // Check that the updates have been persisted const { body: setIndexRule } = await fetchRule(ruleId).expect(200); - expect(setIndexRule.index).to.eql(['initial-index-*']); - expect(setIndexRule.data_view_id).to.eql(undefined); + expect(setIndexRule.index).toEqual(['initial-index-*']); + expect(setIndexRule.data_view_id).toEqual(undefined); }); it('should return error when set an empty index pattern to a rule and overwrite the data view when overwrite_data_views is true', async () => { @@ -2271,8 +2330,8 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).toEqual({ message: "Mutated params invalid: Index patterns can't be empty", status_code: 500, rules: [ @@ -2307,25 +2366,25 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(setIndexBody.attributes.summary).to.eql({ + expect(setIndexBody.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1, }); - expect(setIndexBody.attributes.errors).to.be(undefined); + expect(setIndexBody.attributes.errors).toBe(undefined); // Check that the skipped rule is returned with the response - expect(setIndexBody.attributes.results.skipped[0].id).to.eql(simpleRule.id); - expect(setIndexBody.attributes.results.skipped[0].name).to.eql(simpleRule.name); - expect(setIndexBody.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED'); + expect(setIndexBody.attributes.results.skipped[0].id).toEqual(simpleRule.id); + expect(setIndexBody.attributes.results.skipped[0].name).toEqual(simpleRule.name); + expect(setIndexBody.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED'); // Check that the rule has not been updated const { body: setIndexRule } = await fetchRule(ruleId).expect(200); - expect(setIndexRule.index).to.eql(undefined); - expect(setIndexRule.data_view_id).to.eql(dataViewId); + expect(setIndexRule.index).toEqual(undefined); + expect(setIndexRule.data_view_id).toEqual(dataViewId); }); // This rule will now not have a source defined - as has been the behavior of rules since the beginning @@ -2353,7 +2412,7 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, @@ -2361,14 +2420,14 @@ export default ({ getService }: FtrProviderContext): void => { }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].index).to.eql(undefined); - expect(body.attributes.results.updated[0].data_view_id).to.eql(undefined); + expect(body.attributes.results.updated[0].index).toEqual(undefined); + expect(body.attributes.results.updated[0].data_view_id).toEqual(undefined); // Check that the updates have been persisted const { body: setIndexRule } = await fetchRule(ruleId).expect(200); - expect(setIndexRule.index).to.eql(undefined); - expect(setIndexRule.data_view_id).to.eql(undefined); + expect(setIndexRule.index).toEqual(undefined); + expect(setIndexRule.data_view_id).toEqual(undefined); }); it('should return error if all index patterns removed from a rule with data views and overwrite_data_views is true', async () => { @@ -2394,8 +2453,8 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(500); - expect(body.attributes.summary).to.eql({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); - expect(body.attributes.errors[0]).to.eql({ + expect(body.attributes.summary).toEqual({ failed: 1, skipped: 0, succeeded: 0, total: 1 }); + expect(body.attributes.errors[0]).toEqual({ message: "Mutated params invalid: Index patterns can't be empty", status_code: 500, rules: [ @@ -2430,13 +2489,13 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 1, succeeded: 0, total: 1 }); - expect(body.attributes.errors).to.be(undefined); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1 }); + expect(body.attributes.errors).toBe(undefined); // Check that the skipped rule is returned with the response - expect(body.attributes.results.skipped[0].id).to.eql(rule.id); - expect(body.attributes.results.skipped[0].name).to.eql(rule.name); - expect(body.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED'); + expect(body.attributes.results.skipped[0].id).toEqual(rule.id); + expect(body.attributes.results.skipped[0].name).toEqual(rule.name); + expect(body.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED'); }); }); @@ -2466,17 +2525,17 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].tags).to.eql(['tag1', 'tag2', 'tag3']); - expect(body.attributes.results.updated[0].index).to.eql(['index1-*', 'initial-index-*']); + expect(body.attributes.results.updated[0].tags).toEqual(['tag1', 'tag2', 'tag3']); + expect(body.attributes.results.updated[0].index).toEqual(['index1-*', 'initial-index-*']); // Check that the rule has been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(['index1-*', 'initial-index-*']); - expect(updatedRule.tags).to.eql(['tag1', 'tag2', 'tag3']); + expect(updatedRule.index).toEqual(['index1-*', 'initial-index-*']); + expect(updatedRule.tags).toEqual(['tag1', 'tag2', 'tag3']); }); it('should return one updated rule when applying one valid operation and one operation to be skipped on a rule', async () => { @@ -2506,17 +2565,17 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].tags).to.eql(['tag1', 'tag2']); - expect(body.attributes.results.updated[0].index).to.eql(['index1-*', 'initial-index-*']); + expect(body.attributes.results.updated[0].tags).toEqual(['tag1', 'tag2']); + expect(body.attributes.results.updated[0].index).toEqual(['index1-*', 'initial-index-*']); // Check that the rule has been persisted const { body: updatedRule } = await fetchRule(ruleId).expect(200); - expect(updatedRule.index).to.eql(['index1-*', 'initial-index-*']); - expect(updatedRule.tags).to.eql(['tag1', 'tag2']); + expect(updatedRule.index).toEqual(['index1-*', 'initial-index-*']); + expect(updatedRule.tags).toEqual(['tag1', 'tag2']); }); it('should return one skipped rule when two (all) operations result in a no-op', async () => { @@ -2546,18 +2605,18 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 1, succeeded: 0, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 1, succeeded: 0, total: 1 }); // Check that the skipped rule is returned with the response - expect(body.attributes.results.skipped[0].name).to.eql(rule.name); - expect(body.attributes.results.skipped[0].id).to.eql(rule.id); - expect(body.attributes.results.skipped[0].skip_reason).to.eql('RULE_NOT_MODIFIED'); + expect(body.attributes.results.skipped[0].name).toEqual(rule.name); + expect(body.attributes.results.skipped[0].id).toEqual(rule.id); + expect(body.attributes.results.skipped[0].skip_reason).toEqual('RULE_NOT_MODIFIED'); // Check that no change to the rule have been persisted const { body: skippedRule } = await fetchRule(ruleId).expect(200); - expect(skippedRule.index).to.eql(['index1-*']); - expect(skippedRule.tags).to.eql(['tag1', 'tag2']); + expect(skippedRule.index).toEqual(['index1-*']); + expect(skippedRule.tags).toEqual(['tag1', 'tag2']); }); }); @@ -2585,7 +2644,7 @@ export default ({ getService }: FtrProviderContext): void => { ) ); - expect(responses.filter((r) => r.body.statusCode === 429).length).to.eql(5); + expect(responses.filter((r) => r.body.statusCode === 429).length).toEqual(5); }); it('should bulk update rule by id', async () => { @@ -2613,17 +2672,17 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - expect(body.attributes.summary).to.eql({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); + expect(body.attributes.summary).toEqual({ failed: 0, skipped: 0, succeeded: 1, total: 1 }); // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].timeline_id).to.eql(timelineId); - expect(body.attributes.results.updated[0].timeline_title).to.eql(timelineTitle); + expect(body.attributes.results.updated[0].timeline_id).toEqual(timelineId); + expect(body.attributes.results.updated[0].timeline_title).toEqual(timelineTitle); // Check that the updates have been persisted const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.timeline_id).to.eql(timelineId); - expect(rule.timeline_title).to.eql(timelineTitle); + expect(rule.timeline_id).toEqual(timelineId); + expect(rule.timeline_title).toEqual(timelineTitle); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts index 28a362213ae3b..46ef168f4dd01 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { RuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { @@ -16,6 +16,7 @@ import { removeServerGeneratedPropertiesIncludingRuleId, updateUsername, getSimpleRuleOutput, + getCustomQueryRuleParams, } from '../../../utils'; import { createAlertsIndex, @@ -65,7 +66,28 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompare = removeServerGeneratedProperties(body); const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should create a rule with defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + const { body: createdRuleResponse } = await securitySolutionApi + .createRule({ body: expectedRule }) + .expect(200); + + expect(createdRuleResponse).toMatchObject(expectedRule); + + const { body: createdRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(createdRule).toMatchObject(expectedRule); }); it('should create a single rule without an input index', async () => { @@ -120,7 +142,7 @@ export default ({ getService }: FtrProviderContext) => { ELASTICSEARCH_USERNAME ); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should create a single rule without a rule_id', async () => { @@ -134,7 +156,7 @@ export default ({ getService }: FtrProviderContext) => { ELASTICSEARCH_USERNAME ); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should cause a 409 conflict if we attempt to create the same rule_id twice', async () => { @@ -144,11 +166,42 @@ export default ({ getService }: FtrProviderContext) => { .createRule({ body: getSimpleRule() }) .expect(409); - expect(body).to.eql({ + expect(body).toEqual({ message: 'rule_id: "rule-1" already exists', status_code: 409, }); }); + + describe('max_signals', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('creates a rule with max_signals defaulted to 100 when not present', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: getCustomQueryRuleParams(), + }) + .expect(200); + + expect(body.max_signals).toEqual(100); + }); + + it('does NOT create a rule when max_signals is less than 1', async () => { + const { body } = await securitySolutionApi + .createRule({ + body: { + ...getCustomQueryRuleParams(), + max_signals: 0, + }, + }) + .expect(400); + + expect(body.message).toBe( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts index 3a1cdbbf373ed..17b278345d4bf 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/basic_license_essentials_tier/create_rules_bulk.ts @@ -5,11 +5,12 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { + getCustomQueryRuleParams, getSimpleRule, getSimpleRuleOutput, getSimpleRuleOutputWithoutRuleId, @@ -64,7 +65,28 @@ export default ({ getService }: FtrProviderContext): void => { const bodyToCompare = removeServerGeneratedProperties(body[0]); const expectedRule = updateUsername(getSimpleRuleOutput(), ELASTICSEARCH_USERNAME); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should create a rule with defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + const { body: createdRulesBulkResponse } = await securitySolutionApi + .bulkCreateRules({ body: [expectedRule] }) + .expect(200); + + expect(createdRulesBulkResponse[0]).toMatchObject(expectedRule); + + const { body: createdRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(createdRule).toMatchObject(expectedRule); }); it('should create a single rule without a rule_id', async () => { @@ -78,7 +100,7 @@ export default ({ getService }: FtrProviderContext): void => { ELASTICSEARCH_USERNAME ); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should return a 200 ok but have a 409 conflict if we attempt to create the same rule_id twice', async () => { @@ -86,7 +108,7 @@ export default ({ getService }: FtrProviderContext): void => { .bulkCreateRules({ body: [getSimpleRule(), getSimpleRule()] }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { error: { message: 'rule_id: "rule-1" already exists', @@ -104,7 +126,7 @@ export default ({ getService }: FtrProviderContext): void => { .bulkCreateRules({ body: [getSimpleRule()] }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { error: { message: 'rule_id: "rule-1" already exists', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts index f179c43c8bbea..319a4a20c9c96 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_creation/trial_license_complete_tier/create_rules.ts @@ -482,49 +482,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('max_signals', () => { - beforeEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('creates a rule with max_signals', async () => { - const { body } = await securitySolutionApi - .createRule({ - body: getCustomQueryRuleParams({ - max_signals: 200, - }), - }) - .expect(200); - - expect(body.max_signals).toEqual(200); - }); - - it('creates a rule with max_signals defaulted to 100 when not present', async () => { - const { body } = await securitySolutionApi - .createRule({ - body: getCustomQueryRuleParams(), - }) - .expect(200); - - expect(body.max_signals).toEqual(100); - }); - - it('does NOT create a rule when max_signals is less than 1', async () => { - const { body } = await securitySolutionApi - .createRule({ - body: { - ...getCustomQueryRuleParams(), - max_signals: 0, - }, - }) - .expect(400); - - expect(body.message).toBe( - '[request body]: max_signals: Number must be greater than or equal to 1' - ); - }); - }); - describe('@skipInServerless missing timestamps', () => { beforeEach(async () => { await es.indices.delete({ index: 'myfakeindex-1', ignore_unavailable: true }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts index 7abca99e6e052..d706c1e8c093d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { @@ -15,6 +15,7 @@ import { removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, updateUsername, + getCustomQueryRuleParams, } from '../../../utils'; import { createAlertsIndex, @@ -56,7 +57,37 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should patch defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1' }), + }); + + const { body: patchedRuleResponse } = await securitySolutionApi + .patchRule({ + body: { + rule_id: 'rule-1', + max_signals: expectedRule.max_signals, + }, + }) + .expect(200); + + expect(patchedRuleResponse).toMatchObject(expectedRule); + + const { body: patchedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(patchedRule).toMatchObject(expectedRule); }); it('@skipInServerless should return a "403 forbidden" using a rule_id of type "machine learning"', async () => { @@ -67,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { .patchRule({ body: { rule_id: 'rule-1', type: 'machine_learning' } }) .expect(403); - expect(body).to.eql({ + expect(body).toEqual({ message: 'Your license does not support machine learning. Please upgrade your license.', status_code: 403, }); @@ -90,7 +121,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should patch a single rule property of name using the auto-generated id', async () => { @@ -107,7 +138,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should not change the revision of a rule when it patches only enabled', async () => { @@ -123,7 +154,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change the revision of a rule when it patches enabled and another property', async () => { @@ -141,7 +172,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should not change other properties when it does patches', async () => { @@ -167,7 +198,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should give a 404 if it is given a fake id', async () => { @@ -177,7 +208,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(404); - expect(body).to.eql({ + expect(body).toEqual({ status_code: 404, message: 'id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found', }); @@ -188,11 +219,36 @@ export default ({ getService }: FtrProviderContext) => { .patchRule({ body: { rule_id: 'fake_id', name: 'some other name' } }) .expect(404); - expect(body).to.eql({ + expect(body).toEqual({ status_code: 404, message: 'rule_id: "fake_id" not found', }); }); + + describe('max signals', () => { + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('does NOT patch a rule when max_signals is less than 1', async () => { + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1', max_signals: 100 }), + }); + + const { body } = await securitySolutionApi + .patchRule({ + body: { + rule_id: 'rule-1', + max_signals: 0, + }, + }) + .expect(400); + + expect(body.message).toEqual( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts index bb86ae5d17354..33247a63719e6 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/basic_license_essentials_tier/patch_rules_bulk.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { @@ -15,6 +15,7 @@ import { getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, updateUsername, + getCustomQueryRuleParams, } from '../../../utils'; import { createAlertsIndex, @@ -55,7 +56,39 @@ export default ({ getService }: FtrProviderContext) => { outputRule.revision = 1; const bodyToCompare = removeServerGeneratedProperties(body[0]); const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should patch defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1' }), + }); + + const { body: patchedRulesBulkResponse } = await securitySolutionApi + .bulkPatchRules({ + body: [ + { + rule_id: 'rule-1', + max_signals: expectedRule.max_signals, + }, + ], + }) + .expect(200); + + expect(patchedRulesBulkResponse[0]).toMatchObject(expectedRule); + + const { body: patchedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(patchedRule).toMatchObject(expectedRule); }); it('should patch two rule properties of name using the two rules rule_id', async () => { @@ -84,8 +117,8 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompare1 = removeServerGeneratedProperties(body[0]); const bodyToCompare2 = removeServerGeneratedProperties(body[1]); - expect(bodyToCompare1).to.eql(expectedRule1); - expect(bodyToCompare2).to.eql(expectedRule2); + expect(bodyToCompare1).toEqual(expectedRule1); + expect(bodyToCompare2).toEqual(expectedRule2); }); it('should patch a single rule property of name using an id', async () => { @@ -101,7 +134,7 @@ export default ({ getService }: FtrProviderContext) => { outputRule.revision = 1; const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should patch two rule properties of name using the two rules id', async () => { @@ -130,8 +163,8 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); - expect(bodyToCompare1).to.eql(expectedRule); - expect(bodyToCompare2).to.eql(expectedRule2); + expect(bodyToCompare1).toEqual(expectedRule); + expect(bodyToCompare2).toEqual(expectedRule2); }); it('should patch a single rule property of name using the auto-generated id', async () => { @@ -148,7 +181,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should not change the revision of a rule when it patches only enabled', async () => { @@ -164,7 +197,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change the revision of a rule when it patches enabled and another property', async () => { @@ -182,7 +215,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should not change other properties when it does patches', async () => { @@ -208,7 +241,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { @@ -218,7 +251,7 @@ export default ({ getService }: FtrProviderContext) => { }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { id: '5096dec6-b6b9-4d8d-8f93-6c2602079d9d', error: { @@ -234,7 +267,7 @@ export default ({ getService }: FtrProviderContext) => { .bulkPatchRules({ body: [{ rule_id: 'fake_id', name: 'some other name' }] }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { rule_id: 'fake_id', error: { status_code: 404, message: 'rule_id: "fake_id" not found' }, @@ -261,7 +294,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + expect([bodyToCompare, body[1]]).toEqual([ expectedRule, { error: { @@ -292,7 +325,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + expect([bodyToCompare, body[1]]).toEqual([ expectedRule, { error: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts index 95dbb33f3310a..8256b7734463f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_patch/trial_license_complete_tier/patch_rules.ts @@ -41,7 +41,6 @@ import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); - const securitySolutionApi = getService('securitySolutionApi'); const log = getService('log'); const es = getService('es'); // TODO: add a new service for pulling kibana username, similar to getService('es') @@ -658,54 +657,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('max signals', () => { - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('should overwrite max_signals field on patch', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const rulePatch = { - rule_id: 'rule-1', - max_signals: 200, - }; - - const { body } = await securitySolutionApi - .patchRule({ - body: rulePatch, - }) - .expect(200); - - expect(body.max_signals).to.eql(200); - }); - - it('does NOT patch a rule when max_signals is less than 1', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const rulePatch = { - rule_id: 'rule-1', - max_signals: 0, - }; - - const { body } = await securitySolutionApi - .patchRule({ - body: rulePatch, - }) - .expect(400); - - expect(body.message).to.be( - '[request body]: max_signals: Number must be greater than or equal to 1' - ); - }); - }); - describe('setup guide', () => { beforeEach(async () => { await createAlertsIndex(supertest, log); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts index 301b4413805a9..6b9dd4079b8a5 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { @@ -17,6 +17,7 @@ import { getSimpleMlRuleUpdate, getSimpleRule, updateUsername, + getCustomQueryRuleParams, } from '../../../utils'; import { createAlertsIndex, @@ -61,7 +62,34 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should update a rule with defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1' }), + }); + + const { body: updatedRuleResponse } = await securitySolutionApi + .updateRule({ + body: expectedRule, + }) + .expect(200); + + expect(updatedRuleResponse).toMatchObject(expectedRule); + + const { body: updatedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(updatedRule).toMatchObject(expectedRule); }); it('@skipInServerless should return a 403 forbidden if it is a machine learning job', async () => { @@ -75,7 +103,7 @@ export default ({ getService }: FtrProviderContext) => { const { body } = await securitySolutionApi.updateRule({ body: updatedRule }).expect(403); - expect(body).to.eql({ + expect(body).toEqual({ message: 'Your license does not support machine learning. Please upgrade your license.', status_code: 403, }); @@ -100,7 +128,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should update a single rule property of name using the auto-generated id', async () => { @@ -120,7 +148,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change the revision of a rule when it updates enabled and another property', async () => { @@ -140,7 +168,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { @@ -165,7 +193,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should give a 404 if it is given a fake id', async () => { @@ -175,7 +203,7 @@ export default ({ getService }: FtrProviderContext) => { const { body } = await securitySolutionApi.updateRule({ body: simpleRule }).expect(404); - expect(body).to.eql({ + expect(body).toEqual({ status_code: 404, message: 'id: "5096dec6-b6b9-4d8d-8f93-6c2602079d9d" not found', }); @@ -188,11 +216,58 @@ export default ({ getService }: FtrProviderContext) => { const { body } = await securitySolutionApi.updateRule({ body: simpleRule }).expect(404); - expect(body).to.eql({ + expect(body).toEqual({ status_code: 404, message: 'rule_id: "fake_id" not found', }); }); + + describe('max signals', () => { + afterEach(async () => { + await deleteAllRules(supertest, log); + }); + + it('should reset max_signals field to default value on update when not present', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 100, + }); + + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1', max_signals: 200 }), + }); + + const { body: updatedRuleResponse } = await securitySolutionApi + .updateRule({ + body: getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: undefined, + }), + }) + .expect(200); + + expect(updatedRuleResponse).toMatchObject(expectedRule); + }); + + it('does NOT update a rule when max_signals is less than 1', async () => { + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1', max_signals: 100 }), + }); + + const { body } = await securitySolutionApi + .updateRule({ + body: getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 0, + }), + }) + .expect(400); + + expect(body.message).toEqual( + '[request body]: max_signals: Number must be greater than or equal to 1' + ); + }); + }); }); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts index 4696a5d82444c..9a2ae3d59cced 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/basic_license_essentials_tier/update_rules_bulk.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; import { @@ -16,6 +16,7 @@ import { getSimpleRuleUpdate, getSimpleRule, updateUsername, + getCustomQueryRuleParams, } from '../../../utils'; import { createAlertsIndex, @@ -60,7 +61,34 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); + }); + + it('should update a rule with defaultable fields', async () => { + const expectedRule = getCustomQueryRuleParams({ + rule_id: 'rule-1', + max_signals: 200, + }); + + await securitySolutionApi.createRule({ + body: getCustomQueryRuleParams({ rule_id: 'rule-1' }), + }); + + const { body: updatedRulesBulkResponse } = await securitySolutionApi + .bulkUpdateRules({ + body: [expectedRule], + }) + .expect(200); + + expect(updatedRulesBulkResponse[0]).toMatchObject(expectedRule); + + const { body: updatedRule } = await securitySolutionApi + .readRule({ + query: { rule_id: 'rule-1' }, + }) + .expect(200); + + expect(updatedRule).toMatchObject(expectedRule); }); it('should update two rule properties of name using the two rules rule_id', async () => { @@ -92,8 +120,8 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompare1 = removeServerGeneratedProperties(body[0]); const bodyToCompare2 = removeServerGeneratedProperties(body[1]); - expect(bodyToCompare1).to.eql(expectedRule); - expect(bodyToCompare2).to.eql(expectedRule2); + expect(bodyToCompare1).toEqual(expectedRule); + expect(bodyToCompare2).toEqual(expectedRule2); }); it('should update a single rule property of name using an id', async () => { @@ -115,7 +143,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should update two rule properties of name using the two rules id', async () => { @@ -149,8 +177,8 @@ export default ({ getService }: FtrProviderContext) => { const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); - expect(bodyToCompare1).to.eql(expectedRule); - expect(bodyToCompare2).to.eql(expectedRule2); + expect(bodyToCompare1).toEqual(expectedRule); + expect(bodyToCompare2).toEqual(expectedRule2); }); it('should update a single rule property of name using the auto-generated id', async () => { @@ -172,7 +200,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change the revision of a rule when it updates enabled and another property', async () => { @@ -194,7 +222,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should change other properties when it does updates and effectively delete them such as timeline_title', async () => { @@ -221,7 +249,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect(bodyToCompare).to.eql(expectedRule); + expect(bodyToCompare).toEqual(expectedRule); }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { @@ -233,7 +261,7 @@ export default ({ getService }: FtrProviderContext) => { .bulkUpdateRules({ body: [ruleUpdate] }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { id: '1fd52120-d3a9-4e7a-b23c-96c0e1a74ae5', error: { @@ -253,7 +281,7 @@ export default ({ getService }: FtrProviderContext) => { .bulkUpdateRules({ body: [ruleUpdate] }) .expect(200); - expect(body).to.eql([ + expect(body).toEqual([ { rule_id: 'fake_id', error: { status_code: 404, message: 'rule_id: "fake_id" not found' }, @@ -283,7 +311,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + expect([bodyToCompare, body[1]]).toEqual([ expectedRule, { error: { @@ -319,7 +347,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRule = updateUsername(outputRule, ELASTICSEARCH_USERNAME); const bodyToCompare = removeServerGeneratedProperties(body[0]); - expect([bodyToCompare, body[1]]).to.eql([ + expect([bodyToCompare, body[1]]).toEqual([ expectedRule, { error: { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts index 697b5a43806ca..d905c57aa4a2f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_update/trial_license_complete_tier/update_rules.ts @@ -758,62 +758,6 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('max signals', () => { - afterEach(async () => { - await deleteAllRules(supertest, log); - }); - - it('should overwrite max_signals field on update', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const ruleUpdate = { - ...getSimpleRuleUpdate('rule-1'), - max_signals: 200, - }; - - const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(200); - - expect(body.max_signals).to.eql(200); - }); - - it('should reset max_signals field to default value on update when not present', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 200, - }); - - const ruleUpdate = { - ...getSimpleRuleUpdate('rule-1'), - max_signals: undefined, - }; - - const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(200); - - expect(body.max_signals).to.eql(100); - }); - - it('does NOT update a rule when max_signals is less than 1', async () => { - await createRule(supertest, log, { - ...getSimpleRule('rule-1'), - max_signals: 100, - }); - - const ruleUpdate = { - ...getSimpleRuleUpdate('rule-1'), - max_signals: 0, - }; - - const { body } = await securitySolutionApi.updateRule({ body: ruleUpdate }).expect(400); - - expect(body.message).to.be( - '[request body]: max_signals: Number must be greater than or equal to 1' - ); - }); - }); - describe('setup guide', () => { it('should overwrite setup value on update', async () => { await createRule(supertest, log, { From 54b8041770a449647100b1602ca495cc51c26196 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 25 Apr 2024 21:43:52 -0400 Subject: [PATCH 29/36] fixes spelling mistakes --- .../trial_license_complete_tier/perform_bulk_action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts index 79413e64e86aa..634fd8c02ca08 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts @@ -136,7 +136,7 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should export rules with defaultbale fields when values are set', async () => { + it('should export rules with defaultable fields when values are set', async () => { const defaultableFields: BaseDefaultableFields = { max_signals: 100, }; From ad64d19f209fdf568c4fd5a10b155735b589b6c8 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 26 Apr 2024 17:44:03 -0400 Subject: [PATCH 30/36] addresses response ops changes --- .../components/max_signals/translations.ts | 2 +- .../rule_creation_ui/pages/rule_creation/helpers.ts | 2 +- .../lib/detection_engine/rule_types/utils/utils.ts | 2 +- .../triggers_actions_ui/server/routes/config.test.ts | 3 +-- .../triggers_actions_ui/server/routes/config.ts | 10 +++++++++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts index 700419aa380cd..b69c0557a051f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/translations.ts @@ -20,7 +20,7 @@ export const LESS_THAN_WARNING = (maxNumber: number) => { values: { maxNumber }, defaultMessage: - 'Kibana alerting only allows a maximum of {maxNumber} {maxNumber, plural, =1 {alert} other {alerts}} per rule run.', + 'Kibana only allows a maximum of {maxNumber} {maxNumber, plural, =1 {alert} other {alerts}} per rule run.', } ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 6399850524409..cefb58c10697e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -613,7 +613,7 @@ export const formatAboutStepData = ( timestamp_override: timestampOverride !== '' ? timestampOverride : undefined, timestamp_override_fallback_disabled: timestampOverrideFallbackDisabled, ...(!isEmpty(note) ? { note } : {}), - max_signals: maxSignals, + max_signals: maxSignals ? maxSignals : undefined, ...rest, }; return resp; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index 99b5c2caafe28..abb54d8fa6770 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -442,7 +442,7 @@ export const getRuleRangeTuples = async ({ let maxSignalsToUse = maxSignals; if (maxSignals > maxAlertsAllowed) { maxSignalsToUse = maxAlertsAllowed; - warningStatusMessage = `The rule's max_signals value (${maxSignals}) is greater than the Kibana alerting limit (${maxAlertsAllowed}). The rule will only write a maximum of ${maxAlertsAllowed} per rule run.`; + warningStatusMessage = `The rule's max alerts per run setting (${maxSignals}) is greater than the Kibana alerting limit (${maxAlertsAllowed}). The rule will only write a maximum of ${maxAlertsAllowed} alerts per rule run.`; await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatusEnum['partial failure'], message: warningStatusMessage, diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts index a4966fa05e6b4..70a2041e529be 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.test.ts @@ -40,7 +40,7 @@ const ruleTypes = [ ]; describe('createConfigRoute', () => { - it('registers the route and returns config if user is authorized', async () => { + it('registers the route and returns exposed config values if user is authorized', async () => { const router = httpServiceMock.createRouter(); const logger = loggingSystemMock.create().get(); const mockRulesClient = rulesClientMock.create(); @@ -71,7 +71,6 @@ describe('createConfigRoute', () => { isUsingSecurity: true, maxScheduledPerMinute: 10000, minimumScheduleInterval: { value: '1m', enforce: false }, - run: { alerts: { max: 1000 }, actions: { max: 100000 } }, }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/server/routes/config.ts b/x-pack/plugins/triggers_actions_ui/server/routes/config.ts index 1cc328c0ae5ad..70929263e5f53 100644 --- a/x-pack/plugins/triggers_actions_ui/server/routes/config.ts +++ b/x-pack/plugins/triggers_actions_ui/server/routes/config.ts @@ -50,8 +50,16 @@ export function createConfigRoute({ // Check that user has access to at least one rule type const rulesClient = getRulesClientWithRequest(req); const ruleTypes = Array.from(await rulesClient.listRuleTypes()); + const { minimumScheduleInterval, maxScheduledPerMinute, isUsingSecurity } = alertingConfig(); // Only returns exposed config values + if (ruleTypes.length > 0) { - return res.ok({ body: alertingConfig() }); + return res.ok({ + body: { + minimumScheduleInterval, + maxScheduledPerMinute, + isUsingSecurity, + }, + }); } else { return res.forbidden({ body: { message: `Unauthorized to access config` }, From 1cb02a24b0f72b32114b04e3564ee00207e776f4 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 26 Apr 2024 22:11:04 -0400 Subject: [PATCH 31/36] fixes test --- .../server/lib/detection_engine/rule_types/utils/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index 146a5cd69047c..710b75d51d713 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -572,7 +572,7 @@ describe('utils', () => { expect(tuples.length).toEqual(1); expect(wroteWarningStatus).toEqual(true); expect(warningStatusMessage).toEqual( - "The rule's max_signals value (20) is greater than the Kibana alerting limit (10). The rule will only write a maximum of 10 per rule run." + "The rule's max alerts per run setting (20) is greater than the Kibana alerting limit (10). The rule will only write a maximum of 10 alerts per rule run." ); }); }); From bcca9018a5b4c62300ceeb84a5927d7c90c1c25b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 30 Apr 2024 18:45:10 -0400 Subject: [PATCH 32/36] addresses comments --- x-pack/plugins/alerting/public/plugin.ts | 4 ++-- .../model/rule_schema/rule_schemas.schema.yaml | 1 - .../components/description_step/index.test.tsx | 12 ++++++++++++ .../components/description_step/index.tsx | 4 ++-- .../components/max_signals/index.tsx | 4 ++-- .../components/step_about_rule/default_value.ts | 3 ++- .../components/step_about_rule/index.tsx | 6 +++++- .../detections/pages/detection_engine/rules/utils.ts | 3 +-- 8 files changed, 26 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index ab91aa0473d14..71bae6b28c94c 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -57,7 +57,7 @@ export interface PluginSetupContract { } export interface PluginStartContract { getNavigation: (ruleId: Rule['id']) => Promise; - getMaxAlertsPerRun: () => number | undefined; + getMaxAlertsPerRun: () => number; } export interface AlertingPluginSetup { management: ManagementSetup; @@ -86,7 +86,7 @@ export class AlertingPublicPlugin { private alertNavigationRegistry?: AlertNavigationRegistry; private config: AlertingUIConfig; - readonly maxAlertsPerRun?: number; + readonly maxAlertsPerRun: number; constructor(private readonly initContext: PluginInitializerContext) { this.config = this.initContext.config.get(); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml index b1ce330377c95..b09abd7f66d5f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml @@ -123,7 +123,6 @@ components: references: $ref: './common_attributes.schema.yaml#/components/schemas/RuleReferenceArray' - # maxSignals not used in ML rules but probably should be used max_signals: $ref: './common_attributes.schema.yaml#/components/schemas/MaxSignals' threat: diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index b3140374c6e98..7950493a1b989 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -782,6 +782,18 @@ describe('description_step', () => { expect(result[0].title).toEqual('Max alerts per run'); expect(result[0].description).toEqual(100); }); + + test('returns empty array when "value" is a undefined', () => { + const result: ListItems[] = getDescriptionItem( + 'maxSignals', + 'Max alerts per run', + { ...mockAboutStep, maxSignals: undefined }, + mockFilterManager, + mockLicenseService + ); + + expect(result.length).toEqual(0); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx index f69c358706b70..7e00e90f391b0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx @@ -343,8 +343,8 @@ export const getDescriptionItem = ( ? [{ title: i18n.BUILDING_BLOCK_LABEL, description: i18n.BUILDING_BLOCK_DESCRIPTION }] : []; } else if (field === 'maxSignals') { - const value: number = get(field, data); - return [{ title: label, description: value }]; + const value: number | undefined = get(field, data); + return value ? [{ title: label, description: value }] : []; } const description: string = get(field, data); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx index d266f38f345af..b7d717b581802 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/max_signals/index.tsx @@ -16,7 +16,7 @@ import { useKibana } from '../../../../common/lib/kibana'; interface MaxSignalsFieldProps { dataTestSubj: string; - field: FieldHook; + field: FieldHook; idAria: string; isDisabled: boolean; placeholder?: string; @@ -33,7 +33,7 @@ export const MaxSignals: React.FC = ({ }): JSX.Element => { const { setValue, value } = field; const { alerting } = useKibana().services; - const maxAlertsPerRun = alerting.getMaxAlertsPerRun() ?? 1000; // Defaults to 1000 in the alerting framework config + const maxAlertsPerRun = alerting.getMaxAlertsPerRun(); const [isInvalid, error] = useMemo(() => { if (typeof value === 'number' && !isNaN(value) && value <= 0) { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts index 3497a87115ce4..9d1fff5b8a9ef 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/default_value.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; import type { AboutStepRule } from '../../../../detections/pages/detection_engine/rules/types'; import { fillEmptySeverityMappings } from '../../../../detections/pages/detection_engine/rules/helpers'; @@ -33,6 +34,6 @@ export const stepAboutDefaultValue: AboutStepRule = { timestampOverride: '', threat: threatDefault, note: '', - maxSignals: 100, + maxSignals: DEFAULT_MAX_SIGNALS, setup: '', }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index f27a07009fa2e..3fa1852e6aa05 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -34,7 +34,10 @@ import { SeverityField } from '../severity_mapping'; import { RiskScoreField } from '../risk_score_mapping'; import { AutocompleteField } from '../autocomplete_field'; import { useFetchIndex } from '../../../../common/containers/source'; -import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; +import { + DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, +} from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; import { useRuleIndices } from '../../../rule_management/logic/use_rule_indices'; import { EsqlAutocomplete } from '../esql_autocomplete'; @@ -336,6 +339,7 @@ const StepAboutRuleComponent: FC = ({ idAria: 'detectionEngineStepAboutRuleMaxSignals', dataTestSubj: 'detectionEngineStepAboutRuleMaxSignals', isDisabled: isLoading, + placeholder: DEFAULT_MAX_SIGNALS, }} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 4f40eee5d6978..565180217f842 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -8,7 +8,7 @@ import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; -import { DEFAULT_MAX_SIGNALS, DEFAULT_THREAT_MATCH_QUERY } from '../../../../../common/constants'; +import { DEFAULT_THREAT_MATCH_QUERY } from '../../../../../common/constants'; import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; import type { AboutStepRule, DefineStepRule, RuleStepsOrder, ScheduleStepRule } from './types'; import { DataSourceType, GroupByOptions, RuleStep } from './types'; @@ -96,7 +96,6 @@ export const stepAboutDefaultValue: AboutStepRule = { setup: '', threatIndicatorPath: undefined, timestampOverrideFallbackDisabled: undefined, - maxSignals: DEFAULT_MAX_SIGNALS, }; const DEFAULT_INTERVAL = '5m'; From ce2f06ebc39db4e8401aacce38c20f3d7ab0807f Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Tue, 30 Apr 2024 18:52:38 -0400 Subject: [PATCH 33/36] adds test --- .../rule_types/utils/utils.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index 710b75d51d713..afa9583fef41e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -575,6 +575,24 @@ describe('utils', () => { "The rule's max alerts per run setting (20) is greater than the Kibana alerting limit (10). The rule will only write a maximum of 10 alerts per rule run." ); }); + + test('should use maxSignals value if maxSignals is less than alerting framework limit', async () => { + const { tuples, wroteWarningStatus, warningStatusMessage } = await getRuleRangeTuples({ + previousStartedAt: moment().subtract(30, 's').toDate(), + startedAt: moment().subtract(30, 's').toDate(), + interval: '30s', + from: 'now-30s', + to: 'now', + maxSignals: 20, + ruleExecutionLogger, + alerting, + }); + const someTuple = tuples[0]; + expect(someTuple.maxSignals).toEqual(20); + expect(tuples.length).toEqual(1); + expect(wroteWarningStatus).toEqual(false); + expect(warningStatusMessage).toEqual(undefined); + }); }); describe('getMaxCatchupRatio', () => { From 0b71b7727120bf019ddc393bc165f00a3d6ff315 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Fri, 3 May 2024 16:34:30 +0200 Subject: [PATCH 34/36] Fix handling of 0 in form --- .../rule_creation_ui/pages/rule_creation/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 41ff5305b7dac..3054a894d0df1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -614,7 +614,7 @@ export const formatAboutStepData = ( timestamp_override: timestampOverride !== '' ? timestampOverride : undefined, timestamp_override_fallback_disabled: timestampOverrideFallbackDisabled, ...(!isEmpty(note) ? { note } : {}), - max_signals: maxSignals ? maxSignals : undefined, + max_signals: Number.isSafeInteger(maxSignals) ? maxSignals : undefined, ...rest, }; return resp; From 07d81e6d3143c4a40c4ee605f2e8805a9cc2ebfb Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Fri, 3 May 2024 16:50:47 +0200 Subject: [PATCH 35/36] Added tests for form validation --- .../pages/rule_creation/helpers.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index af655519e8a6b..c092b884bca36 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -698,6 +698,32 @@ describe('helpers', () => { expect(result).toEqual(expected); }); + + // Users are allowed to input 0 in the form, but value is validated in the API layer + test('returns formatted object with max_signals set to 0', () => { + const mockDataWithZeroMaxSignals: AboutStepRule = { + ...mockData, + maxSignals: 0, + }; + + const result = formatAboutStepData(mockDataWithZeroMaxSignals); + + expect(result.max_signals).toEqual(0); + }); + + // Strings or empty values are replaced with undefined and overriden with the default value of 1000 + test('returns formatted object with undefined max_signals for non-integer values inputs', () => { + const mockDataWithNonIntegerMaxSignals: AboutStepRule = { + ...mockData, + // @ts-expect-error + maxSignals: '', + }; + + const result = formatAboutStepData(mockDataWithNonIntegerMaxSignals); + + expect(result.max_signals).toEqual(undefined); + }); + test('returns formatted object with endpoint exceptions_list', () => { const result = formatAboutStepData( { From c850892e9756b10087bfde62769b2af1612569f5 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Fri, 3 May 2024 16:51:59 +0200 Subject: [PATCH 36/36] Remove empty line --- .../rule_creation_ui/pages/rule_creation/helpers.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index c092b884bca36..20a432cdc1420 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -698,7 +698,6 @@ describe('helpers', () => { expect(result).toEqual(expected); }); - // Users are allowed to input 0 in the form, but value is validated in the API layer test('returns formatted object with max_signals set to 0', () => { const mockDataWithZeroMaxSignals: AboutStepRule = {