Skip to content

Commit fb1eeb0

Browse files
authored
[Security Solution][Detections] Add new fields to the rule model: Related Integrations, Required Fields, and Setup (elastic#132409)
**Addresses partially:** elastic/security-team#2083, elastic/security-team#558, elastic/security-team#2856, elastic/security-team#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<typeof RelatedIntegration>; 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<typeof RequiredField>; 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<typeof SetupGuide>; 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 elastic#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
1 parent 788dd2e commit fb1eeb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+660
-119
lines changed

x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
*/
77

88
export * from './rule_monitoring';
9+
export * from './rule_params';
910
export * from './schemas';
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import * as t from 'io-ts';
9+
import { NonEmptyString } from '@kbn/securitysolution-io-ts-types';
10+
11+
// -------------------------------------------------------------------------------------------------
12+
// Related integrations
13+
14+
/**
15+
* Related integration is a potential dependency of a rule. It's assumed that if the user installs
16+
* one of the related integrations of a rule, the rule might start to work properly because it will
17+
* have source events (generated by this integration) potentially matching the rule's query.
18+
*
19+
* NOTE: Proper work is not guaranteed, because a related integration, if installed, can be
20+
* configured differently or generate data that is not necessarily relevant for this rule.
21+
*
22+
* Related integration is a combination of a Fleet package and (optionally) one of the
23+
* package's "integrations" that this package contains. It is represented by 3 properties:
24+
*
25+
* - `package`: name of the package (required, unique id)
26+
* - `version`: version of the package (required, semver-compatible)
27+
* - `integration`: name of the integration of this package (optional, id within the package)
28+
*
29+
* There are Fleet packages like `windows` that contain only one integration; in this case,
30+
* `integration` should be unspecified. There are also packages like `aws` and `azure` that contain
31+
* several integrations; in this case, `integration` should be specified.
32+
*
33+
* @example
34+
* const x: RelatedIntegration = {
35+
* package: 'windows',
36+
* version: '1.5.x',
37+
* };
38+
*
39+
* @example
40+
* const x: RelatedIntegration = {
41+
* package: 'azure',
42+
* version: '~1.1.6',
43+
* integration: 'activitylogs',
44+
* };
45+
*/
46+
export type RelatedIntegration = t.TypeOf<typeof RelatedIntegration>;
47+
export const RelatedIntegration = t.exact(
48+
t.intersection([
49+
t.type({
50+
package: NonEmptyString,
51+
version: NonEmptyString,
52+
}),
53+
t.partial({
54+
integration: NonEmptyString,
55+
}),
56+
])
57+
);
58+
59+
/**
60+
* Array of related integrations.
61+
*
62+
* @example
63+
* const x: RelatedIntegrationArray = [
64+
* {
65+
* package: 'windows',
66+
* version: '1.5.x',
67+
* },
68+
* {
69+
* package: 'azure',
70+
* version: '~1.1.6',
71+
* integration: 'activitylogs',
72+
* },
73+
* ];
74+
*/
75+
export type RelatedIntegrationArray = t.TypeOf<typeof RelatedIntegrationArray>;
76+
export const RelatedIntegrationArray = t.array(RelatedIntegration);
77+
78+
// -------------------------------------------------------------------------------------------------
79+
// Required fields
80+
81+
/**
82+
* Almost all types of Security rules check source event documents for a match to some kind of
83+
* query or filter. If a document has certain field with certain values, then it's a match and
84+
* the rule will generate an alert.
85+
*
86+
* Required field is an event field that must be present in the source indices of a given rule.
87+
*
88+
* @example
89+
* const standardEcsField: RequiredField = {
90+
* name: 'event.action',
91+
* type: 'keyword',
92+
* ecs: true,
93+
* };
94+
*
95+
* @example
96+
* const nonEcsField: RequiredField = {
97+
* name: 'winlog.event_data.AttributeLDAPDisplayName',
98+
* type: 'keyword',
99+
* ecs: false,
100+
* };
101+
*/
102+
export type RequiredField = t.TypeOf<typeof RequiredField>;
103+
export const RequiredField = t.exact(
104+
t.type({
105+
name: NonEmptyString,
106+
type: NonEmptyString,
107+
ecs: t.boolean,
108+
})
109+
);
110+
111+
/**
112+
* Array of event fields that must be present in the source indices of a given rule.
113+
*
114+
* @example
115+
* const x: RequiredFieldArray = [
116+
* {
117+
* name: 'event.action',
118+
* type: 'keyword',
119+
* ecs: true,
120+
* },
121+
* {
122+
* name: 'event.code',
123+
* type: 'keyword',
124+
* ecs: true,
125+
* },
126+
* {
127+
* name: 'winlog.event_data.AttributeLDAPDisplayName',
128+
* type: 'keyword',
129+
* ecs: false,
130+
* },
131+
* ];
132+
*/
133+
export type RequiredFieldArray = t.TypeOf<typeof RequiredFieldArray>;
134+
export const RequiredFieldArray = t.array(RequiredField);
135+
136+
// -------------------------------------------------------------------------------------------------
137+
// Setup guide
138+
139+
/**
140+
* Any instructions for the user for setting up their environment in order to start receiving
141+
* source events for a given rule.
142+
*
143+
* It's a multiline text. Markdown is supported.
144+
*/
145+
export type SetupGuide = t.TypeOf<typeof SetupGuide>;
146+
export const SetupGuide = t.string;

x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ import {
7272
Author,
7373
event_category_override,
7474
namespace,
75-
} from '../common/schemas';
75+
RelatedIntegrationArray,
76+
RequiredFieldArray,
77+
SetupGuide,
78+
} from '../common';
7679

