From fb1eeb0945e25928a2516133055f048ff098166f Mon Sep 17 00:00:00 2001 From: Georgii Gorbachev Date: Sat, 21 May 2022 00:21:53 +0200 Subject: [PATCH] [Security Solution][Detections] Add new fields to the rule model: Related Integrations, Required Fields, and Setup (#132409) **Addresses partially:** https://github.com/elastic/security-team/issues/2083, https://github.com/elastic/security-team/issues/558, https://github.com/elastic/security-team/issues/2856, https://github.com/elastic/security-team/issues/1801 (internal tickets) ## Summary **TL;DR:** With this PR, it's now possible to specify `related_integrations`, `required_fields`, and `setup` fields in prebuilt rules in https://github.com/elastic/detection-rules. They are returned within rules in the API responses. This PR: - Adds 3 new fields to the model of Security detection rules. These fields are common to all of the rule types we have. - **Related Integrations**. It's a list of Fleet integrations associated with a given rule. It's assumed that if the user installs them, the rule might start to work properly because it will start receiving source events potentially matching the rule's query. - **Required Fields**. It's a list of event fields that must be present in the source indices of a given rule. - **Setup Guide**. It's any instructions for the user for setting up their environment in order to start receiving source events for a given rule. It's a text. Markdown is supported. It's similar to the Investigation Guide that we show on the Details page. - Adjusts API endpoints accordingly: - These fields are for prebuilt rules only and are supposed to be read-only in the UI. - Specifying these fields in the request parameters of the create/update/patch rule API endpoints is not supported. - These fields are returned in all responses that contain rules. If they are missing in a rule, default values are returned (empty array, empty string). - When duplicating a prebuilt rule, these fields are being reset to their default value (empty array, empty string). - Export/Import is supported. Edge case / supported hack: it's possible to specify these fields manually in a ndjson doc and import with a rule. - The fields are being copied to `kibana.alert.rule.parameters` field of an alert document, which is mapped as a flattened field type. No special handling for the new fields was needed there. - Adjusts tests accordingly. ## Related Integrations Example (part of a rule returned from the API): ```json { "related_integrations": [ { "package": "windows", "version": "1.5.x" }, { "package": "azure", "integration": "activitylogs", "version": "~1.1.6" } ], } ``` Schema: ```ts /** * Related integration is a potential dependency of a rule. It's assumed that if the user installs * one of the related integrations of a rule, the rule might start to work properly because it will * have source events (generated by this integration) potentially matching the rule's query. * * NOTE: Proper work is not guaranteed, because a related integration, if installed, can be * configured differently or generate data that is not necessarily relevant for this rule. * * Related integration is a combination of a Fleet package and (optionally) one of the * package's "integrations" that this package contains. It is represented by 3 properties: * * - `package`: name of the package (required, unique id) * - `version`: version of the package (required, semver-compatible) * - `integration`: name of the integration of this package (optional, id within the package) * * There are Fleet packages like `windows` that contain only one integration; in this case, * `integration` should be unspecified. There are also packages like `aws` and `azure` that contain * several integrations; in this case, `integration` should be specified. * * @example * const x: RelatedIntegration = { * package: 'windows', * version: '1.5.x', * }; * * @example * const x: RelatedIntegration = { * package: 'azure', * version: '~1.1.6', * integration: 'activitylogs', * }; */ export type RelatedIntegration = t.TypeOf; export const RelatedIntegration = t.exact( t.intersection([ t.type({ package: NonEmptyString, version: NonEmptyString, }), t.partial({ integration: NonEmptyString, }), ]) ); ``` ## Required Fields Example (part of a rule returned from the API): ```json { "required_fields": [ { "name": "event.action", "type": "keyword", "ecs": true }, { "name": "event.code", "type": "keyword", "ecs": true }, { "name": "winlog.event_data.AttributeLDAPDisplayName", "type": "keyword", "ecs": false } ], } ``` Schema: ```ts /** * Almost all types of Security rules check source event documents for a match to some kind of * query or filter. If a document has certain field with certain values, then it's a match and * the rule will generate an alert. * * Required field is an event field that must be present in the source indices of a given rule. * * @example * const standardEcsField: RequiredField = { * name: 'event.action', * type: 'keyword', * ecs: true, * }; * * @example * const nonEcsField: RequiredField = { * name: 'winlog.event_data.AttributeLDAPDisplayName', * type: 'keyword', * ecs: false, * }; */ export type RequiredField = t.TypeOf; export const RequiredField = t.exact( t.type({ name: NonEmptyString, type: NonEmptyString, ecs: t.boolean, }) ); ``` ## Setup Guide Example (part of a rule returned from the API): ```json { "setup": "## Config\n\nThe 'PowerShell Script Block Logging' logging policy must be enabled.\nSteps to implement the logging policy with with Advanced Audit Configuration:\n\n```\nComputer Configuration > \nAdministrative Templates > \nWindows PowerShell > \nTurn on PowerShell Script Block Logging (Enable)\n```\n\nSteps to implement the logging policy via registry:\n\n```\nreg add \"hklm\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\" /v EnableScriptBlockLogging /t REG_DWORD /d 1\n```\n", } ``` Schema: ```ts /** * Any instructions for the user for setting up their environment in order to start receiving * source events for a given rule. * * It's a multiline text. Markdown is supported. */ export type SetupGuide = t.TypeOf; export const SetupGuide = t.string; ``` ## Details on the schema This PR adjusts all the 6 rule schemas we have: 1. Alerting Framework rule `params` schema: - `security_solution/server/lib/detection_engine/schemas/rule_schemas.ts` - `security_solution/server/lib/detection_engine/schemas/rule_converters.ts` 2. HTTP API main old schema: - `security_solution/common/detection_engine/schemas/response/rules_schema.ts` 3. HTTP API main new schema: - `security_solution/common/detection_engine/schemas/request/rule_schemas.ts` 4. Prebuilt rule schema: - `security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts` 5. Import rule schema: - `security_solution/common/detection_engine/schemas/request/import_rules_schema.ts` 6. Rule schema used on the frontend side: - `security_solution/public/detections/containers/detection_engine/rules/types.ts` Names of the fields on the HTTP API level: - `related_integrations` - `required_fields` - `setup` Names of the fields on the Alerting Framework level: - `params.relatedIntegrations` - `params.requiredFields` - `params.setup` ## Next steps - Create a new endpoint for returning installed Fleet integrations (gonna be a separate PR). - Rebase https://github.com/elastic/kibana/pull/131475 on top of this PR after merge. - Cover the new fields with dedicated tests (gonna be a separate PR). - Update API docs (gonna be a separate PR). - Address the tech debt of having 6 different schemas (gonna create a ticket for that). ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../detection_engine/schemas/common/index.ts | 1 + .../schemas/common/rule_params.ts | 146 +++++++ .../request/add_prepackaged_rules_schema.ts | 8 +- .../schemas/request/import_rules_schema.ts | 8 +- .../schemas/request/patch_rules_schema.ts | 2 +- .../schemas/request/rule_schemas.ts | 11 + .../schemas/response/rules_schema.mocks.ts | 6 + .../schemas/response/rules_schema.ts | 6 + .../security_solution/cypress/objects/rule.ts | 9 +- .../containers/detection_engine/rules/mock.ts | 9 + .../detection_engine/rules/types.ts | 6 + .../detection_engine/rules/use_rule.test.tsx | 3 + .../rules/use_rule_with_fallback.test.tsx | 3 + .../rules/all/__mocks__/mock.ts | 6 + .../schedule_notification_actions.test.ts | 3 + ...dule_throttle_notification_actions.test.ts | 3 + .../routes/__mocks__/utils.ts | 3 + .../routes/rules/utils/import_rules_utils.ts | 9 + .../routes/rules/validate.test.ts | 3 + .../factories/utils/build_alert.test.ts | 6 + .../rules/create_rules.mock.ts | 9 + .../detection_engine/rules/create_rules.ts | 6 + .../rules/duplicate_rule.test.ts | 391 +++++++++++++----- .../detection_engine/rules/duplicate_rule.ts | 16 +- .../rules/get_export_all.test.ts | 3 + .../rules/get_export_by_object_ids.test.ts | 6 + .../rules/install_prepacked_rules.ts | 6 + .../lib/detection_engine/rules/patch_rules.ts | 9 + .../lib/detection_engine/rules/types.ts | 9 + .../rules/update_prepacked_rules.ts | 9 + .../detection_engine/rules/update_rules.ts | 3 + .../lib/detection_engine/rules/utils.test.ts | 9 + .../lib/detection_engine/rules/utils.ts | 8 +- .../schemas/rule_converters.ts | 6 + .../schemas/rule_schemas.mock.ts | 3 + .../detection_engine/schemas/rule_schemas.ts | 8 +- .../signals/__mocks__/es_results.ts | 9 + .../basic/tests/create_rules.ts | 3 + .../group1/create_rules.ts | 3 + .../group6/alerts/alerts_compatibility.ts | 6 + .../utils/get_complex_rule_output.ts | 3 + .../utils/get_simple_rule_output.ts | 3 + 42 files changed, 660 insertions(+), 119 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts index 4ef5d6178d5a5..615eb3f05876e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts @@ -6,4 +6,5 @@ */ export * from './rule_monitoring'; +export * from './rule_params'; export * from './schemas'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts new file mode 100644 index 0000000000000..b9588a26bb35b --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts @@ -0,0 +1,146 @@ +/* + * 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 * as t from 'io-ts'; +import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; + +// ------------------------------------------------------------------------------------------------- +// Related integrations + +/** + * Related integration is a potential dependency of a rule. It's assumed that if the user installs + * one of the related integrations of a rule, the rule might start to work properly because it will + * have source events (generated by this integration) potentially matching the rule's query. + * + * NOTE: Proper work is not guaranteed, because a related integration, if installed, can be + * configured differently or generate data that is not necessarily relevant for this rule. + * + * Related integration is a combination of a Fleet package and (optionally) one of the + * package's "integrations" that this package contains. It is represented by 3 properties: + * + * - `package`: name of the package (required, unique id) + * - `version`: version of the package (required, semver-compatible) + * - `integration`: name of the integration of this package (optional, id within the package) + * + * There are Fleet packages like `windows` that contain only one integration; in this case, + * `integration` should be unspecified. There are also packages like `aws` and `azure` that contain + * several integrations; in this case, `integration` should be specified. + * + * @example + * const x: RelatedIntegration = { + * package: 'windows', + * version: '1.5.x', + * }; + * + * @example + * const x: RelatedIntegration = { + * package: 'azure', + * version: '~1.1.6', + * integration: 'activitylogs', + * }; + */ +export type RelatedIntegration = t.TypeOf; +export const RelatedIntegration = t.exact( + t.intersection([ + t.type({ + package: NonEmptyString, + version: NonEmptyString, + }), + t.partial({ + integration: NonEmptyString, + }), + ]) +); + +/** + * Array of related integrations. + * + * @example + * const x: RelatedIntegrationArray = [ + * { + * package: 'windows', + * version: '1.5.x', + * }, + * { + * package: 'azure', + * version: '~1.1.6', + * integration: 'activitylogs', + * }, + * ]; + */ +export type RelatedIntegrationArray = t.TypeOf; +export const RelatedIntegrationArray = t.array(RelatedIntegration); + +// ------------------------------------------------------------------------------------------------- +// Required fields + +/** + * Almost all types of Security rules check source event documents for a match to some kind of + * query or filter. If a document has certain field with certain values, then it's a match and + * the rule will generate an alert. + * + * Required field is an event field that must be present in the source indices of a given rule. + * + * @example + * const standardEcsField: RequiredField = { + * name: 'event.action', + * type: 'keyword', + * ecs: true, + * }; + * + * @example + * const nonEcsField: RequiredField = { + * name: 'winlog.event_data.AttributeLDAPDisplayName', + * type: 'keyword', + * ecs: false, + * }; + */ +export type RequiredField = t.TypeOf; +export const RequiredField = t.exact( + t.type({ + name: NonEmptyString, + type: NonEmptyString, + ecs: t.boolean, + }) +); + +/** + * Array of event fields that must be present in the source indices of a given rule. + * + * @example + * const x: RequiredFieldArray = [ + * { + * name: 'event.action', + * type: 'keyword', + * ecs: true, + * }, + * { + * name: 'event.code', + * type: 'keyword', + * ecs: true, + * }, + * { + * name: 'winlog.event_data.AttributeLDAPDisplayName', + * type: 'keyword', + * ecs: false, + * }, + * ]; + */ +export type RequiredFieldArray = t.TypeOf; +export const RequiredFieldArray = t.array(RequiredField); + +// ------------------------------------------------------------------------------------------------- +// Setup guide + +/** + * Any instructions for the user for setting up their environment in order to start receiving + * source events for a given rule. + * + * It's a multiline text. Markdown is supported. + */ +export type SetupGuide = t.TypeOf; +export const SetupGuide = t.string; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts index 618aee3379316..27ebf9a608ffa 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts @@ -72,7 +72,10 @@ import { Author, event_category_override, namespace, -} from '../common/schemas'; + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, +} from '../common'; /** * Big differences between this schema and the createRulesSchema @@ -117,8 +120,11 @@ export const addPrepackagedRulesSchema = t.intersection([ meta, // defaults to "undefined" if not set during decode machine_learning_job_id, // defaults to "undefined" if not set during decode max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode + related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode + required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode rule_name_override, // defaults to "undefined" if not set during decode + setup: SetupGuide, // defaults to "undefined" if not set during decode severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode tags: DefaultStringArray, // defaults to empty string array if not set during decode to: DefaultToString, // defaults to "now" if not set during decode diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts index 63c41e45e42d0..8cee4183d6ee7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts @@ -80,7 +80,10 @@ import { timestamp_override, Author, event_category_override, -} from '../common/schemas'; + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, +} from '../common'; /** * Differences from this and the createRulesSchema are @@ -129,8 +132,11 @@ export const importRulesSchema = t.intersection([ meta, // defaults to "undefined" if not set during decode machine_learning_job_id, // defaults to "undefined" if not set during decode max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode + related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode + required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode rule_name_override, // defaults to "undefined" if not set during decode + setup: SetupGuide, // defaults to "undefined" if not set during decode severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode tags: DefaultStringArray, // defaults to empty string array if not set during decode to: DefaultToString, // defaults to "now" if not set during decode diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts index 8c801e75af08c..6678681471b38 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts @@ -61,7 +61,7 @@ import { rule_name_override, timestamp_override, event_category_override, -} from '../common/schemas'; +} from '../common'; /** * All of the patch elements should default to undefined if not set diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 69a748c3bd95c..9aef9ac8f2651 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -67,6 +67,9 @@ import { created_by, namespace, ruleExecutionSummary, + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, } from '../common'; export const createSchema = < @@ -412,6 +415,14 @@ const responseRequiredFields = { updated_by, created_at, created_by, + + // NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt + // rules only. We don't want to allow users to edit these 3 fields via the API. If we added them + // to baseParams.defaultable, they would become a part of the request schema as optional fields. + // This is why we add them here, in order to add them only to the response schema. + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, }; const responseOptionalFields = { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 0642481b62a6a..eeaab6dc50021 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -68,6 +68,9 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem rule_id: 'query-rule-id', interval: '5m', exceptions_list: getListArrayMock(), + related_integrations: [], + required_fields: [], + setup: '', }); export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { @@ -132,6 +135,9 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial; diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 32c55e22ae7c9..de2de9bd78160 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -442,7 +442,9 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response severity, query, } = ruleResponse.body; - const rule = { + + // NOTE: Order of the properties in this object matters for the tests to work. + const rule: RulesSchema = { id, updated_at: updatedAt, updated_by: updatedBy, @@ -469,6 +471,9 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response version: 1, exceptions_list: [], immutable: false, + related_integrations: [], + required_fields: [], + setup: '', type: 'query', language: 'kuery', index: getIndexPatterns(), @@ -476,6 +481,8 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response throttle: 'no_actions', actions: [], }; + + // NOTE: Order of the properties in this object matters for the tests to work. const details = { exported_count: 1, exported_rules_count: 1, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts index 8c1737a4519a7..8a23cbf9e4318 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts @@ -38,6 +38,9 @@ export const savedRuleMock: Rule = { max_signals: 100, query: "user.email: 'root@elastic.co'", references: [], + related_integrations: [], + required_fields: [], + setup: '', severity: 'high', severity_mapping: [], tags: ['APM'], @@ -80,6 +83,9 @@ export const rulesMock: FetchRulesResponse = { 'event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection', filters: [], references: [], + related_integrations: [], + required_fields: [], + setup: '', severity: 'high', severity_mapping: [], updated_by: 'elastic', @@ -115,6 +121,9 @@ export const rulesMock: FetchRulesResponse = { query: 'event.kind:alert and event.module:endgame and event.action:rules_engine_event', filters: [], references: [], + related_integrations: [], + required_fields: [], + setup: '', severity: 'medium', severity_mapping: [], updated_by: 'elastic', diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index ddd65674274be..d6e278599d62d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -34,6 +34,9 @@ import { BulkAction, BulkActionEditPayload, ruleExecutionSummary, + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, } from '../../../../../common/detection_engine/schemas/common'; import { @@ -102,11 +105,14 @@ export const RuleSchema = t.intersection([ name: t.string, max_signals: t.number, references: t.array(t.string), + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, risk_score: t.number, risk_score_mapping, rule_id: t.string, severity, severity_mapping, + setup: SetupGuide, tags: t.array(t.string), type, to: t.string, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx index 096463872fc01..3ca18552a85ef 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx @@ -67,9 +67,12 @@ describe('useRule', () => { max_signals: 100, query: "user.email: 'root@elastic.co'", references: [], + related_integrations: [], + required_fields: [], risk_score: 75, risk_score_mapping: [], rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', + setup: '', severity: 'high', severity_mapping: [], tags: ['APM'], diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx index d7c4ad8772bd2..1816fd4c5a7af 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx @@ -78,9 +78,12 @@ describe('useRuleWithFallback', () => { "name": "Test rule", "query": "user.email: 'root@elastic.co'", "references": Array [], + "related_integrations": Array [], + "required_fields": Array [], "risk_score": 75, "risk_score_mapping": Array [], "rule_id": "bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf", + "setup": "", "severity": "high", "severity_mapping": Array [], "tags": Array [ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 77de8902be33a..d9f16242a544a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -70,6 +70,9 @@ export const mockRule = (id: string): Rule => ({ timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', timeline_title: 'Untitled timeline', meta: { from: '0m' }, + related_integrations: [], + required_fields: [], + setup: '', severity: 'low', severity_mapping: [], updated_by: 'elastic', @@ -133,6 +136,9 @@ export const mockRuleWithEverything = (id: string): Rule => ({ timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', timeline_title: 'Titled timeline', meta: { from: '0m' }, + related_integrations: [], + required_fields: [], + setup: '', severity: 'low', severity_mapping: [], updated_by: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts index d97eff43aeb8d..04e8f2130e88f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts @@ -51,6 +51,9 @@ describe('schedule_notification_actions', () => { note: '# sample markdown', version: 1, exceptionsList: [], + relatedIntegrations: [], + requiredFields: [], + setup: '', }; it('Should schedule actions with unflatted and legacy context', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts index d7293275c9c49..72ddb96301c47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts @@ -59,6 +59,9 @@ describe('schedule_throttle_notification_actions', () => { note: '# sample markdown', version: 1, exceptionsList: [], + relatedIntegrations: [], + requiredFields: [], + setup: '', }; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index 2622493a51dc1..54bf6133f9e37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -90,4 +90,7 @@ export const getOutputRuleAlertForRest = (): Omit< note: '# Investigative notes', version: 1, execution_summary: undefined, + related_integrations: [], + required_fields: [], + setup: '', }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts index d603784fc7081..8f87c1cdc0467 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts @@ -117,10 +117,13 @@ export const importRules = async ({ index, interval, max_signals: maxSignals, + related_integrations: relatedIntegrations, + required_fields: requiredFields, risk_score: riskScore, risk_score_mapping: riskScoreMapping, rule_name_override: ruleNameOverride, name, + setup, severity, severity_mapping: severityMapping, tags, @@ -192,9 +195,12 @@ export const importRules = async ({ interval, maxSignals, name, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, + setup, severity, severityMapping, tags, @@ -250,10 +256,13 @@ export const importRules = async ({ index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, name, + setup, severity, severityMapping, tags, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 0b8c49cdb4d17..833361e7e22bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -63,6 +63,9 @@ export const ruleOutput = (): RulesSchema => ({ note: '# Investigative notes', timeline_title: 'some-timeline-title', timeline_id: 'some-timeline-id', + related_integrations: [], + required_fields: [], + setup: '', }); describe('validate', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index 5768306999f79..083f495366480 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -126,6 +126,9 @@ describe('buildAlert', () => { ], to: 'now', references: ['http://example.com', 'https://example.com'], + related_integrations: [], + required_fields: [], + setup: '', version: 1, exceptions_list: [ { @@ -303,6 +306,9 @@ describe('buildAlert', () => { ], to: 'now', references: ['http://example.com', 'https://example.com'], + related_integrations: [], + required_fields: [], + setup: '', version: 1, exceptions_list: [ { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts index 1a41adb4f6da5..3c7acccae703a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts @@ -32,11 +32,14 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ index: ['index-123'], interval: '5m', maxSignals: 100, + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: 80, riskScoreMapping: [], ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Query with a rule id', + setup: undefined, severity: 'high', severityMapping: [], tags: [], @@ -85,11 +88,14 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ index: ['index-123'], interval: '5m', maxSignals: 100, + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: 80, riskScoreMapping: [], ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Machine Learning Job', + setup: undefined, severity: 'high', severityMapping: [], tags: [], @@ -141,12 +147,15 @@ export const getCreateThreatMatchRulesOptionsMock = (): CreateRulesOptions => ({ outputIndex: 'output-1', query: 'user.name: root or user.name: admin', references: ['http://www.example.com'], + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: 80, riskScoreMapping: [], ruleId: 'rule-1', ruleNameOverride: undefined, rulesClient: rulesClientMock.create(), savedId: 'savedId-123', + setup: undefined, severity: 'high', severityMapping: [], tags: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 24017adc20626..726964cdf3596 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -46,11 +46,14 @@ export const createRules = async ({ index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, outputIndex, name, + setup, severity, severityMapping, tags, @@ -109,9 +112,12 @@ export const createRules = async ({ : undefined, filters, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, + setup, severity, severityMapping, threat, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts index 04d8e66a076fb..cab22e136f529 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts @@ -6,6 +6,8 @@ */ import uuid from 'uuid'; +import { SanitizedRule } from '@kbn/alerting-plugin/common'; +import { RuleParams } from '../schemas/rule_schemas'; import { duplicateRule } from './duplicate_rule'; jest.mock('uuid', () => ({ @@ -13,120 +15,287 @@ jest.mock('uuid', () => ({ })); describe('duplicateRule', () => { - it('should return a copy of rule with new ruleId', () => { - (uuid.v4 as jest.Mock).mockReturnValue('newId'); - - expect( - duplicateRule({ - id: 'oldTestRuleId', - notifyWhen: 'onActiveAlert', - name: 'test', - tags: ['test'], - alertTypeId: 'siem.signals', - consumer: 'siem', - params: { - savedId: undefined, - author: [], - description: 'test', - ruleId: 'oldTestRuleId', - falsePositives: [], - from: 'now-360s', - immutable: false, - license: '', - outputIndex: '.siem-signals-default', - meta: undefined, - maxSignals: 100, - riskScore: 42, - riskScoreMapping: [], - severity: 'low', - severityMapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptionsList: [], - type: 'query', - language: 'kuery', - index: [], - query: 'process.args : "chmod"', - filters: [], - buildingBlockType: undefined, - namespace: undefined, - note: undefined, - timelineId: undefined, - timelineTitle: undefined, - ruleNameOverride: undefined, - timestampOverride: undefined, - }, - schedule: { - interval: '5m', - }, + const createTestRule = (): SanitizedRule => ({ + id: 'some id', + notifyWhen: 'onActiveAlert', + name: 'Some rule', + tags: ['some tag'], + alertTypeId: 'siem.queryRule', + consumer: 'siem', + params: { + savedId: undefined, + author: [], + description: 'Some description.', + ruleId: 'some ruleId', + falsePositives: [], + from: 'now-360s', + immutable: false, + license: '', + outputIndex: '.siem-signals-default', + meta: undefined, + maxSignals: 100, + relatedIntegrations: [], + requiredFields: [], + riskScore: 42, + riskScoreMapping: [], + severity: 'low', + severityMapping: [], + setup: 'Some setup guide.', + threat: [], + to: 'now', + references: [], + version: 1, + exceptionsList: [], + type: 'query', + language: 'kuery', + index: [], + query: 'process.args : "chmod"', + filters: [], + buildingBlockType: undefined, + namespace: undefined, + note: undefined, + timelineId: undefined, + timelineTitle: undefined, + ruleNameOverride: undefined, + timestampOverride: undefined, + }, + schedule: { + interval: '5m', + }, + enabled: false, + actions: [], + throttle: null, + apiKeyOwner: 'kibana', + createdBy: 'kibana', + updatedBy: 'kibana', + muteAll: false, + mutedInstanceIds: [], + updatedAt: new Date(2021, 0), + createdAt: new Date(2021, 0), + scheduledTaskId: undefined, + executionStatus: { + lastExecutionDate: new Date(2021, 0), + status: 'ok', + }, + }); + + beforeAll(() => { + (uuid.v4 as jest.Mock).mockReturnValue('new ruleId'); + }); + + afterAll(() => { + jest.clearAllMocks(); + }); + + it('returns an object with fields copied from a given rule', () => { + const rule = createTestRule(); + const result = duplicateRule(rule); + + expect(result).toEqual({ + name: expect.anything(), // covered in a separate test + params: { + ...rule.params, + ruleId: expect.anything(), // covered in a separate test + }, + tags: rule.tags, + alertTypeId: rule.alertTypeId, + consumer: rule.consumer, + schedule: rule.schedule, + actions: rule.actions, + throttle: null, // TODO: fix? + notifyWhen: null, // TODO: fix? + enabled: false, // covered in a separate test + }); + }); + + it('appends [Duplicate] to the name', () => { + const rule = createTestRule(); + rule.name = 'PowerShell Keylogging Script'; + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + name: 'PowerShell Keylogging Script [Duplicate]', + }) + ); + }); + + it('generates a new ruleId', () => { + const rule = createTestRule(); + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + ruleId: 'new ruleId', + }), + }) + ); + }); + + it('makes sure the duplicated rule is disabled', () => { + const rule = createTestRule(); + rule.enabled = true; + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ enabled: false, - actions: [], - throttle: null, - apiKeyOwner: 'kibana', - createdBy: 'kibana', - updatedBy: 'kibana', - muteAll: false, - mutedInstanceIds: [], - updatedAt: new Date(2021, 0), - createdAt: new Date(2021, 0), - scheduledTaskId: undefined, - executionStatus: { - lastExecutionDate: new Date(2021, 0), - status: 'ok', - }, }) - ).toMatchInlineSnapshot(` - Object { - "actions": Array [], - "alertTypeId": "siem.queryRule", - "consumer": "siem", - "enabled": false, - "name": "test [Duplicate]", - "notifyWhen": null, - "params": Object { - "author": Array [], - "buildingBlockType": undefined, - "description": "test", - "exceptionsList": Array [], - "falsePositives": Array [], - "filters": Array [], - "from": "now-360s", - "immutable": false, - "index": Array [], - "language": "kuery", - "license": "", - "maxSignals": 100, - "meta": undefined, - "namespace": undefined, - "note": undefined, - "outputIndex": ".siem-signals-default", - "query": "process.args : \\"chmod\\"", - "references": Array [], - "riskScore": 42, - "riskScoreMapping": Array [], - "ruleId": "newId", - "ruleNameOverride": undefined, - "savedId": undefined, - "severity": "low", - "severityMapping": Array [], - "threat": Array [], - "timelineId": undefined, - "timelineTitle": undefined, - "timestampOverride": undefined, - "to": "now", - "type": "query", - "version": 1, + ); + }); + + describe('when duplicating a prebuilt (immutable) rule', () => { + const createPrebuiltRule = () => { + const rule = createTestRule(); + rule.params.immutable = true; + return rule; + }; + + it('transforms it to a custom (mutable) rule', () => { + const rule = createPrebuiltRule(); + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + immutable: false, + }), + }) + ); + }); + + it('resets related integrations to an empty array', () => { + const rule = createPrebuiltRule(); + rule.params.relatedIntegrations = [ + { + package: 'aws', + version: '~1.2.3', + integration: 'route53', }, - "schedule": Object { - "interval": "5m", + ]; + + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + relatedIntegrations: [], + }), + }) + ); + }); + + it('resets required fields to an empty array', () => { + const rule = createPrebuiltRule(); + rule.params.requiredFields = [ + { + name: 'event.action', + type: 'keyword', + ecs: true, }, - "tags": Array [ - "test", - ], - "throttle": null, - } - `); + ]; + + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + requiredFields: [], + }), + }) + ); + }); + + it('resets setup guide to an empty string', () => { + const rule = createPrebuiltRule(); + rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`; + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + setup: '', + }), + }) + ); + }); + }); + + describe('when duplicating a custom (mutable) rule', () => { + const createCustomRule = () => { + const rule = createTestRule(); + rule.params.immutable = false; + return rule; + }; + + it('keeps it custom', () => { + const rule = createCustomRule(); + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + immutable: false, + }), + }) + ); + }); + + it('copies related integrations as is', () => { + const rule = createCustomRule(); + rule.params.relatedIntegrations = [ + { + package: 'aws', + version: '~1.2.3', + integration: 'route53', + }, + ]; + + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + relatedIntegrations: rule.params.relatedIntegrations, + }), + }) + ); + }); + + it('copies required fields as is', () => { + const rule = createCustomRule(); + rule.params.requiredFields = [ + { + name: 'event.action', + type: 'keyword', + ecs: true, + }, + ]; + + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + requiredFields: rule.params.requiredFields, + }), + }) + ); + }); + + it('copies setup guide as is', () => { + const rule = createCustomRule(); + rule.params.setup = `## Config\n\nThe 'Audit Detailed File Share' audit policy must be configured...`; + const result = duplicateRule(rule); + + expect(result).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ + setup: rule.params.setup, + }), + }) + ); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts index 4ef21d0450517..81af1533498ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts @@ -22,7 +22,16 @@ const DUPLICATE_TITLE = i18n.translate( ); export const duplicateRule = (rule: SanitizedRule): InternalRuleCreate => { - const newRuleId = uuid.v4(); + // Generate a new static ruleId + const ruleId = uuid.v4(); + + // If it's a prebuilt rule, reset Related Integrations, Required Fields and Setup Guide. + // We do this because for now we don't allow the users to edit these fields for custom rules. + const isPrebuilt = rule.params.immutable; + const relatedIntegrations = isPrebuilt ? [] : rule.params.relatedIntegrations; + const requiredFields = isPrebuilt ? [] : rule.params.requiredFields; + const setup = isPrebuilt ? '' : rule.params.setup; + return { name: `${rule.name} [${DUPLICATE_TITLE}]`, tags: rule.tags, @@ -31,7 +40,10 @@ export const duplicateRule = (rule: SanitizedRule): InternalRuleCrea params: { ...rule.params, immutable: false, - ruleId: newRuleId, + ruleId, + relatedIntegrations, + requiredFields, + setup, }, schedule: rule.schedule, enabled: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts index de80a8ba8c26b..68fad65a8ff7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts @@ -85,6 +85,9 @@ describe('getExportAll', () => { name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://example.com', 'https://example.com'], + related_integrations: [], + required_fields: [], + setup: '', timeline_id: 'some-timeline-id', timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index f297f375dda0b..e31c1444cd9fc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -82,6 +82,9 @@ describe('get_export_by_object_ids', () => { name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://example.com', 'https://example.com'], + related_integrations: [], + required_fields: [], + setup: '', timeline_id: 'some-timeline-id', timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, @@ -191,6 +194,9 @@ describe('get_export_by_object_ids', () => { name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://example.com', 'https://example.com'], + related_integrations: [], + required_fields: [], + setup: '', timeline_id: 'some-timeline-id', timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index bffa0bc39eb91..1ef4f14b17b6b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -39,10 +39,13 @@ export const installPrepackagedRules = ( index, interval, max_signals: maxSignals, + related_integrations: relatedIntegrations, + required_fields: requiredFields, risk_score: riskScore, risk_score_mapping: riskScoreMapping, rule_name_override: ruleNameOverride, name, + setup, severity, severity_mapping: severityMapping, tags, @@ -95,10 +98,13 @@ export const installPrepackagedRules = ( index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, name, + setup, severity, severityMapping, tags, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index ad2443b34fa95..e5f87b7cdb2e2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -54,11 +54,14 @@ export const patchRules = async ({ index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, rule, name, + setup, severity, severityMapping, tags, @@ -108,10 +111,13 @@ export const patchRules = async ({ index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, name, + setup, severity, severityMapping, tags, @@ -158,9 +164,12 @@ export const patchRules = async ({ filters, index, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, + setup, severity, severityMapping, threat, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 8b560d0edea0f..eeb0e88e53d47 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -93,6 +93,9 @@ import { RuleNameOverrideOrUndefined, EventCategoryOverrideOrUndefined, NamespaceOrUndefined, + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, } from '../../../../common/detection_engine/schemas/common'; import { PartialFilter } from '../types'; @@ -161,11 +164,14 @@ export interface CreateRulesOptions { interval: Interval; license: LicenseOrUndefined; maxSignals: MaxSignals; + relatedIntegrations: RelatedIntegrationArray | undefined; + requiredFields: RequiredFieldArray | undefined; riskScore: RiskScore; riskScoreMapping: RiskScoreMapping; ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; + setup: SetupGuide | undefined; severity: Severity; severityMapping: SeverityMapping; tags: Tags; @@ -225,11 +231,14 @@ interface PatchRulesFieldsOptions { interval: IntervalOrUndefined; license: LicenseOrUndefined; maxSignals: MaxSignalsOrUndefined; + relatedIntegrations: RelatedIntegrationArray | undefined; + requiredFields: RequiredFieldArray | undefined; riskScore: RiskScoreOrUndefined; riskScoreMapping: RiskScoreMappingOrUndefined; ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndexOrUndefined; name: NameOrUndefined; + setup: SetupGuide | undefined; severity: SeverityOrUndefined; severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index ad35e11d35668..079af5b82d608 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -83,10 +83,13 @@ export const createPromises = ( index, interval, max_signals: maxSignals, + related_integrations: relatedIntegrations, + required_fields: requiredFields, risk_score: riskScore, risk_score_mapping: riskScoreMapping, rule_name_override: ruleNameOverride, name, + setup, severity, severity_mapping: severityMapping, tags, @@ -169,10 +172,13 @@ export const createPromises = ( index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, name, + setup, severity, severityMapping, tags, @@ -220,10 +226,13 @@ export const createPromises = ( index, interval, maxSignals, + relatedIntegrations, + requiredFields, riskScore, riskScoreMapping, ruleNameOverride, name, + setup, severity, severityMapping, tags, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index ba65b76f01c4a..7c981a5481ff9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -54,9 +54,12 @@ export const updateRules = async ({ timelineTitle: ruleUpdate.timeline_title, meta: ruleUpdate.meta, maxSignals: ruleUpdate.max_signals ?? DEFAULT_MAX_SIGNALS, + relatedIntegrations: existingRule.params.relatedIntegrations, + requiredFields: existingRule.params.requiredFields, riskScore: ruleUpdate.risk_score, riskScoreMapping: ruleUpdate.risk_score_mapping ?? [], ruleNameOverride: ruleUpdate.rule_name_override, + setup: existingRule.params.setup, severity: ruleUpdate.severity, severityMapping: ruleUpdate.severity_mapping ?? [], threat: ruleUpdate.threat ?? [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index 0952da3182e01..43ac38f447abc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -127,10 +127,13 @@ describe('utils', () => { index: undefined, interval: undefined, maxSignals: undefined, + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: undefined, riskScoreMapping: undefined, ruleNameOverride: undefined, name: undefined, + setup: undefined, severity: undefined, severityMapping: undefined, tags: undefined, @@ -179,10 +182,13 @@ describe('utils', () => { index: undefined, interval: undefined, maxSignals: undefined, + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: undefined, riskScoreMapping: undefined, ruleNameOverride: undefined, name: undefined, + setup: undefined, severity: undefined, severityMapping: undefined, tags: undefined, @@ -231,10 +237,13 @@ describe('utils', () => { index: undefined, interval: undefined, maxSignals: undefined, + relatedIntegrations: undefined, + requiredFields: undefined, riskScore: undefined, riskScoreMapping: undefined, ruleNameOverride: undefined, name: undefined, + setup: undefined, severity: undefined, severityMapping: undefined, tags: undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index dd25676a758e4..4ac138e1629f3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -56,7 +56,10 @@ import { TimestampOverrideOrUndefined, EventCategoryOverrideOrUndefined, NamespaceOrUndefined, -} from '../../../../common/detection_engine/schemas/common/schemas'; + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, +} from '../../../../common/detection_engine/schemas/common'; import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { @@ -107,11 +110,14 @@ export interface UpdateProperties { index: IndexOrUndefined; interval: IntervalOrUndefined; maxSignals: MaxSignalsOrUndefined; + relatedIntegrations: RelatedIntegrationArray | undefined; + requiredFields: RequiredFieldArray | undefined; riskScore: RiskScoreOrUndefined; riskScoreMapping: RiskScoreMappingOrUndefined; ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndexOrUndefined; name: NameOrUndefined; + setup: SetupGuide | undefined; severity: SeverityOrUndefined; severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index fd80bec1f6ad9..356436058b55c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -161,6 +161,9 @@ export const convertCreateAPIToInternalSchema = ( note: input.note, version: input.version ?? 1, exceptionsList: input.exceptions_list ?? [], + relatedIntegrations: [], + requiredFields: [], + setup: '', ...typeSpecificParams, }, schedule: { interval: input.interval ?? '5m' }, @@ -276,6 +279,9 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { version: params.version, exceptions_list: params.exceptionsList, immutable: params.immutable, + related_integrations: params.relatedIntegrations ?? [], + required_fields: params.requiredFields ?? [], + setup: params.setup ?? '', }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts index edaacf38d7712..9e3fa6a906da9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts @@ -51,6 +51,9 @@ const getBaseRuleParams = (): BaseRuleParams => { threat: getThreatMock(), version: 1, exceptionsList: getListArrayMock(), + relatedIntegrations: [], + requiredFields: [], + setup: '', }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index 47e49e5f9c467..d1776136f6513 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -72,7 +72,10 @@ import { updatedByOrNull, created_at, updated_at, -} from '../../../../common/detection_engine/schemas/common/schemas'; + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, +} from '../../../../common/detection_engine/schemas/common'; import { SERVER_APP_ID } from '../../../../common/constants'; const nonEqlLanguages = t.keyof({ kuery: null, lucene: null }); @@ -105,6 +108,9 @@ export const baseRuleParams = t.exact( references, version, exceptionsList: listArray, + relatedIntegrations: t.union([RelatedIntegrationArray, t.undefined]), + requiredFields: t.union([RequiredFieldArray, t.undefined]), + setup: t.union([SetupGuide, t.undefined]), }) ); export type BaseRuleParams = t.TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index 9213d6c5b278c..03074b9560553 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -157,6 +157,9 @@ export const expectedRule = (): RulesSchema => { timeline_id: 'some-timeline-id', timeline_title: 'some-timeline-title', exceptions_list: getListArrayMock(), + related_integrations: [], + required_fields: [], + setup: '', }; }; @@ -624,6 +627,9 @@ export const sampleSignalHit = (): SignalHit => ({ rule_id: 'query-rule-id', interval: '5m', exceptions_list: getListArrayMock(), + related_integrations: [], + required_fields: [], + setup: '', }, depth: 1, }, @@ -685,6 +691,9 @@ export const sampleThresholdSignalHit = (): SignalHit => ({ rule_id: 'query-rule-id', interval: '5m', exceptions_list: getListArrayMock(), + related_integrations: [], + required_fields: [], + setup: '', }, depth: 1, }, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index 7d32af43d1913..aff63d635c976 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -89,6 +89,9 @@ export default ({ getService }: FtrProviderContext) => { name: 'Simple Rule Query', query: 'user.name: root or user.name: admin', references: [], + related_integrations: [], + required_fields: [], + setup: '', severity: 'high', severity_mapping: [], updated_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index 1b7e22fb21c57..966420c90b8d2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -171,6 +171,9 @@ export default ({ getService }: FtrProviderContext) => { name: 'Simple Rule Query', query: 'user.name: root or user.name: admin', references: [], + related_integrations: [], + required_fields: [], + setup: '', severity: 'high', severity_mapping: [], updated_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts index 865185387c57c..5382ba5fd18f4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts @@ -353,6 +353,9 @@ export default ({ getService }: FtrProviderContext) => { language: 'kuery', index: ['.siem-signals-*'], query: '*:*', + related_integrations: [], + required_fields: [], + setup: '', }, 'kibana.alert.rule.actions': [], 'kibana.alert.rule.created_by': 'elastic', @@ -518,6 +521,9 @@ export default ({ getService }: FtrProviderContext) => { language: 'kuery', index: ['.alerts-security.alerts-default'], query: '*:*', + related_integrations: [], + required_fields: [], + setup: '', }, 'kibana.alert.rule.actions': [], 'kibana.alert.rule.created_by': 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index 98fdfa99cbd3c..81a169636605b 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -97,4 +97,7 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => version: 1, query: 'user.name: root or user.name: admin', exceptions_list: [], + related_integrations: [], + required_fields: [], + setup: '', }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 30dc7eecb9256..ca8b04e66f3fc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -26,11 +26,14 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial language: 'kuery', output_index: '.siem-signals-default', max_signals: 100, + related_integrations: [], + required_fields: [], risk_score: 1, risk_score_mapping: [], name: 'Simple Rule Query', query: 'user.name: root or user.name: admin', references: [], + setup: '', severity: 'high', severity_mapping: [], updated_by: 'elastic',