[EDR Workflows] Automated Actions in more rule types (#191874)

This commit is contained in:
Tomasz Ciecierski 2024-09-18 18:56:06 +02:00 committed by GitHub
parent 70b7d26335
commit 004631b6c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 737 additions and 85 deletions

View file

@ -6135,6 +6135,175 @@ Object {
"query": Object {
"type": "string",
},
"responseActions": Object {
"items": Object {
"anyOf": Array [
Object {
"additionalProperties": false,
"properties": Object {
"actionTypeId": Object {
"const": ".osquery",
"type": "string",
},
"params": Object {
"additionalProperties": false,
"properties": Object {
"ecsMapping": Object {
"additionalProperties": Object {
"additionalProperties": false,
"properties": Object {
"field": Object {
"type": "string",
},
"value": Object {
"anyOf": Array [
Object {
"type": "string",
},
Object {
"items": Object {
"type": "string",
},
"type": "array",
},
],
},
},
"type": "object",
},
"properties": Object {},
"type": "object",
},
"packId": Object {
"type": "string",
},
"queries": Object {
"items": Object {
"additionalProperties": false,
"properties": Object {
"ecs_mapping": Object {
"$ref": "#/allOf/1/properties/responseActions/items/anyOf/0/properties/params/properties/ecsMapping",
},
"id": Object {
"type": "string",
},
"platform": Object {
"type": "string",
},
"query": Object {
"type": "string",
},
"removed": Object {
"type": "boolean",
},
"snapshot": Object {
"type": "boolean",
},
"version": Object {
"type": "string",
},
},
"required": Array [
"id",
"query",
],
"type": "object",
},
"type": "array",
},
"query": Object {
"type": "string",
},
"savedQueryId": Object {
"type": "string",
},
"timeout": Object {
"type": "number",
},
},
"type": "object",
},
},
"required": Array [
"actionTypeId",
"params",
],
"type": "object",
},
Object {
"additionalProperties": false,
"properties": Object {
"actionTypeId": Object {
"const": ".endpoint",
"type": "string",
},
"params": Object {
"anyOf": Array [
Object {
"additionalProperties": false,
"properties": Object {
"command": Object {
"const": "isolate",
"type": "string",
},
"comment": Object {
"type": "string",
},
},
"required": Array [
"command",
],
"type": "object",
},
Object {
"additionalProperties": false,
"properties": Object {
"command": Object {
"enum": Array [
"kill-process",
"suspend-process",
],
"type": "string",
},
"comment": Object {
"type": "string",
},
"config": Object {
"additionalProperties": false,
"properties": Object {
"field": Object {
"type": "string",
},
"overwrite": Object {
"default": true,
"type": "boolean",
},
},
"required": Array [
"field",
],
"type": "object",
},
},
"required": Array [
"command",
"config",
],
"type": "object",
},
],
},
},
"required": Array [
"actionTypeId",
"params",
],
"type": "object",
},
],
},
"type": "array",
},
"tiebreakerField": Object {
"type": "string",
},
@ -7687,6 +7856,175 @@ Object {
"query": Object {
"type": "string",
},
"responseActions": Object {
"items": Object {
"anyOf": Array [
Object {
"additionalProperties": false,
"properties": Object {
"actionTypeId": Object {
"const": ".osquery",
"type": "string",
},
"params": Object {
"additionalProperties": false,
"properties": Object {
"ecsMapping": Object {
"additionalProperties": Object {
"additionalProperties": false,
"properties": Object {
"field": Object {
"type": "string",
},
"value": Object {
"anyOf": Array [
Object {
"type": "string",
},
Object {
"items": Object {
"type": "string",
},
"type": "array",
},
],
},
},
"type": "object",
},
"properties": Object {},
"type": "object",
},
"packId": Object {
"type": "string",
},
"queries": Object {
"items": Object {
"additionalProperties": false,
"properties": Object {
"ecs_mapping": Object {
"$ref": "#/allOf/1/properties/responseActions/items/anyOf/0/properties/params/properties/ecsMapping",
},
"id": Object {
"type": "string",
},
"platform": Object {
"type": "string",
},
"query": Object {
"type": "string",
},
"removed": Object {
"type": "boolean",
},
"snapshot": Object {
"type": "boolean",
},
"version": Object {
"type": "string",
},
},
"required": Array [
"id",
"query",
],
"type": "object",
},
"type": "array",
},
"query": Object {
"type": "string",
},
"savedQueryId": Object {
"type": "string",
},
"timeout": Object {
"type": "number",
},
},
"type": "object",
},
},
"required": Array [
"actionTypeId",
"params",
],
"type": "object",
},
Object {
"additionalProperties": false,
"properties": Object {
"actionTypeId": Object {
"const": ".endpoint",
"type": "string",
},
"params": Object {
"anyOf": Array [
Object {
"additionalProperties": false,
"properties": Object {
"command": Object {
"const": "isolate",
"type": "string",
},
"comment": Object {
"type": "string",
},
},
"required": Array [
"command",
],
"type": "object",
},
Object {
"additionalProperties": false,
"properties": Object {
"command": Object {
"enum": Array [
"kill-process",
"suspend-process",
],
"type": "string",
},
"comment": Object {
"type": "string",
},
"config": Object {
"additionalProperties": false,
"properties": Object {
"field": Object {
"type": "string",
},
"overwrite": Object {
"default": true,
"type": "boolean",
},
},
"required": Array [
"field",
],
"type": "object",
},
},
"required": Array [
"command",
"config",
],
"type": "object",
},
],
},
},
"required": Array [
"actionTypeId",
"params",
],
"type": "object",
},
],
},
"type": "array",
},
"type": Object {
"const": "new_terms",
"type": "string",

View file

@ -224,6 +224,7 @@ export const EqlOptionalFields = z.object({
tiebreaker_field: TiebreakerField.optional(),
timestamp_field: TimestampField.optional(),
alert_suppression: AlertSuppression.optional(),
response_actions: z.array(ResponseAction).optional(),
});
export type EqlRuleCreateFields = z.infer<typeof EqlRuleCreateFields>;
@ -521,6 +522,7 @@ export const NewTermsRuleOptionalFields = z.object({
data_view_id: DataViewId.optional(),
filters: RuleFilterArray.optional(),
alert_suppression: AlertSuppression.optional(),
response_actions: z.array(ResponseAction).optional(),
});
export type NewTermsRuleDefaultableFields = z.infer<typeof NewTermsRuleDefaultableFields>;
@ -574,6 +576,7 @@ export const EsqlRuleRequiredFields = z.object({
export type EsqlRuleOptionalFields = z.infer<typeof EsqlRuleOptionalFields>;
export const EsqlRuleOptionalFields = z.object({
alert_suppression: AlertSuppression.optional(),
response_actions: z.array(ResponseAction).optional(),
});
export type EsqlRulePatchFields = z.infer<typeof EsqlRulePatchFields>;

View file

@ -292,6 +292,10 @@ components:
$ref: './specific_attributes/eql_attributes.schema.yaml#/components/schemas/TimestampField'
alert_suppression:
$ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression'
response_actions:
type: array
items:
$ref: '../rule_response_actions/response_actions.schema.yaml#/components/schemas/ResponseAction'
EqlRuleCreateFields:
allOf:
@ -762,6 +766,10 @@ components:
$ref: './common_attributes.schema.yaml#/components/schemas/RuleFilterArray'
alert_suppression:
$ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression'
response_actions:
type: array
items:
$ref: '../rule_response_actions/response_actions.schema.yaml#/components/schemas/ResponseAction'
NewTermsRuleDefaultableFields:
type: object
@ -840,6 +848,10 @@ components:
properties:
alert_suppression:
$ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression'
response_actions:
type: array
items:
$ref: '../rule_response_actions/response_actions.schema.yaml#/components/schemas/ResponseAction'
EsqlRulePatchFields:
allOf:

View file

@ -93,3 +93,14 @@ export const isSuppressionRuleConfiguredWithMissingFields = (ruleType: Type) =>
export const isSuppressionRuleInGA = (ruleType: Type): boolean => {
return isSuppressibleAlertRule(ruleType) && SUPPRESSIBLE_ALERT_RULES_GA.includes(ruleType);
};
export const shouldShowResponseActions = (
ruleType: Type | undefined,
automatedResponseActionsForMoreRulesEnabled: boolean
) => {
return (
isQueryRule(ruleType) ||
(automatedResponseActionsForMoreRulesEnabled &&
(isEsqlRule(ruleType) || isEqlRule(ruleType) || isNewTermsRule(ruleType)))
);
};

View file

@ -52,6 +52,11 @@ export const allowedExperimentalValues = Object.freeze({
*/
automatedProcessActionsEnabled: true,
/**
* Temporary feature flag to enable the Response Actions in Rules UI - intermediate release
*/
automatedResponseActionsForMoreRulesEnabled: false,
/**
* Enables the ability to send Response actions to SentinelOne and persist the results
* in ES. Adds API changes to support `agentType` and supports `isolate` and `release`

View file

@ -2042,6 +2042,10 @@ components:
$ref: '#/components/schemas/RuleFilterArray'
index:
$ref: '#/components/schemas/IndexPatternArray'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
tiebreaker_field:
$ref: '#/components/schemas/TiebreakerField'
timestamp_field:
@ -2729,6 +2733,10 @@ components:
properties:
alert_suppression:
$ref: '#/components/schemas/AlertSuppression'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
EsqlRulePatchProps:
allOf:
- type: object
@ -3873,6 +3881,10 @@ components:
$ref: '#/components/schemas/RuleFilterArray'
index:
$ref: '#/components/schemas/IndexPatternArray'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
NewTermsRulePatchFields:
allOf:
- type: object

View file

@ -1316,6 +1316,10 @@ components:
$ref: '#/components/schemas/RuleFilterArray'
index:
$ref: '#/components/schemas/IndexPatternArray'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
tiebreaker_field:
$ref: '#/components/schemas/TiebreakerField'
timestamp_field:
@ -2003,6 +2007,10 @@ components:
properties:
alert_suppression:
$ref: '#/components/schemas/AlertSuppression'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
EsqlRulePatchProps:
allOf:
- type: object
@ -3026,6 +3034,10 @@ components:
$ref: '#/components/schemas/RuleFilterArray'
index:
$ref: '#/components/schemas/IndexPatternArray'
response_actions:
items:
$ref: '#/components/schemas/ResponseAction'
type: array
NewTermsRulePatchFields:
allOf:
- type: object

View file

@ -16,8 +16,9 @@ import type {
} from '@kbn/triggers-actions-ui-plugin/public';
import { UseArray } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { shouldShowResponseActions } from '../../../../../common/detection_engine/utils';
import type { RuleObjectId } from '../../../../../common/api/detection_engine/model/rule_schema';
import { isQueryRule } from '../../../../../common/detection_engine/utils';
import { ResponseActionsForm } from '../../../rule_response_actions/response_actions_form';
import type {
RuleStepProps,
@ -84,6 +85,9 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
const {
services: { application },
} = useKibana();
const automatedResponseActionsForMoreRulesEnabled = useIsExperimentalFeatureEnabled(
'automatedResponseActionsForMoreRulesEnabled'
);
const displayActionsOptions = useMemo(
() => (
<>
@ -101,7 +105,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
[actionMessageParams, summaryActionMessageParams]
);
const displayResponseActionsOptions = useMemo(() => {
if (isQueryRule(ruleType)) {
if (shouldShowResponseActions(ruleType, automatedResponseActionsForMoreRulesEnabled)) {
return (
<UseArray path="responseActions" initialNumberOfItems={0}>
{ResponseActionsForm}
@ -109,7 +113,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
);
}
return null;
}, [ruleType]);
}, [ruleType, automatedResponseActionsForMoreRulesEnabled]);
// only display the actions dropdown if the user has "read" privileges for actions
const displayActionsDropDown = useMemo(() => {
return application.capabilities.actions.show ? (

View file

@ -12,6 +12,8 @@ import {
tryAddingDisabledResponseAction,
validateAvailableCommands,
visitRuleActions,
selectIsolateAndSaveWithoutEnabling,
fillUpNewEsqlRule,
} from '../../tasks/response_actions';
import { cleanupRule, generateRandomStringName, loadRule } from '../../tasks/api_fixtures';
import { ResponseActionTypesEnum } from '../../../../../common/api/detection_engine';
@ -28,6 +30,7 @@ describe(
kbnServerArgs: [
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
'automatedProcessActionsEnabled',
'automatedResponseActionsForMoreRulesEnabled',
])}`,
],
},
@ -202,6 +205,23 @@ describe(
});
});
describe('User should be able to add response action to ESQL rule', () => {
const [ruleName, ruleDescription] = generateRandomStringName(2);
beforeEach(() => {
login(ROLE.soc_manager);
});
it('create and save endpoint response action inside of a rule', () => {
const query = 'FROM * METADATA _index, _id';
fillUpNewEsqlRule(ruleName, ruleDescription, query);
addEndpointResponseAction();
focusAndOpenCommandDropdown();
validateAvailableCommands();
selectIsolateAndSaveWithoutEnabling(ruleName);
});
});
describe('User should not see endpoint action when no rbac', () => {
const [ruleName, ruleDescription] = generateRandomStringName(2);

View file

@ -42,6 +42,12 @@ export const validateAvailableCommands = () => {
cy.getByTestSubj(`command-type-${command}`);
});
};
export const selectIsolateAndSaveWithoutEnabling = (ruleName: string) => {
cy.getByTestSubj(`command-type-isolate`).click();
cy.getByTestSubj('create-enabled-false').click();
cy.contains(`${ruleName} was created`);
};
export const addEndpointResponseAction = () => {
cy.getByTestSubj('response-actions-wrapper').within(() => {
cy.getByTestSubj('Elastic Defend-response-action-type-selection-option').click();
@ -69,6 +75,26 @@ export const fillUpNewRule = (name = 'Test', description = 'Test') => {
cy.getByTestSubj('about-continue').click();
cy.getByTestSubj('schedule-continue').click();
};
export const fillUpNewEsqlRule = (name = 'Test', description = 'Test', query: string) => {
loadPage('app/security/rules/management');
cy.getByTestSubj('create-new-rule').click();
cy.getByTestSubj('stepDefineRule').within(() => {
cy.getByTestSubj('esqlRuleType').click();
cy.getByTestSubj('detectionEngineStepDefineRuleEsqlQueryBar').within(() => {
cy.getByTestSubj('globalQueryBar').click();
cy.getByTestSubj('kibanaCodeEditor').type(query);
});
});
cy.getByTestSubj('define-continue').click();
cy.getByTestSubj('detectionEngineStepAboutRuleName').within(() => {
cy.getByTestSubj('input').type(name);
});
cy.getByTestSubj('detectionEngineStepAboutRuleDescription').within(() => {
cy.getByTestSubj('input').type(description);
});
cy.getByTestSubj('about-continue').click();
cy.getByTestSubj('schedule-continue').click();
};
export const visitRuleActions = (ruleId: string) => {
loadPage(`app/security/rules/id/${ruleId}/edit`);
cy.getByTestSubj('edit-rule-actions-tab').should('exist');

View file

@ -51,6 +51,7 @@ describe('Prebuilt rule asset schema', () => {
// See: detection_engine/prebuilt_rules/model/rule_assets/prebuilt_rule_asset.ts
const omittedBaseFields = [
'actions',
'response_actions',
'throttle',
'meta',
'output_index',

View file

@ -63,14 +63,14 @@ const TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_SAVED_QUERY_RULES =
export type TypeSpecificFields = z.infer<typeof TypeSpecificFields>;
export const TypeSpecificFields = z.discriminatedUnion('type', [
EqlRuleCreateFields,
EqlRuleCreateFields.omit(TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_QUERY_RULES),
QueryRuleCreateFields.omit(TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_QUERY_RULES),
SavedQueryRuleCreateFields.omit(TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_SAVED_QUERY_RULES),
ThresholdRuleCreateFields,
ThreatMatchRuleCreateFields,
MachineLearningRuleCreateFields,
NewTermsRuleCreateFields,
EsqlRuleCreateFields,
NewTermsRuleCreateFields.omit(TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_QUERY_RULES),
EsqlRuleCreateFields.omit(TYPE_SPECIFIC_FIELDS_TO_OMIT_FROM_QUERY_RULES),
]);
// Make sure the type-specific fields contain all the same rule types as the type-specific rule params.

View file

@ -16,7 +16,12 @@ import {
} from '../../../../routes/__mocks__/request_responses';
import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__';
import { createRuleRoute } from './route';
import { getCreateRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks';
import {
getCreateEqlRuleSchemaMock,
getCreateEsqlRulesSchemaMock,
getCreateNewTermsRulesSchemaMock,
getCreateRulesSchemaMock,
} from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks';
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
import { getQueryRuleParams } from '../../../../rule_schema/mocks';
import { HttpAuthzError } from '../../../../../machine_learning/validation';
@ -181,20 +186,29 @@ describe('Create rule route', () => {
},
});
const defaultAction = getResponseAction();
const ruleTypes: Array<[string, () => object]> = [
['query', getCreateRulesSchemaMock],
['esql', getCreateEsqlRulesSchemaMock],
['eql', getCreateEqlRuleSchemaMock],
['new_terms', getCreateNewTermsRulesSchemaMock],
];
test('is successful', async () => {
const request = requestMock.create({
method: 'post',
path: DETECTION_ENGINE_RULES_URL,
body: {
...getCreateRulesSchemaMock(),
response_actions: [defaultAction],
},
});
test.each(ruleTypes)(
'is successful for %s rule',
async (ruleType: string, schemaMock: (ruleId: string) => object) => {
const request = requestMock.create({
method: 'post',
path: DETECTION_ENGINE_RULES_URL,
body: {
...schemaMock(`rule-${ruleType}`),
response_actions: [defaultAction],
},
});
const response = await server.inject(request, requestContextMock.convertContext(context));
expect(response.status).toEqual(200);
});
const response = await server.inject(request, requestContextMock.convertContext(context));
expect(response.status).toEqual(200);
}
);
test('fails when isolate rbac is set to false', async () => {
(context.securitySolution.getEndpointAuthz as jest.Mock).mockReturnValue(() => ({

View file

@ -17,6 +17,9 @@ import { getRulesSchemaMock } from '../../../../../../../common/api/detection_en
import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants';
import { updateRuleRoute } from './route';
import {
getCreateEqlRuleSchemaMock,
getCreateEsqlRulesSchemaMock,
getCreateNewTermsRulesSchemaMock,
getCreateRulesSchemaMock,
getUpdateRulesSchemaMock,
} from '../../../../../../../common/api/detection_engine/model/rule_schema/mocks';
@ -189,19 +192,29 @@ describe('Update rule route', () => {
});
const defaultAction = getResponseAction();
test('is successful', async () => {
const request = requestMock.create({
method: 'post',
path: DETECTION_ENGINE_RULES_URL,
body: {
...getCreateRulesSchemaMock(),
response_actions: [defaultAction],
},
});
const ruleTypes: Array<[string, () => object]> = [
['query', () => getCreateRulesSchemaMock()],
['esql', getCreateEsqlRulesSchemaMock],
['eql', getCreateEqlRuleSchemaMock],
['new_terms', getCreateNewTermsRulesSchemaMock],
];
const response = await server.inject(request, requestContextMock.convertContext(context));
expect(response.status).toEqual(200);
});
test.each(ruleTypes)(
'is successful for %s rule',
async (ruleType: string, schemaMock: (ruleId: string) => object) => {
const request = requestMock.create({
method: 'post',
path: DETECTION_ENGINE_RULES_URL,
body: {
...schemaMock(`rule-${ruleType}`),
response_actions: [defaultAction],
},
});
const response = await server.inject(request, requestContextMock.convertContext(context));
expect(response.status).toEqual(200);
}
);
test('fails when isolate rbac is set to false', async () => {
(context.securitySolution.getEndpointAuthz as jest.Mock).mockReturnValue(() => ({

View file

@ -119,6 +119,9 @@ const typeSpecificSnakeToCamel = (params: TypeSpecificCreateProps): TypeSpecific
eventCategoryOverride: params.event_category_override,
tiebreakerField: params.tiebreaker_field,
alertSuppression: convertObjectKeysToCamelCase(params.alert_suppression),
responseActions: params.response_actions?.map((rule) =>
transformRuleToAlertResponseAction(rule)
),
};
}
case 'esql': {
@ -127,6 +130,9 @@ const typeSpecificSnakeToCamel = (params: TypeSpecificCreateProps): TypeSpecific
language: params.language,
query: params.query,
alertSuppression: convertObjectKeysToCamelCase(params.alert_suppression),
responseActions: params.response_actions?.map((rule) =>
transformRuleToAlertResponseAction(rule)
),
};
}
case 'threat_match': {
@ -173,9 +179,6 @@ const typeSpecificSnakeToCamel = (params: TypeSpecificCreateProps): TypeSpecific
filters: params.filters,
savedId: params.saved_id,
dataViewId: params.data_view_id,
responseActions: params.response_actions?.map((rule) =>
transformRuleToAlertResponseAction(rule)
),
alertSuppression: convertObjectKeysToCamelCase(params.alert_suppression),
};
}
@ -213,6 +216,9 @@ const typeSpecificSnakeToCamel = (params: TypeSpecificCreateProps): TypeSpecific
language: params.language ?? 'kuery',
dataViewId: params.data_view_id,
alertSuppression: convertObjectKeysToCamelCase(params.alert_suppression),
responseActions: params.response_actions?.map((rule) =>
transformRuleToAlertResponseAction(rule)
),
};
}
default: {

View file

@ -6,8 +6,8 @@
*/
import type { RequiredOptional } from '@kbn/zod-helpers';
import type { TypeSpecificResponse } from '../../../../../../../common/api/detection_engine/model/rule_schema';
import { transformAlertToRuleResponseAction } from '../../../../../../../common/detection_engine/transform_actions';
import type { TypeSpecificResponse } from '../../../../../../../common/api/detection_engine/model/rule_schema';
import { assertUnreachable } from '../../../../../../../common/utility_types';
import { convertObjectKeysToSnakeCase } from '../../../../../../utils/object_case_converters';
import type { TypeSpecificRuleParams } from '../../../../rule_schema';
@ -28,6 +28,7 @@ export const typeSpecificCamelToSnake = (
event_category_override: params.eventCategoryOverride,
tiebreaker_field: params.tiebreakerField,
alert_suppression: convertObjectKeysToSnakeCase(params.alertSuppression),
response_actions: params.responseActions?.map(transformAlertToRuleResponseAction),
};
}
case 'esql': {
@ -36,6 +37,7 @@ export const typeSpecificCamelToSnake = (
language: params.language,
query: params.query,
alert_suppression: convertObjectKeysToSnakeCase(params.alertSuppression),
response_actions: params.responseActions?.map(transformAlertToRuleResponseAction),
};
}
case 'threat_match': {
@ -118,6 +120,7 @@ export const typeSpecificCamelToSnake = (
language: params.language,
data_view_id: params.dataViewId,
alert_suppression: convertObjectKeysToSnakeCase(params.alertSuppression),
response_actions: params.responseActions?.map(transformAlertToRuleResponseAction),
};
}
default: {

View file

@ -86,6 +86,7 @@ export const setTypeSpecificDefaults = (props: TypeSpecificCreateProps) => {
event_category_override: props.event_category_override,
tiebreaker_field: props.tiebreaker_field,
alert_suppression: props.alert_suppression,
response_actions: props.response_actions,
};
}
case 'esql': {
@ -94,6 +95,7 @@ export const setTypeSpecificDefaults = (props: TypeSpecificCreateProps) => {
language: props.language,
query: props.query,
alert_suppression: props.alert_suppression,
response_actions: props.response_actions,
};
}
case 'threat_match': {
@ -176,6 +178,7 @@ export const setTypeSpecificDefaults = (props: TypeSpecificCreateProps) => {
language: props.language ?? 'kuery',
data_view_id: props.data_view_id,
alert_suppression: props.alert_suppression,
response_actions: props.response_actions,
};
}
default: {

View file

@ -138,6 +138,7 @@ const patchEqlParams = (
rulePatch.event_category_override ?? existingRule.event_category_override,
tiebreaker_field: rulePatch.tiebreaker_field ?? existingRule.tiebreaker_field,
alert_suppression: rulePatch.alert_suppression ?? existingRule.alert_suppression,
response_actions: rulePatch.response_actions ?? existingRule.response_actions,
};
};
@ -150,6 +151,7 @@ const patchEsqlParams = (
language: rulePatch.language ?? existingRule.language,
query: rulePatch.query ?? existingRule.query,
alert_suppression: rulePatch.alert_suppression ?? existingRule.alert_suppression,
response_actions: rulePatch.response_actions ?? existingRule.response_actions,
};
};
@ -258,6 +260,7 @@ const patchNewTermsParams = (
new_terms_fields: params.new_terms_fields ?? existingRule.new_terms_fields,
history_window_start: params.history_window_start ?? existingRule.history_window_start,
alert_suppression: params.alert_suppression ?? existingRule.alert_suppression,
response_actions: params.response_actions ?? existingRule.response_actions,
};
};

View file

@ -9,8 +9,13 @@ 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';
import type {
EqlRule,
EsqlRule,
NewTermsRule,
QueryRule,
} from '../../../../../common/api/detection_engine';
import {
type QueryRule,
type ResponseAction,
type RuleCreateProps,
RuleResponse,
@ -21,9 +26,10 @@ import {
RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP,
RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ,
} from '../../../../../common/endpoint/service/response_actions/constants';
import { isQueryRule } from '../../../../../common/detection_engine/utils';
import { shouldShowResponseActions } from '../../../../../common/detection_engine/utils';
import type { SecuritySolutionApiRequestHandlerContext } from '../../../..';
import { CustomHttpRequestError } from '../../../../utils/custom_http_request_error';
import type { EqlRuleParams, EsqlRuleParams, NewTermsRuleParams } from '../../rule_schema';
import {
hasValidRuleType,
type RuleAlertType,
@ -64,11 +70,21 @@ export const validateResponseActionsPermissions = async (
ruleUpdate: RuleCreateProps | RuleUpdateProps,
existingRule?: RuleAlertType | null
): Promise<void> => {
if (!isQueryRule(ruleUpdate.type)) {
const { experimentalFeatures } = await securitySolution.getConfig();
if (
!shouldShowResponseActions(
ruleUpdate.type,
experimentalFeatures.automatedResponseActionsForMoreRulesEnabled
)
) {
return;
}
if (!isQueryRulePayload(ruleUpdate) || (existingRule && !isQueryRuleObject(existingRule))) {
if (
!rulePayloadContainsResponseActions(ruleUpdate) ||
(existingRule && !ruleObjectContainsResponseActions(existingRule))
) {
return;
}
@ -108,10 +124,14 @@ export const validateResponseActionsPermissions = async (
});
};
function isQueryRulePayload(rule: RuleCreateProps | RuleUpdateProps): rule is QueryRule {
function rulePayloadContainsResponseActions(
rule: RuleCreateProps | RuleUpdateProps
): rule is QueryRule | EsqlRule | EqlRule | NewTermsRule {
return 'response_actions' in rule;
}
function isQueryRuleObject(rule?: RuleAlertType): rule is Rule<UnifiedQueryRuleParams> {
function ruleObjectContainsResponseActions(
rule?: RuleAlertType
): rule is Rule<UnifiedQueryRuleParams | EsqlRuleParams | EqlRuleParams | NewTermsRuleParams> {
return rule != null && 'params' in rule && 'responseActions' in rule?.params;
}

View file

@ -6,7 +6,6 @@
*/
import { each } from 'lodash';
import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils';
import { stringify } from '../../../endpoint/utils/stringify';
import type {
RuleResponseEndpointAction,
@ -29,8 +28,8 @@ export const endpointResponseAction = async (
'ruleExecution',
'automatedResponseActions'
);
const ruleId = alerts[0][ALERT_RULE_UUID];
const ruleName = alerts[0][ALERT_RULE_NAME];
const ruleId = alerts[0].kibana.alert?.rule.uuid;
const ruleName = alerts[0].kibana.alert?.rule.name;
const logMsgPrefix = `Rule [${ruleName}][${ruleId}]:`;
const { comment, command } = responseAction.params;
const errors: string[] = [];

View file

@ -96,8 +96,13 @@ describe('ScheduleNotificationResponseActions', () => {
},
},
];
await scheduleNotificationResponseActions({ signals, responseActions });
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: signals.length,
responseActions,
});
expect(response).not.toBeUndefined();
expect(osqueryActionMock.create).toHaveBeenCalledWith({
...defaultQueryResultParams,
query: simpleQuery,
@ -123,8 +128,13 @@ describe('ScheduleNotificationResponseActions', () => {
},
},
];
await scheduleNotificationResponseActions({ signals, responseActions });
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: signals.length,
responseActions,
});
expect(response).not.toBeUndefined();
expect(osqueryActionMock.create).toHaveBeenCalledWith({
...defaultPackResultParams,
queries: [{ ...defaultQueries, id: 'query-1', query: simpleQuery }],
@ -149,8 +159,12 @@ describe('ScheduleNotificationResponseActions', () => {
},
},
];
await scheduleNotificationResponseActions({ signals, responseActions });
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: signals.length,
responseActions,
});
expect(response).not.toBeUndefined();
expect(endpointActionMock.getInternalResponseActionsClient).toHaveBeenCalledTimes(1);
expect(endpointActionMock.getInternalResponseActionsClient).toHaveBeenCalledWith({
agentType: 'endpoint',
@ -188,11 +202,14 @@ describe('ScheduleNotificationResponseActions', () => {
},
},
];
await scheduleNotificationResponseActions({
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: signals.length,
responseActions,
});
expect(response).not.toBeUndefined();
expect(mockedResponseActionsClient.killProcess).toHaveBeenCalledWith(
{
alert_ids: ['alert-id-1'],
@ -223,12 +240,42 @@ describe('ScheduleNotificationResponseActions', () => {
},
},
];
await scheduleNotificationResponseActions({
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: signals.length,
responseActions,
});
expect(response).not.toBeUndefined();
expect(mockedResponseActionsClient.isolate).toHaveBeenCalledTimes(signals.length - 1);
});
it('should not call any action service if no response actions are provided', async () => {
const response = await scheduleNotificationResponseActions({
signals: getSignals(),
signalsCount: 2,
responseActions: [],
});
expect(response).toBeUndefined();
});
it('should not call any action service if signalsCount is 0', async () => {
const signals = getSignals();
const responseActions: RuleResponseAction[] = [
{
actionTypeId: ResponseActionTypesEnum['.endpoint'],
params: {
command: 'isolate',
comment: 'test process comment',
},
},
];
const response = await scheduleNotificationResponseActions({
signals,
signalsCount: 0,
responseActions,
});
expect(response).toBeUndefined();
});
});
});

View file

@ -5,13 +5,14 @@
* 2.0.
*/
import { expandDottedObject } from '../../../../common/utils/expand_dotted';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import type { SetupPlugins } from '../../../plugin_contract';
import { ResponseActionTypesEnum } from '../../../../common/api/detection_engine/model/rule_response_actions';
import { osqueryResponseAction } from './osquery_response_action';
import { endpointResponseAction } from './endpoint_response_action';
import type { ScheduleNotificationActions } from '../rule_types/types';
import type { AlertWithAgent, Alert } from './types';
import type { Alert, AlertWithAgent } from './types';
interface ScheduleNotificationResponseActionsService {
endpointAppContextService: EndpointAppContextService;
@ -23,10 +24,15 @@ export const getScheduleNotificationResponseActionsService =
osqueryCreateActionService,
endpointAppContextService,
}: ScheduleNotificationResponseActionsService) =>
async ({ signals, responseActions }: ScheduleNotificationActions) => {
const alerts = (signals as Alert[]).filter((alert) => alert.agent?.id) as AlertWithAgent[];
async ({ signals, signalsCount, responseActions }: ScheduleNotificationActions) => {
if (!signalsCount || !responseActions?.length) {
return;
}
// expandDottedObject is needed eg in ESQL rule because it's alerts come without nested agent, host etc data but everything is dotted
const nestedAlerts = signals.map((signal) => expandDottedObject(signal as object)) as Alert[];
const alerts = nestedAlerts.filter((alert) => alert.agent?.id) as AlertWithAgent[];
await Promise.all(
return Promise.all(
responseActions.map(async (responseAction) => {
if (
responseAction.actionTypeId === ResponseActionTypesEnum['.osquery'] &&

View file

@ -19,6 +19,14 @@ export type Alert = ParsedTechnicalFields & {
process?: {
pid: string;
};
kibana: {
alert?: {
rule: {
uuid: string;
name: string;
};
};
};
};
export interface AlertAgent {

View file

@ -162,6 +162,7 @@ export const EqlSpecificRuleParams = z.object({
timestampField: TimestampField.optional(),
tiebreakerField: TiebreakerField.optional(),
alertSuppression: AlertSuppressionCamel.optional(),
responseActions: z.array(RuleResponseAction).optional(),
});
export type EqlRuleParams = BaseRuleParams & EqlSpecificRuleParams;
@ -173,6 +174,7 @@ export const EsqlSpecificRuleParams = z.object({
language: z.literal('esql'),
query: RuleQuery,
alertSuppression: AlertSuppressionCamel.optional(),
responseActions: z.array(RuleResponseAction).optional(),
});
export type EsqlRuleParams = BaseRuleParams & EsqlSpecificRuleParams;
@ -280,6 +282,7 @@ export const NewTermsSpecificRuleParams = z.object({
language: KqlQueryLanguage,
dataViewId: DataViewId.optional(),
alertSuppression: AlertSuppressionCamel.optional(),
responseActions: z.array(RuleResponseAction).optional(),
});
export type NewTermsRuleParams = BaseRuleParams & NewTermsSpecificRuleParams;

View file

@ -11,16 +11,22 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { SERVER_APP_ID } from '../../../../../common/constants';
import { EqlRuleParams } from '../../rule_schema';
import { eqlExecutor } from './eql';
import type { CreateRuleOptions, SecurityAlertType, SignalSourceHit } from '../types';
import type {
CreateRuleOptions,
SecurityAlertType,
SignalSourceHit,
CreateRuleAdditionalOptions,
} from '../types';
import { validateIndexPatterns } from '../utils';
import type { BuildReasonMessage } from '../utils/reason_formatters';
import { wrapSuppressedAlerts } from '../utils/wrap_suppressed_alerts';
import { getIsAlertSuppressionActive } from '../utils/get_is_alert_suppression_active';
export const createEqlAlertType = (
createOptions: CreateRuleOptions
createOptions: CreateRuleOptions & CreateRuleAdditionalOptions
): SecurityAlertType<EqlRuleParams, {}, {}, 'default'> => {
const { experimentalFeatures, version, licensing } = createOptions;
const { experimentalFeatures, version, licensing, scheduleNotificationResponseActionsService } =
createOptions;
return {
id: EQL_RULE_TYPE_ID,
name: 'Event Correlation Rule',
@ -125,6 +131,7 @@ export const createEqlAlertType = (
alertWithSuppression,
isAlertSuppressionActive: isNonSeqAlertSuppressionActive,
experimentalFeatures,
scheduleNotificationResponseActionsService,
});
return { ...result, state };
},

View file

@ -37,6 +37,7 @@ describe('eql_executor', () => {
maxSignals: params.maxSignals,
};
const mockExperimentalFeatures = {} as ExperimentalFeatures;
const mockScheduleNotificationResponseActionsService = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
@ -72,6 +73,8 @@ describe('eql_executor', () => {
alertWithSuppression: jest.fn(),
isAlertSuppressionActive: false,
experimentalFeatures: mockExperimentalFeatures,
scheduleNotificationResponseActionsService:
mockScheduleNotificationResponseActionsService,
});
expect(result.warningMessages).toEqual([
`The following exceptions won't be applied to rule execution: ${
@ -121,6 +124,8 @@ describe('eql_executor', () => {
alertWithSuppression: jest.fn(),
isAlertSuppressionActive: true,
experimentalFeatures: mockExperimentalFeatures,
scheduleNotificationResponseActionsService:
mockScheduleNotificationResponseActionsService,
});
expect(result.warningMessages).toContain(
@ -154,10 +159,40 @@ describe('eql_executor', () => {
alertWithSuppression: jest.fn(),
isAlertSuppressionActive: true,
experimentalFeatures: mockExperimentalFeatures,
scheduleNotificationResponseActionsService: mockScheduleNotificationResponseActionsService,
});
expect(result.userError).toEqual(true);
});
it('should handle scheduleNotificationResponseActionsService call', async () => {
const result = await eqlExecutor({
inputIndex: DEFAULT_INDEX_PATTERN,
runtimeMappings: {},
completeRule: eqlCompleteRule,
tuple,
ruleExecutionLogger,
services: alertServices,
version,
bulkCreate: jest.fn(),
wrapHits: jest.fn(),
wrapSequences: jest.fn(),
primaryTimestamp: '@timestamp',
exceptionFilter: undefined,
unprocessedExceptions: [],
wrapSuppressedHits: jest.fn(),
alertTimestampOverride: undefined,
alertWithSuppression: jest.fn(),
isAlertSuppressionActive: false,
experimentalFeatures: mockExperimentalFeatures,
scheduleNotificationResponseActionsService: mockScheduleNotificationResponseActionsService,
});
expect(mockScheduleNotificationResponseActionsService).toBeCalledWith({
signals: result.createdSignals,
signalsCount: result.createdSignalsCount,
responseActions: eqlCompleteRule.ruleParams.responseActions,
});
});
it('should pass frozen tier filters in eql search request', async () => {
getDataTierFilterMock.mockResolvedValue([
{
@ -189,6 +224,7 @@ describe('eql_executor', () => {
alertWithSuppression: jest.fn(),
isAlertSuppressionActive: true,
experimentalFeatures: mockExperimentalFeatures,
scheduleNotificationResponseActionsService: mockScheduleNotificationResponseActionsService,
});
const searchArgs =

View file

@ -26,6 +26,7 @@ import type {
SearchAfterAndBulkCreateReturnType,
SignalSource,
WrapSuppressedHits,
CreateRuleAdditionalOptions,
} from '../types';
import {
addToSearchAfterReturn,
@ -66,6 +67,7 @@ interface EqlExecutorParams {
alertWithSuppression: SuppressedAlertService;
isAlertSuppressionActive: boolean;
experimentalFeatures: ExperimentalFeatures;
scheduleNotificationResponseActionsService: CreateRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
}
export const eqlExecutor = async ({
@ -88,6 +90,7 @@ export const eqlExecutor = async ({
alertWithSuppression,
isAlertSuppressionActive,
experimentalFeatures,
scheduleNotificationResponseActionsService,
}: EqlExecutorParams): Promise<SearchAfterAndBulkCreateReturnType> => {
const ruleParams = completeRule.ruleParams;
@ -188,6 +191,14 @@ export const eqlExecutor = async ({
result.warningMessages.push(maxSignalsWarning);
}
if (scheduleNotificationResponseActionsService) {
scheduleNotificationResponseActionsService({
signals: result.createdSignals,
signalsCount: result.createdSignalsCount,
responseActions: completeRule.ruleParams.responseActions,
});
}
return result;
} catch (error) {
if (

View file

@ -11,12 +11,13 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { SERVER_APP_ID } from '../../../../../common/constants';
import { EsqlRuleParams } from '../../rule_schema';
import { esqlExecutor } from './esql';
import type { CreateRuleOptions, SecurityAlertType } from '../types';
import type { CreateRuleOptions, SecurityAlertType, CreateRuleAdditionalOptions } from '../types';
export const createEsqlAlertType = (
createOptions: CreateRuleOptions
createOptions: CreateRuleOptions & CreateRuleAdditionalOptions
): SecurityAlertType<EsqlRuleParams, {}, {}, 'default'> => {
const { version, experimentalFeatures, licensing } = createOptions;
const { version, experimentalFeatures, licensing, scheduleNotificationResponseActionsService } =
createOptions;
return {
id: ESQL_RULE_TYPE_ID,
name: 'ES|QL Rule',
@ -44,6 +45,13 @@ export const createEsqlAlertType = (
isExportable: false,
category: DEFAULT_APP_CATEGORIES.security.id,
producer: SERVER_APP_ID,
executor: (params) => esqlExecutor({ ...params, experimentalFeatures, version, licensing }),
executor: (params) =>
esqlExecutor({
...params,
experimentalFeatures,
version,
licensing,
scheduleNotificationResponseActionsService,
}),
};
};

View file

@ -28,8 +28,7 @@ import { rowToDocument } from './utils';
import { fetchSourceDocuments } from './fetch_source_documents';
import { buildReasonMessageForEsqlAlert } from '../utils/reason_formatters';
import type { RunOpts, SignalSource } from '../types';
import type { RunOpts, SignalSource, CreateRuleAdditionalOptions } from '../types';
import {
addToSearchAfterReturn,
createSearchAfterReturnType,
@ -63,6 +62,7 @@ export const esqlExecutor = async ({
spaceId,
experimentalFeatures,
licensing,
scheduleNotificationResponseActionsService,
}: {
runOpts: RunOpts<EsqlRuleParams>;
services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>;
@ -71,6 +71,7 @@ export const esqlExecutor = async ({
version: string;
experimentalFeatures: ExperimentalFeatures;
licensing: LicensingPluginSetup;
scheduleNotificationResponseActionsService: CreateRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
}) => {
const ruleParams = completeRule.ruleParams;
/**
@ -225,6 +226,13 @@ export const esqlExecutor = async ({
break;
}
}
if (scheduleNotificationResponseActionsService) {
scheduleNotificationResponseActionsService({
signals: result.createdSignals,
signalsCount: result.createdSignalsCount,
responseActions: completeRule.ruleParams.responseActions,
});
}
// no more results will be found
if (response.values.length < size) {

View file

@ -12,7 +12,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { SERVER_APP_ID } from '../../../../../common/constants';
import { NewTermsRuleParams } from '../../rule_schema';
import type { CreateRuleOptions, SecurityAlertType } from '../types';
import type { CreateRuleOptions, SecurityAlertType, CreateRuleAdditionalOptions } from '../types';
import { singleSearchAfter } from '../utils/single_search_after';
import { getFilter } from '../utils/get_filter';
import { wrapNewTermsAlerts } from './wrap_new_terms_alerts';
@ -46,9 +46,10 @@ import { multiTermsComposite } from './multi_terms_composite';
import type { GenericBulkCreateResponse } from '../utils/bulk_create_with_suppression';
export const createNewTermsAlertType = (
createOptions: CreateRuleOptions
createOptions: CreateRuleOptions & CreateRuleAdditionalOptions
): SecurityAlertType<NewTermsRuleParams, {}, {}, 'default'> => {
const { logger, licensing, experimentalFeatures } = createOptions;
const { logger, licensing, experimentalFeatures, scheduleNotificationResponseActionsService } =
createOptions;
return {
id: NEW_TERMS_RULE_TYPE_ID,
name: 'New Terms Rule',
@ -414,6 +415,15 @@ export const createNewTermsAlertType = (
afterKey = searchResultWithAggs.aggregations.new_terms.after_key;
}
if (scheduleNotificationResponseActionsService) {
scheduleNotificationResponseActionsService({
signals: result.createdSignals,
signalsCount: result.createdSignalsCount,
responseActions: completeRule.ruleParams.responseActions,
});
}
return { ...result, state };
},
};

View file

@ -22,7 +22,7 @@ import type { UnifiedQueryRuleParams } from '../../rule_schema';
import type { ExperimentalFeatures } from '../../../../../common/experimental_features';
import { buildReasonMessageForQueryAlert } from '../utils/reason_formatters';
import { withSecuritySpan } from '../../../../utils/with_security_span';
import type { CreateQueryRuleAdditionalOptions, RunOpts } from '../types';
import type { CreateRuleAdditionalOptions, RunOpts } from '../types';
export const queryExecutor = async ({
runOpts,
@ -42,7 +42,7 @@ export const queryExecutor = async ({
version: string;
spaceId: string;
bucketHistory?: BucketHistory[];
scheduleNotificationResponseActionsService?: CreateQueryRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
scheduleNotificationResponseActionsService: CreateRuleAdditionalOptions['scheduleNotificationResponseActionsService'];
licensing: LicensingPluginSetup;
}) => {
const completeRule = runOpts.completeRule;
@ -99,13 +99,10 @@ export const queryExecutor = async ({
state: {},
};
if (
completeRule.ruleParams.responseActions?.length &&
result.createdSignalsCount &&
scheduleNotificationResponseActionsService
) {
if (scheduleNotificationResponseActionsService) {
scheduleNotificationResponseActionsService({
signals: result.createdSignals,
signalsCount: result.createdSignalsCount,
responseActions: completeRule.ruleParams.responseActions,
});
}

View file

@ -161,15 +161,15 @@ export interface CreateRuleOptions {
export interface ScheduleNotificationActions {
signals: unknown[];
responseActions: RuleResponseAction[];
signalsCount: number;
responseActions: RuleResponseAction[] | undefined;
}
export interface CreateQueryRuleAdditionalOptions {
export interface CreateRuleAdditionalOptions {
scheduleNotificationResponseActionsService?: (params: ScheduleNotificationActions) => void;
}
export interface CreateQueryRuleOptions
extends CreateRuleOptions,
CreateQueryRuleAdditionalOptions {
export interface CreateQueryRuleOptions extends CreateRuleOptions, CreateRuleAdditionalOptions {
id: typeof QUERY_RULE_TYPE_ID | typeof SAVED_QUERY_RULE_TYPE_ID;
name: 'Custom Query Rule' | 'Saved Query Rule';
}

View file

@ -78,7 +78,7 @@ import type { IRuleMonitoringService } from './lib/detection_engine/rule_monitor
import { createRuleMonitoringService } from './lib/detection_engine/rule_monitoring';
import { EndpointMetadataService } from './endpoint/services/metadata';
import type {
CreateQueryRuleAdditionalOptions,
CreateRuleAdditionalOptions,
CreateRuleOptions,
} from './lib/detection_engine/rule_types/types';
// eslint-disable-next-line no-restricted-imports
@ -311,7 +311,7 @@ export class Plugin implements ISecuritySolutionPlugin {
analytics: core.analytics,
};
const queryRuleAdditionalOptions: CreateQueryRuleAdditionalOptions = {
const ruleAdditionalOptions: CreateRuleAdditionalOptions = {
scheduleNotificationResponseActionsService: getScheduleNotificationResponseActionsService({
endpointAppContextService: this.endpointAppContextService,
osqueryCreateActionService: plugins.osquery.createActionService,
@ -320,15 +320,19 @@ export class Plugin implements ISecuritySolutionPlugin {
const securityRuleTypeWrapper = createSecurityRuleTypeWrapper(securityRuleTypeOptions);
plugins.alerting.registerType(securityRuleTypeWrapper(createEqlAlertType(ruleOptions)));
plugins.alerting.registerType(
securityRuleTypeWrapper(createEqlAlertType({ ...ruleOptions, ...ruleAdditionalOptions }))
);
if (!experimentalFeatures.esqlRulesDisabled) {
plugins.alerting.registerType(securityRuleTypeWrapper(createEsqlAlertType(ruleOptions)));
plugins.alerting.registerType(
securityRuleTypeWrapper(createEsqlAlertType({ ...ruleOptions, ...ruleAdditionalOptions }))
);
}
plugins.alerting.registerType(
securityRuleTypeWrapper(
createQueryAlertType({
...ruleOptions,
...queryRuleAdditionalOptions,
...ruleAdditionalOptions,
id: SAVED_QUERY_RULE_TYPE_ID,
name: 'Saved Query Rule',
})
@ -342,14 +346,16 @@ export class Plugin implements ISecuritySolutionPlugin {
securityRuleTypeWrapper(
createQueryAlertType({
...ruleOptions,
...queryRuleAdditionalOptions,
...ruleAdditionalOptions,
id: QUERY_RULE_TYPE_ID,
name: 'Custom Query Rule',
})
)
);
plugins.alerting.registerType(securityRuleTypeWrapper(createThresholdAlertType(ruleOptions)));
plugins.alerting.registerType(securityRuleTypeWrapper(createNewTermsAlertType(ruleOptions)));
plugins.alerting.registerType(
securityRuleTypeWrapper(createNewTermsAlertType({ ...ruleOptions, ...ruleAdditionalOptions }))
);
// TODO We need to get the endpoint routes inside of initRoutes
initRoutes(