7780
/**
7881
* Big differences between this schema and the createRulesSchema
@@ -117,8 +120,11 @@ export const addPrepackagedRulesSchema = t.intersection([
117120
meta, // defaults to "undefined" if not set during decode
118121
machine_learning_job_id, // defaults to "undefined" if not set during decode
119122
max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode
123+
related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode
124+
required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode
120125
risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode
121126
rule_name_override, // defaults to "undefined" if not set during decode
127+
setup: SetupGuide, // defaults to "undefined" if not set during decode
122128
severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode
123129
tags: DefaultStringArray, // defaults to empty string array if not set during decode
124130
to: DefaultToString, // defaults to "now" if not set during decode

x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ import {
8080
timestamp_override,
8181
Author,
8282
event_category_override,
83-
} from '../common/schemas';
83+
RelatedIntegrationArray,
84+
RequiredFieldArray,
85+
SetupGuide,
86+
} from '../common';
8487

8588
/**
8689
* Differences from this and the createRulesSchema are
@@ -129,8 +132,11 @@ export const importRulesSchema = t.intersection([
129132
meta, // defaults to "undefined" if not set during decode
130133
machine_learning_job_id, // defaults to "undefined" if not set during decode
131134
max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode
135+
related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode
136+
required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode
132137
risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode
133138
rule_name_override, // defaults to "undefined" if not set during decode
139+
setup: SetupGuide, // defaults to "undefined" if not set during decode
134140
severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode
135141
tags: DefaultStringArray, // defaults to empty string array if not set during decode
136142
to: DefaultToString, // defaults to "now" if not set during decode

x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import {
6161
rule_name_override,
6262
timestamp_override,
6363
event_category_override,
64-
} from '../common/schemas';
64+
} from '../common';
6565

6666
/**
6767
* All of the patch elements should default to undefined if not set

x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ import {
6767
created_by,
6868
namespace,
6969
ruleExecutionSummary,
70+
RelatedIntegrationArray,
71+
RequiredFieldArray,
72+
SetupGuide,
7073
} from '../common';
7174

7275
export const createSchema = <
@@ -412,6 +415,14 @@ const responseRequiredFields = {
412415
updated_by,
413416
created_at,
414417
created_by,
418+
419+
// NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt
420+
// rules only. We don't want to allow users to edit these 3 fields via the API. If we added them
421+
// to baseParams.defaultable, they would become a part of the request schema as optional fields.
422+
// This is why we add them here, in order to add them only to the response schema.
423+
related_integrations: RelatedIntegrationArray,
424+
required_fields: RequiredFieldArray,
425+
setup: SetupGuide,
415426
};
416427

417428
const responseOptionalFields = {

x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem
6868
rule_id: 'query-rule-id',
6969
interval: '5m',
7070
exceptions_list: getListArrayMock(),
71+
related_integrations: [],
72+
required_fields: [],
73+
setup: '',
7174
});
7275

7376
export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => {
@@ -132,6 +135,9 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial<Rul
132135
risk_score_mapping: [],
133136
name: 'Query with a rule id',
134137
references: [],
138+
related_integrations: [],
139+
required_fields: [],
140+
setup: '',
135141
severity: 'high',
136142
severity_mapping: [],
137143
updated_by: 'elastic',

x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ import {
7474
timestamp_override,
7575
namespace,
7676
ruleExecutionSummary,
77+
RelatedIntegrationArray,
78+
RequiredFieldArray,
79+
SetupGuide,
7780
} from '../common';
7881

7982
import { typeAndTimelineOnlySchema, TypeAndTimelineOnly } from './type_timeline_only_schema';
@@ -111,6 +114,9 @@ export const requiredRulesSchema = t.type({
111114
created_by,
112115
version,
113116
exceptions_list: DefaultListArray,
117+
related_integrations: RelatedIntegrationArray,
118+
required_fields: RequiredFieldArray,
119+
setup: SetupGuide,
114120
});
115121

116122
export type RequiredRulesSchema = t.TypeOf<typeof requiredRulesSchema>;

x-pack/plugins/security_solution/cypress/objects/rule.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,9 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>
442442
severity,
443443
query,
444444
} = ruleResponse.body;
445-
const rule = {
445+
446+
// NOTE: Order of the properties in this object matters for the tests to work.
447+
const rule: RulesSchema = {
446448
id,
447449
updated_at: updatedAt,
448450
updated_by: updatedBy,
@@ -469,13 +471,18 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>
469471
version: 1,
470472
exceptions_list: [],
471473
immutable: false,
474+
related_integrations: [],
475+
required_fields: [],
476+
setup: '',
472477
type: 'query',
473478
language: 'kuery',
474479
index: getIndexPatterns(),
475480
query,
476481
throttle: 'no_actions',
477482
actions: [],
478483
};
484+
485+
// NOTE: Order of the properties in this object matters for the tests to work.
479486
const details = {
480487
exported_count: 1,
481488
exported_rules_count: 1,

x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export const savedRuleMock: Rule = {
3838
max_signals: 100,
3939
query: "user.email: '[email protected]'",
4040
references: [],
41+
related_integrations: [],
42+
required_fields: [],
43+
setup: '',
4144
severity: 'high',
4245
severity_mapping: [],
4346
tags: ['APM'],
@@ -80,6 +83,9 @@ export const rulesMock: FetchRulesResponse = {
8083
'event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection',
8184
filters: [],
8285
references: [],
86+
related_integrations: [],
87+
required_fields: [],
88+
setup: '',
8389
severity: 'high',
8490
severity_mapping: [],
8591
updated_by: 'elastic',
@@ -115,6 +121,9 @@ export const rulesMock: FetchRulesResponse = {
115121
query: 'event.kind:alert and event.module:endgame and event.action:rules_engine_event',
116122
filters: [],
117123
references: [],
124+
related_integrations: [],
125+
required_fields: [],
126+
setup: '',
118127
severity: 'medium',
119128
severity_mapping: [],
120129
updated_by: 'elastic',

x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import {
3434
BulkAction,
3535
BulkActionEditPayload,
3636
ruleExecutionSummary,
37+
RelatedIntegrationArray,
38+
RequiredFieldArray,
39+
SetupGuide,
3740
} from '../../../../../common/detection_engine/schemas/common';
3841

3942
import {
@@ -102,11 +105,14 @@ export const RuleSchema = t.intersection([
102105
name: t.string,
103106
max_signals: t.number,
104107
references: t.array(t.string),
108+
related_integrations: RelatedIntegrationArray,
109+
required_fields: RequiredFieldArray,
105110
risk_score: t.number,
106111
risk_score_mapping,
107112
rule_id: t.string,
108113
severity,
109114
severity_mapping,
115+
setup: SetupGuide,
110116
tags: t.array(t.string),
111117
type,
112118
to: t.string,

x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,12 @@ describe('useRule', () => {
6767
max_signals: 100,
6868
query: "user.email: '[email protected]'",
6969
references: [],
70+
related_integrations: [],
71+
required_fields: [],
7072
risk_score: 75,
7173
risk_score_mapping: [],
7274
rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf',
75+
setup: '',
7376
severity: 'high',
7477
severity_mapping: [],
7578
tags: ['APM'],

x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,12 @@ describe('useRuleWithFallback', () => {
7878
"name": "Test rule",
7979
"query": "user.email: '[email protected]'",
8080
"references": Array [],
81+
"related_integrations": Array [],
82+
"required_fields": Array [],
8183
"risk_score": 75,
8284
"risk_score_mapping": Array [],
8385
"rule_id": "bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf",
86+
"setup": "",
8487
"severity": "high",
8588
"severity_mapping": Array [],
8689
"tags": Array [

0 commit comments

Comments
 (0)