From c9f586797ed7eb1176dc37e17c99ccdfe350d5d7 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Wed, 1 Jul 2020 22:49:30 -0600 Subject: [PATCH] [Security] Adds field mapping support to rule creation (#70288) ## Summary Resolves: https://github.com/elastic/kibana/issues/65941, https://github.com/elastic/kibana/issues/66317, and `Add support for "building block" alerts` This PR is `Part I` and adds additional fields to the `rules schema` in supporting the ability to map and override fields when generating alerts. A few bookkeeping fields like `license` and `author` have been added as well. The new fields are as follows: ``` ts export interface TheseAreTheNewFields { author: string[]; building_block_type: string; // 'default' license: string; risk_score_mapping: Array< { field: string; operator: string; // 'equals' value: string; } >; rule_name_override: string; severity_mapping: Array< { field: string; operator: string; // 'equals' value: string; severity: string; // 'low' | 'medium' | 'high' | 'critical' } >; timestamp_override: string; } ``` These new fields are exposed as additional settings on the `About rule` section of the Rule Creation UI. ##### Default collapsed view, no severity or risk score override specified:

##### Severity & risk score override specified:

##### Additional fields in Advanced settings:

Note: This PR adds the fields to the `Rules Schema`, the `signals index mapping`, and creates the UI for adding these fields during Rule Creation/Editing. The follow-up `Part II` will add the business logic for mapping fields during `rule execution`, and also add UI validation/additional tests. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - Syncing w/ @benskelker - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [x] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../schemas/common/schemas.ts | 71 ++++++ .../add_prepackaged_rules_schema.mock.ts | 3 + .../request/add_prepackaged_rules_schema.ts | 22 ++ .../add_prepackged_rules_schema.test.ts | 24 ++ .../request/create_rules_schema.mock.ts | 3 + .../request/create_rules_schema.test.ts | 36 +++ .../schemas/request/create_rules_schema.ts | 22 ++ .../request/import_rules_schema.mock.ts | 3 + .../request/import_rules_schema.test.ts | 30 +++ .../schemas/request/import_rules_schema.ts | 22 ++ .../schemas/request/patch_rules_schema.ts | 14 ++ .../request/update_rules_schema.mock.ts | 3 + .../request/update_rules_schema.test.ts | 30 +++ .../schemas/request/update_rules_schema.ts | 22 ++ .../schemas/response/rules_schema.mocks.ts | 3 + .../schemas/response/rules_schema.ts | 16 ++ .../types/default_risk_score_mapping_array.ts | 24 ++ .../types/default_severity_mapping_array.ts | 24 ++ .../detection_engine/schemas/types/index.ts | 2 + .../rules/description_step/index.test.tsx | 2 +- .../rules/description_step/index.tsx | 19 +- .../rules/risk_score_mapping/index.tsx | 190 ++++++++++++++++ .../rules/risk_score_mapping/translations.tsx | 57 +++++ .../rules/severity_mapping/index.tsx | 214 ++++++++++++++++++ .../rules/severity_mapping/translations.tsx | 57 +++++ .../components/rules/step_about_rule/data.tsx | 2 +- .../rules/step_about_rule/default_value.ts | 9 +- .../rules/step_about_rule/index.test.tsx | 18 +- .../rules/step_about_rule/index.tsx | 184 ++++++++++----- .../rules/step_about_rule/schema.tsx | 123 ++++++++-- .../detection_engine/rules/api.test.ts | 2 +- .../containers/detection_engine/rules/mock.ts | 9 + .../detection_engine/rules/types.ts | 18 ++ .../detection_engine/rules/use_rule.test.tsx | 3 + .../rules/use_rule_status.test.tsx | 3 + .../detection_engine/rules/use_rules.test.tsx | 6 + .../rules/all/__mocks__/mock.ts | 19 +- .../rules/create/helpers.test.ts | 24 ++ .../detection_engine/rules/create/helpers.ts | 27 ++- .../detection_engine/rules/create/index.tsx | 3 + .../detection_engine/rules/helpers.test.tsx | 9 +- .../pages/detection_engine/rules/helpers.tsx | 22 +- .../pages/detection_engine/rules/types.ts | 35 ++- .../routes/__mocks__/request_responses.ts | 7 + .../routes/__mocks__/utils.ts | 4 + .../routes/index/signals_mapping.json | 44 ++++ .../rules/add_prepackaged_rules_route.test.ts | 3 + .../routes/rules/create_rules_bulk_route.ts | 16 +- .../routes/rules/create_rules_route.ts | 16 +- .../routes/rules/import_rules_route.ts | 23 +- .../routes/rules/patch_rules_bulk_route.ts | 14 ++ .../routes/rules/patch_rules_route.ts | 14 ++ .../routes/rules/update_rules_bulk_route.ts | 14 ++ .../routes/rules/update_rules_route.ts | 14 ++ .../detection_engine/routes/rules/utils.ts | 7 + .../routes/rules/validate.test.ts | 4 + .../rules/create_rules.mock.ts | 14 ++ .../detection_engine/rules/create_rules.ts | 14 ++ .../create_rules_stream_from_ndjson.test.ts | 30 +++ .../rules/get_export_all.test.ts | 4 + .../rules/get_export_by_object_ids.test.ts | 8 + .../rules/install_prepacked_rules.ts | 14 ++ .../rules/patch_rules.mock.ts | 14 ++ .../lib/detection_engine/rules/patch_rules.ts | 21 ++ .../lib/detection_engine/rules/types.ts | 31 +++ .../rules/update_prepacked_rules.ts | 14 ++ .../rules/update_rules.mock.ts | 14 ++ .../detection_engine/rules/update_rules.ts | 21 ++ .../lib/detection_engine/rules/utils.test.ts | 21 ++ .../lib/detection_engine/rules/utils.ts | 14 ++ .../lib/detection_engine/scripts/post_rule.sh | 2 +- .../rules/queries/query_with_mappings.json | 44 ++++ .../signals/__mocks__/es_results.ts | 7 + .../signals/build_bulk_body.test.ts | 20 ++ .../signals/build_rule.test.ts | 15 ++ .../detection_engine/signals/build_rule.ts | 13 +- .../signals/signal_params_schema.mock.ts | 7 + .../signals/signal_params_schema.ts | 8 + .../server/lib/detection_engine/types.ts | 14 ++ .../detection_engine_api_integration/utils.ts | 9 + 80 files changed, 1868 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts create mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts create mode 100644 x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/index.tsx create mode 100644 x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/translations.tsx create mode 100644 x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/index.tsx create mode 100644 x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/translations.tsx create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index f6b732cd1f64e..6e43bd645fd7b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -13,6 +13,18 @@ import { IsoDateString } from '../types/iso_date_string'; import { PositiveIntegerGreaterThanZero } from '../types/positive_integer_greater_than_zero'; import { PositiveInteger } from '../types/positive_integer'; +export const author = t.array(t.string); +export type Author = t.TypeOf; + +export const authorOrUndefined = t.union([author, t.undefined]); +export type AuthorOrUndefined = t.TypeOf; + +export const building_block_type = t.string; +export type BuildingBlockType = t.TypeOf; + +export const buildingBlockTypeOrUndefined = t.union([building_block_type, t.undefined]); +export type BuildingBlockTypeOrUndefined = t.TypeOf; + export const description = t.string; export type Description = t.TypeOf; @@ -111,6 +123,12 @@ export type Language = t.TypeOf; export const languageOrUndefined = t.union([language, t.undefined]); export type LanguageOrUndefined = t.TypeOf; +export const license = t.string; +export type License = t.TypeOf; + +export const licenseOrUndefined = t.union([license, t.undefined]); +export type LicenseOrUndefined = t.TypeOf; + export const objects = t.array(t.type({ rule_id })); export const output_index = t.string; @@ -137,6 +155,12 @@ export type TimelineTitle = t.TypeOf; export const timelineTitleOrUndefined = t.union([timeline_title, t.undefined]); export type TimelineTitleOrUndefined = t.TypeOf; +export const timestamp_override = t.string; +export type TimestampOverride = t.TypeOf; + +export const timestampOverrideOrUndefined = t.union([timestamp_override, t.undefined]); +export type TimestampOverrideOrUndefined = t.TypeOf; + export const throttle = t.string; export type Throttle = t.TypeOf; @@ -179,18 +203,65 @@ export type Name = t.TypeOf; export const nameOrUndefined = t.union([name, t.undefined]); export type NameOrUndefined = t.TypeOf; +export const operator = t.keyof({ + equals: null, +}); +export type Operator = t.TypeOf; +export enum OperatorEnum { + EQUALS = 'equals', +} + export const risk_score = RiskScore; export type RiskScore = t.TypeOf; export const riskScoreOrUndefined = t.union([risk_score, t.undefined]); export type RiskScoreOrUndefined = t.TypeOf; +export const risk_score_mapping_field = t.string; +export const risk_score_mapping_value = t.string; +export const risk_score_mapping_item = t.exact( + t.type({ + field: risk_score_mapping_field, + operator, + value: risk_score_mapping_value, + }) +); + +export const risk_score_mapping = t.array(risk_score_mapping_item); +export type RiskScoreMapping = t.TypeOf; + +export const riskScoreMappingOrUndefined = t.union([risk_score_mapping, t.undefined]); +export type RiskScoreMappingOrUndefined = t.TypeOf; + +export const rule_name_override = t.string; +export type RuleNameOverride = t.TypeOf; + +export const ruleNameOverrideOrUndefined = t.union([rule_name_override, t.undefined]); +export type RuleNameOverrideOrUndefined = t.TypeOf; + export const severity = t.keyof({ low: null, medium: null, high: null, critical: null }); export type Severity = t.TypeOf; export const severityOrUndefined = t.union([severity, t.undefined]); export type SeverityOrUndefined = t.TypeOf; +export const severity_mapping_field = t.string; +export const severity_mapping_value = t.string; +export const severity_mapping_item = t.exact( + t.type({ + field: severity_mapping_field, + operator, + value: severity_mapping_value, + severity, + }) +); + +export const severity_mapping = t.array(severity_mapping_item); +export type SeverityMapping = t.TypeOf; + +export const severityMappingOrUndefined = t.union([severity_mapping, t.undefined]); +export type SeverityMappingOrUndefined = t.TypeOf; + export const status = t.keyof({ open: null, closed: null, 'in-progress': null }); export type Status = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts index 52a210f3a01aa..b666b95ea1e97 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts @@ -23,12 +23,15 @@ export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => }); export const getAddPrepackagedRulesSchemaDecodedMock = (): AddPrepackagedRulesSchemaDecoded => ({ + author: [], description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', + severity_mapping: [], type: 'query', risk_score: 55, + risk_score_mapping: [], language: 'kuery', references: [], actions: [], 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 43000f6d36f46..bf96be5e688fa 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 @@ -37,6 +37,13 @@ import { query, rule_id, version, + building_block_type, + license, + rule_name_override, + timestamp_override, + Author, + RiskScoreMapping, + SeverityMapping, } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ @@ -52,6 +59,8 @@ import { DefaultThrottleNull, DefaultListArray, ListArray, + DefaultRiskScoreMappingArray, + DefaultSeverityMappingArray, } from '../types'; /** @@ -79,6 +88,8 @@ export const addPrepackagedRulesSchema = t.intersection([ t.partial({ actions: DefaultActionsArray, // defaults to empty actions array if not set during decode anomaly_threshold, // defaults to undefined if not set during decode + author: DefaultStringArray, // defaults to empty array of strings if not set during decode + building_block_type, // defaults to undefined if not set during decode enabled: DefaultBooleanFalse, // defaults to false if not set during decode false_positives: DefaultStringArray, // defaults to empty string array if not set during decode filters, // defaults to undefined if not set during decode @@ -87,16 +98,21 @@ export const addPrepackagedRulesSchema = t.intersection([ interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode language, // defaults to undefined if not set during decode + license, // defaults to "undefined" if not set during decode saved_id, // defaults to "undefined" if not set during decode timeline_id, // defaults to "undefined" if not set during decode timeline_title, // defaults to "undefined" if not set during decode 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 + 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 + 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 threat: DefaultThreatArray, // defaults to empty array if not set during decode throttle: DefaultThrottleNull, // defaults to "null" if not set during decode + timestamp_override, // defaults to "undefined" if not set during decode references: DefaultStringArray, // defaults to empty array of strings if not set during decode note, // defaults to "undefined" if not set during decode exceptions_list: DefaultListArray, // defaults to empty array if not set during decode @@ -109,6 +125,7 @@ export type AddPrepackagedRulesSchema = t.TypeOf & { + author: Author; references: References; actions: Actions; enabled: Enabled; @@ -129,6 +149,8 @@ export type AddPrepackagedRulesSchemaDecoded = Omit< from: From; interval: Interval; max_signals: MaxSignals; + risk_score_mapping: RiskScoreMapping; + severity_mapping: SeverityMapping; tags: Tags; to: To; threat: Threat; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackged_rules_schema.test.ts index 47a98166927b4..0c45a7b1ef6bb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackged_rules_schema.test.ts @@ -261,6 +261,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -333,6 +336,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -430,6 +436,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -508,6 +517,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1354,6 +1366,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1404,6 +1419,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1462,6 +1480,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1539,6 +1560,9 @@ describe('add prepackaged rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: AddPrepackagedRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts index 2847bd32df514..f1e87bdb11e75 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts @@ -30,6 +30,9 @@ export const getCreateMlRulesSchemaMock = (ruleId = 'rule-1') => { }; export const getCreateRulesSchemaDecodedMock = (): CreateRulesSchemaDecoded => ({ + author: [], + severity_mapping: [], + risk_score_mapping: [], description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts index 1648044f5305a..e529cf3fa555c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.test.ts @@ -248,6 +248,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -318,6 +321,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -366,6 +372,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -412,6 +421,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -438,6 +450,9 @@ describe('create rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { const payload: CreateRulesSchema = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -456,6 +471,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -535,6 +553,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1228,6 +1249,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1399,6 +1423,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], type: 'machine_learning', anomaly_threshold: 50, machine_learning_job_id: 'linux_anomalous_network_activity_ecs', @@ -1459,6 +1486,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1516,6 +1546,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1591,6 +1624,9 @@ describe('create rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: CreateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts index d623cff8f1fc3..0debe01e5a4d7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { description, anomaly_threshold, + building_block_type, filters, RuleId, index, @@ -38,6 +39,12 @@ import { Interval, language, query, + license, + rule_name_override, + timestamp_override, + Author, + RiskScoreMapping, + SeverityMapping, } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ @@ -55,6 +62,8 @@ import { DefaultListArray, ListArray, DefaultUuid, + DefaultRiskScoreMappingArray, + DefaultSeverityMappingArray, } from '../types'; export const createRulesSchema = t.intersection([ @@ -71,6 +80,8 @@ export const createRulesSchema = t.intersection([ t.partial({ actions: DefaultActionsArray, // defaults to empty actions array if not set during decode anomaly_threshold, // defaults to undefined if not set during decode + author: DefaultStringArray, // defaults to empty array of strings if not set during decode + building_block_type, // defaults to undefined if not set during decode enabled: DefaultBooleanTrue, // defaults to true if not set during decode false_positives: DefaultStringArray, // defaults to empty string array if not set during decode filters, // defaults to undefined if not set during decode @@ -80,6 +91,7 @@ export const createRulesSchema = t.intersection([ interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode language, // defaults to undefined if not set during decode + license, // defaults to "undefined" if not set during decode // TODO: output_index: This should be removed eventually output_index, // defaults to "undefined" if not set during decode saved_id, // defaults to "undefined" if not set during decode @@ -88,10 +100,14 @@ export const createRulesSchema = 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 + 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 + 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 threat: DefaultThreatArray, // defaults to empty array if not set during decode throttle: DefaultThrottleNull, // defaults to "null" if not set during decode + timestamp_override, // defaults to "undefined" if not set during decode references: DefaultStringArray, // defaults to empty array of strings if not set during decode note, // defaults to "undefined" if not set during decode version: DefaultVersionNumber, // defaults to 1 if not set during decode @@ -105,6 +121,7 @@ export type CreateRulesSchema = t.TypeOf; // This type is used after a decode since some things are defaults after a decode. export type CreateRulesSchemaDecoded = Omit< CreateRulesSchema, + | 'author' | 'references' | 'actions' | 'enabled' @@ -112,6 +129,8 @@ export type CreateRulesSchemaDecoded = Omit< | 'from' | 'interval' | 'max_signals' + | 'risk_score_mapping' + | 'severity_mapping' | 'tags' | 'to' | 'threat' @@ -120,6 +139,7 @@ export type CreateRulesSchemaDecoded = Omit< | 'exceptions_list' | 'rule_id' > & { + author: Author; references: References; actions: Actions; enabled: Enabled; @@ -127,6 +147,8 @@ export type CreateRulesSchemaDecoded = Omit< from: From; interval: Interval; max_signals: MaxSignals; + risk_score_mapping: RiskScoreMapping; + severity_mapping: SeverityMapping; tags: Tags; to: To; threat: Threat; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts index aaeb90ffc5bcf..e3b4196c90c6c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts @@ -31,12 +31,15 @@ export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSc }); export const getImportRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ({ + author: [], description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', + severity_mapping: [], type: 'query', risk_score: 55, + risk_score_mapping: [], language: 'kuery', references: [], actions: [], diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts index 12a13ab1a5ed1..bbf0a8debd651 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts @@ -253,6 +253,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -324,6 +327,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -373,6 +379,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -420,6 +429,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -465,6 +477,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -545,6 +560,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1543,6 +1561,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1593,6 +1614,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1651,6 +1675,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1727,6 +1754,9 @@ describe('import rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: ImportRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', 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 7d79861aacf38..f61a1546e3e8a 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 @@ -44,6 +44,13 @@ import { updated_at, created_by, updated_by, + building_block_type, + license, + rule_name_override, + timestamp_override, + Author, + RiskScoreMapping, + SeverityMapping, } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ @@ -62,6 +69,8 @@ import { DefaultStringBooleanFalse, DefaultListArray, ListArray, + DefaultRiskScoreMappingArray, + DefaultSeverityMappingArray, } from '../types'; /** @@ -90,6 +99,8 @@ export const importRulesSchema = t.intersection([ id, // defaults to undefined if not set during decode actions: DefaultActionsArray, // defaults to empty actions array if not set during decode anomaly_threshold, // defaults to undefined if not set during decode + author: DefaultStringArray, // defaults to empty array of strings if not set during decode + building_block_type, // defaults to undefined if not set during decode enabled: DefaultBooleanTrue, // defaults to true if not set during decode false_positives: DefaultStringArray, // defaults to empty string array if not set during decode filters, // defaults to undefined if not set during decode @@ -99,6 +110,7 @@ export const importRulesSchema = t.intersection([ interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode language, // defaults to undefined if not set during decode + license, // defaults to "undefined" if not set during decode // TODO: output_index: This should be removed eventually output_index, // defaults to "undefined" if not set during decode saved_id, // defaults to "undefined" if not set during decode @@ -107,10 +119,14 @@ 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 + 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 + 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 threat: DefaultThreatArray, // defaults to empty array if not set during decode throttle: DefaultThrottleNull, // defaults to "null" if not set during decode + timestamp_override, // defaults to "undefined" if not set during decode references: DefaultStringArray, // defaults to empty array of strings if not set during decode note, // defaults to "undefined" if not set during decode version: DefaultVersionNumber, // defaults to 1 if not set during decode @@ -128,6 +144,7 @@ export type ImportRulesSchema = t.TypeOf; // This type is used after a decode since some things are defaults after a decode. export type ImportRulesSchemaDecoded = Omit< ImportRulesSchema, + | 'author' | 'references' | 'actions' | 'enabled' @@ -135,6 +152,8 @@ export type ImportRulesSchemaDecoded = Omit< | 'from' | 'interval' | 'max_signals' + | 'risk_score_mapping' + | 'severity_mapping' | 'tags' | 'to' | 'threat' @@ -144,6 +163,7 @@ export type ImportRulesSchemaDecoded = Omit< | 'rule_id' | 'immutable' > & { + author: Author; references: References; actions: Actions; enabled: Enabled; @@ -151,6 +171,8 @@ export type ImportRulesSchemaDecoded = Omit< from: From; interval: Interval; max_signals: MaxSignals; + risk_score_mapping: RiskScoreMapping; + severity_mapping: SeverityMapping; tags: Tags; to: To; threat: Threat; 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 29d5467071a3d..070f3ccfd03b0 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 @@ -39,6 +39,13 @@ import { language, query, id, + building_block_type, + author, + license, + rule_name_override, + timestamp_override, + risk_score_mapping, + severity_mapping, } from '../common/schemas'; import { listArrayOrUndefined } from '../types/lists'; /* eslint-enable @typescript-eslint/camelcase */ @@ -48,6 +55,8 @@ import { listArrayOrUndefined } from '../types/lists'; */ export const patchRulesSchema = t.exact( t.partial({ + author, + building_block_type, description, risk_score, name, @@ -65,6 +74,7 @@ export const patchRulesSchema = t.exact( interval, query, language, + license, // TODO: output_index: This should be removed eventually output_index, saved_id, @@ -73,10 +83,14 @@ export const patchRulesSchema = t.exact( meta, machine_learning_job_id, max_signals, + risk_score_mapping, + rule_name_override, + severity_mapping, tags, to, threat, throttle, + timestamp_override, references, note, version, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.mock.ts index b8a99115ba7d5..b3fbf96188352 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.mock.ts @@ -19,12 +19,15 @@ export const getUpdateRulesSchemaMock = (): UpdateRulesSchema => ({ }); export const getUpdateRulesSchemaDecodedMock = (): UpdateRulesSchemaDecoded => ({ + author: [], description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', + severity_mapping: [], type: 'query', risk_score: 55, + risk_score_mapping: [], language: 'kuery', references: [], actions: [], diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.test.ts index 02f8e7bbeb59b..c15803eee874e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.test.ts @@ -248,6 +248,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -317,6 +320,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -364,6 +370,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -409,6 +418,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -452,6 +464,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -530,6 +545,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1353,6 +1371,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1401,6 +1422,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1457,6 +1481,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1531,6 +1558,9 @@ describe('update rules schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); const expected: UpdateRulesSchemaDecoded = { + author: [], + severity_mapping: [], + risk_score_mapping: [], rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts index 73078e617efc6..98082c2de838a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema.ts @@ -40,6 +40,13 @@ import { language, query, id, + building_block_type, + license, + rule_name_override, + timestamp_override, + Author, + RiskScoreMapping, + SeverityMapping, } from '../common/schemas'; /* eslint-enable @typescript-eslint/camelcase */ @@ -55,6 +62,8 @@ import { DefaultThrottleNull, DefaultListArray, ListArray, + DefaultRiskScoreMappingArray, + DefaultSeverityMappingArray, } from '../types'; /** @@ -79,6 +88,8 @@ export const updateRulesSchema = t.intersection([ id, // defaults to "undefined" if not set during decode actions: DefaultActionsArray, // defaults to empty actions array if not set during decode anomaly_threshold, // defaults to undefined if not set during decode + author: DefaultStringArray, // defaults to empty array of strings if not set during decode + building_block_type, // defaults to undefined if not set during decode enabled: DefaultBooleanTrue, // defaults to true if not set during decode false_positives: DefaultStringArray, // defaults to empty string array if not set during decode filters, // defaults to undefined if not set during decode @@ -88,6 +99,7 @@ export const updateRulesSchema = t.intersection([ interval: DefaultIntervalString, // defaults to "5m" if not set during decode query, // defaults to undefined if not set during decode language, // defaults to undefined if not set during decode + license, // defaults to "undefined" if not set during decode // TODO: output_index: This should be removed eventually output_index, // defaults to "undefined" if not set during decode saved_id, // defaults to "undefined" if not set during decode @@ -96,10 +108,14 @@ export const updateRulesSchema = 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 + 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 + 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 threat: DefaultThreatArray, // defaults to empty array if not set during decode throttle: DefaultThrottleNull, // defaults to "null" if not set during decode + timestamp_override, // defaults to "undefined" if not set during decode references: DefaultStringArray, // defaults to empty array of strings if not set during decode note, // defaults to "undefined" if not set during decode version, // defaults to "undefined" if not set during decode @@ -113,6 +129,7 @@ export type UpdateRulesSchema = t.TypeOf; // This type is used after a decode since some things are defaults after a decode. export type UpdateRulesSchemaDecoded = Omit< UpdateRulesSchema, + | 'author' | 'references' | 'actions' | 'enabled' @@ -120,6 +137,8 @@ export type UpdateRulesSchemaDecoded = Omit< | 'from' | 'interval' | 'max_signals' + | 'risk_score_mapping' + | 'severity_mapping' | 'tags' | 'to' | 'threat' @@ -127,6 +146,7 @@ export type UpdateRulesSchemaDecoded = Omit< | 'exceptions_list' | 'rule_id' > & { + author: Author; references: References; actions: Actions; enabled: Enabled; @@ -134,6 +154,8 @@ export type UpdateRulesSchemaDecoded = Omit< from: From; interval: Interval; max_signals: MaxSignals; + risk_score_mapping: RiskScoreMapping; + severity_mapping: SeverityMapping; tags: Tags; to: To; threat: Threat; 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 e63a7ad981e12..ed9fb8930ea1b 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 @@ -36,6 +36,7 @@ export const getPartialRulesSchemaMock = (): Partial => ({ }); export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => ({ + author: [], id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), updated_at: new Date(anchorDate).toISOString(), @@ -49,6 +50,7 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem query: 'user.name: root or user.name: admin', references: ['test 1', 'test 2'], severity: 'high', + severity_mapping: [], updated_by: 'elastic_kibana', tags: [], to: 'now', @@ -62,6 +64,7 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem output_index: '.siem-signals-hassanabad-frank-default', max_signals: 100, risk_score: 55, + risk_score_mapping: [], language: 'kuery', rule_id: 'query-rule-id', interval: '5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts index 9803a80f57857..c0fec2b2eefc2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts @@ -55,8 +55,17 @@ import { filters, meta, note, + building_block_type, + license, + rule_name_override, + timestamp_override, } from '../common/schemas'; import { DefaultListArray } from '../types/lists_default_array'; +import { + DefaultStringArray, + DefaultRiskScoreMappingArray, + DefaultSeverityMappingArray, +} from '../types'; /** * This is the required fields for the rules schema response. Put all required properties on @@ -64,6 +73,7 @@ import { DefaultListArray } from '../types/lists_default_array'; * output schema. */ export const requiredRulesSchema = t.type({ + author: DefaultStringArray, description, enabled, false_positives, @@ -75,9 +85,11 @@ export const requiredRulesSchema = t.type({ output_index, max_signals, risk_score, + risk_score_mapping: DefaultRiskScoreMappingArray, name, references, severity, + severity_mapping: DefaultSeverityMappingArray, updated_by, tags, to, @@ -120,9 +132,13 @@ export const dependentRulesSchema = t.partial({ */ export const partialRulesSchema = t.partial({ actions, + building_block_type, + license, throttle, + rule_name_override, status: job_status, status_date, + timestamp_override, last_success_at, last_success_message, last_failure_at, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts new file mode 100644 index 0000000000000..ba74045b4e32c --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_risk_score_mapping_array.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; +// eslint-disable-next-line @typescript-eslint/camelcase +import { risk_score_mapping, RiskScoreMapping } from '../common/schemas'; + +/** + * Types the DefaultStringArray as: + * - If null or undefined, then a default risk_score_mapping array will be set + */ +export const DefaultRiskScoreMappingArray = new t.Type( + 'DefaultRiskScoreMappingArray', + risk_score_mapping.is, + (input, context): Either => + input == null ? t.success([]) : risk_score_mapping.validate(input, context), + t.identity +); + +export type DefaultRiskScoreMappingArrayC = typeof DefaultRiskScoreMappingArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts new file mode 100644 index 0000000000000..8e68b73148af1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/default_severity_mapping_array.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; +// eslint-disable-next-line @typescript-eslint/camelcase +import { severity_mapping, SeverityMapping } from '../common/schemas'; + +/** + * Types the DefaultStringArray as: + * - If null or undefined, then a default severity_mapping array will be set + */ +export const DefaultSeverityMappingArray = new t.Type( + 'DefaultSeverityMappingArray', + severity_mapping.is, + (input, context): Either => + input == null ? t.success([]) : severity_mapping.validate(input, context), + t.identity +); + +export type DefaultSeverityMappingArrayC = typeof DefaultSeverityMappingArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/index.ts index 368dd4922eec4..aab9a550d25e7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/index.ts @@ -15,6 +15,8 @@ export * from './default_language_string'; export * from './default_max_signals_number'; export * from './default_page'; export * from './default_per_page'; +export * from './default_risk_score_mapping_array'; +export * from './default_severity_mapping_array'; export * from './default_string_array'; export * from './default_string_boolean_false'; export * from './default_threat_array'; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx index 2bd90f17daf0c..0a7e666d65aef 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.test.tsx @@ -257,7 +257,7 @@ describe('description_step', () => { test('returns expected ListItems array when given valid inputs', () => { const result: ListItems[] = buildListItems(mockAboutStep, schema, mockFilterManager); - expect(result.length).toEqual(9); + expect(result.length).toEqual(11); }); }); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx index b9642b8761019..8f3a76c6aea57 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/description_step/index.tsx @@ -18,7 +18,11 @@ import { } from '../../../../../../../../src/plugins/data/public'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import { useKibana } from '../../../../common/lib/kibana'; -import { IMitreEnterpriseAttack } from '../../../pages/detection_engine/rules/types'; +import { + AboutStepRiskScore, + AboutStepSeverity, + IMitreEnterpriseAttack, +} from '../../../pages/detection_engine/rules/types'; import { FieldValueTimeline } from '../pick_timeline'; import { FormSchema } from '../../../../shared_imports'; import { ListItems } from './types'; @@ -184,9 +188,18 @@ export const getDescriptionItem = ( } else if (Array.isArray(get(field, data))) { const values: string[] = get(field, data); return buildStringArrayDescription(label, field, values); + // TODO: Add custom UI for Risk/Severity Mappings (and fix missing label) + } else if (field === 'riskScore') { + const val: AboutStepRiskScore = get(field, data); + return [ + { + title: label, + description: val.value, + }, + ]; } else if (field === 'severity') { - const val: string = get(field, data); - return buildSeverityDescription(label, val); + const val: AboutStepSeverity = get(field, data); + return buildSeverityDescription(label, val.value); } else if (field === 'timeline') { const timeline = get(field, data) as FieldValueTimeline; return [ diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/index.tsx new file mode 100644 index 0000000000000..bdf1ac600faef --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/index.tsx @@ -0,0 +1,190 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFormRow, + EuiFieldText, + EuiCheckbox, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiFormLabel, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import styled from 'styled-components'; +import * as i18n from './translations'; +import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +import { CommonUseField } from '../../../../cases/components/create'; +import { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types'; + +const NestedContent = styled.div` + margin-left: 24px; +`; + +const EuiFlexItemIconColumn = styled(EuiFlexItem)` + width: 20px; +`; + +const EuiFlexItemRiskScoreColumn = styled(EuiFlexItem)` + width: 160px; +`; + +interface RiskScoreFieldProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + indices: string[]; +} + +export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskScoreFieldProps) => { + const [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected] = useState(false); + + const updateRiskScoreMapping = useCallback( + (event) => { + const values = field.value as AboutStepRiskScore; + field.setValue({ + value: values.value, + mapping: [ + { + field: event.target.value, + operator: 'equals', + value: '', + }, + ], + }); + }, + [field] + ); + + const severityLabel = useMemo(() => { + return ( +
+ + {i18n.RISK_SCORE} + + + {i18n.RISK_SCORE_DESCRIPTION} +
+ ); + }, []); + + const severityMappingLabel = useMemo(() => { + return ( +
+ setIsRiskScoreMappingSelected(!isRiskScoreMappingSelected)} + > + + setIsRiskScoreMappingSelected(e.target.checked)} + /> + + {i18n.RISK_SCORE_MAPPING} + + + + {i18n.RISK_SCORE_MAPPING_DESCRIPTION} + +
+ ); + }, [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected]); + + return ( + + + + + + + + {i18n.RISK_SCORE_MAPPING_DETAILS} + ) : ( + '' + ) + } + error={'errorMessage'} + isInvalid={false} + fullWidth + data-test-subj={dataTestSubj} + describedByIds={idAria ? [idAria] : undefined} + > + + + {isRiskScoreMappingSelected && ( + + + + + {i18n.SOURCE_FIELD} + + + + {i18n.RISK_SCORE} + + + + + + + + + + + + + + {i18n.RISK_SCORE_FIELD} + + + + + )} + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/translations.tsx new file mode 100644 index 0000000000000..a75bf19b5b3c4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/translations.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const RISK_SCORE = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.riskScoreTitle', + { + defaultMessage: 'Default risk score', + } +); + +export const RISK_SCORE_FIELD = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.riskScoreFieldTitle', + { + defaultMessage: 'signal.rule.risk_score', + } +); + +export const SOURCE_FIELD = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.sourceFieldTitle', + { + defaultMessage: 'Source field', + } +); + +export const RISK_SCORE_MAPPING = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.riskScoreMappingTitle', + { + defaultMessage: 'Risk score override', + } +); + +export const RISK_SCORE_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel', + { + defaultMessage: 'Select a risk score for all alerts generated by this rule.', + } +); + +export const RISK_SCORE_MAPPING_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel', + { + defaultMessage: 'Map a field from the source event (scaled 1-100) to risk score.', + } +); + +export const RISK_SCORE_MAPPING_DETAILS = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.mappingDetailsLabel', + { + defaultMessage: + 'If value is out of bounds, or field is not present, the default risk score will be used.', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/index.tsx new file mode 100644 index 0000000000000..47c45a6bdf88d --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/index.tsx @@ -0,0 +1,214 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFormRow, + EuiFieldText, + EuiCheckbox, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiFormLabel, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; +import styled from 'styled-components'; +import * as i18n from './translations'; +import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +import { SeverityOptionItem } from '../step_about_rule/data'; +import { CommonUseField } from '../../../../cases/components/create'; +import { AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; + +const NestedContent = styled.div` + margin-left: 24px; +`; + +const EuiFlexItemIconColumn = styled(EuiFlexItem)` + width: 20px; +`; + +const EuiFlexItemSeverityColumn = styled(EuiFlexItem)` + width: 80px; +`; + +interface SeverityFieldProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + indices: string[]; + options: SeverityOptionItem[]; +} + +export const SeverityField = ({ + dataTestSubj, + field, + idAria, + indices, // TODO: To be used with autocomplete fields once https://github.com/elastic/kibana/pull/67013 is merged + options, +}: SeverityFieldProps) => { + const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); + + const updateSeverityMapping = useCallback( + (index: number, severity: string, mappingField: string, event) => { + const values = field.value as AboutStepSeverity; + field.setValue({ + value: values.value, + mapping: [ + ...values.mapping.slice(0, index), + { + ...values.mapping[index], + [mappingField]: event.target.value, + operator: 'equals', + severity, + }, + ...values.mapping.slice(index + 1), + ], + }); + }, + [field] + ); + + const severityLabel = useMemo(() => { + return ( +
+ + {i18n.SEVERITY} + + + {i18n.SEVERITY_DESCRIPTION} +
+ ); + }, []); + + const severityMappingLabel = useMemo(() => { + return ( +
+ setIsSeverityMappingChecked(!isSeverityMappingChecked)} + > + + setIsSeverityMappingChecked(e.target.checked)} + /> + + {i18n.SEVERITY_MAPPING} + + + + {i18n.SEVERITY_MAPPING_DESCRIPTION} + +
+ ); + }, [isSeverityMappingChecked, setIsSeverityMappingChecked]); + + return ( + + + + + + + + + {i18n.SEVERITY_MAPPING_DETAILS} + ) : ( + '' + ) + } + error={'errorMessage'} + isInvalid={false} + fullWidth + data-test-subj={dataTestSubj} + describedByIds={idAria ? [idAria] : undefined} + > + + + {isSeverityMappingChecked && ( + + + + + {i18n.SOURCE_FIELD} + + + {i18n.SOURCE_VALUE} + + + + {i18n.SEVERITY} + + + + + {options.map((option, index) => ( + + + + + + + + + + + + + + {option.inputDisplay} + + + + ))} + + )} + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/translations.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/translations.tsx new file mode 100644 index 0000000000000..9c9784bac6b63 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/translations.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SEVERITY = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.severityTitle', + { + defaultMessage: 'Default severity', + } +); + +export const SOURCE_FIELD = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.sourceFieldTitle', + { + defaultMessage: 'Source field', + } +); + +export const SOURCE_VALUE = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.sourceValueTitle', + { + defaultMessage: 'Source value', + } +); + +export const SEVERITY_MAPPING = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.severityMappingTitle', + { + defaultMessage: 'Severity override', + } +); + +export const SEVERITY_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.defaultDescriptionLabel', + { + defaultMessage: 'Select a severity level for all alerts generated by this rule.', + } +); + +export const SEVERITY_MAPPING_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.mappingDescriptionLabel', + { + defaultMessage: 'Map a value from the source event to a specific severity.', + } +); + +export const SEVERITY_MAPPING_DETAILS = i18n.translate( + 'xpack.securitySolution.alerts.severityMapping.mappingDetailsLabel', + { + defaultMessage: + 'For multiple matches the highest severity match will apply. If no match is found, the default severity will be used.', + } +); diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx index 269d2d4509508..1ef3edf8c720e 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/data.tsx @@ -12,7 +12,7 @@ import * as I18n from './translations'; export type SeverityValue = 'low' | 'medium' | 'high' | 'critical'; -interface SeverityOptionItem { +export interface SeverityOptionItem { value: SeverityValue; inputDisplay: React.ReactElement; } diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts index 977769158481e..060a2183eb06e 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/default_value.ts @@ -15,14 +15,19 @@ export const threatDefault = [ ]; export const stepAboutDefaultValue: AboutStepRule = { + author: [], name: '', description: '', + isBuildingBlock: false, isNew: true, - severity: 'low', - riskScore: 50, + severity: { value: 'low', mapping: [] }, + riskScore: { value: 50, mapping: [] }, references: [''], falsePositives: [''], + license: '', + ruleNameOverride: '', tags: [], + timestampOverride: '', threat: threatDefault, note: '', }; diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx index 5a08b0a20d1fc..b21c54a0b6131 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.test.tsx @@ -164,13 +164,18 @@ describe('StepAboutRuleComponent', () => { wrapper.find('button[data-test-subj="about-continue"]').first().simulate('click').update(); await wait(); const expected: Omit = { + author: [], + isBuildingBlock: false, + license: '', + ruleNameOverride: '', + timestampOverride: '', description: 'Test description text', falsePositives: [''], name: 'Test name text', note: '', references: [''], - riskScore: 50, - severity: 'low', + riskScore: { value: 50, mapping: [] }, + severity: { value: 'low', mapping: [] }, tags: [], threat: [ { @@ -217,13 +222,18 @@ describe('StepAboutRuleComponent', () => { wrapper.find('[data-test-subj="about-continue"]').first().simulate('click').update(); await wait(); const expected: Omit = { + author: [], + isBuildingBlock: false, + license: '', + ruleNameOverride: '', + timestampOverride: '', description: 'Test description text', falsePositives: [''], name: 'Test name text', note: '', references: [''], - riskScore: 80, - severity: 'low', + riskScore: { value: 80, mapping: [] }, + severity: { value: 'low', mapping: [] }, tags: [], threat: [ { diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx index f23c51e019f24..7f7ee94ed85b7 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; +import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -13,6 +13,7 @@ import { RuleStepProps, RuleStep, AboutStepRule, + DefineStepRule, } from '../../../pages/detection_engine/rules/types'; import { AddItem } from '../add_item_form'; import { StepRuleDescription } from '../description_step'; @@ -35,11 +36,14 @@ import { StepContentWrapper } from '../step_content_wrapper'; import { NextStep } from '../next_step'; import { MarkdownEditorForm } from '../../../../common/components/markdown_editor/form'; import { setFieldValue } from '../../../pages/detection_engine/rules/helpers'; +import { SeverityField } from '../severity_mapping'; +import { RiskScoreField } from '../risk_score_mapping'; const CommonUseField = getUseField({ component: Field }); interface StepAboutRuleProps extends RuleStepProps { defaultValues?: AboutStepRule | null; + defineRuleData?: DefineStepRule | null; } const ThreeQuartersContainer = styled.div` @@ -77,6 +81,7 @@ const AdvancedSettingsAccordionButton = ( const StepAboutRuleComponent: FC = ({ addPadding = false, defaultValues, + defineRuleData, descriptionColumns = 'singleSplit', isReadOnlyView, isUpdateView = false, @@ -132,64 +137,54 @@ const StepAboutRuleComponent: FC = ({ <>
- - - - - - - - + + + + + - - + @@ -207,13 +202,13 @@ const StepAboutRuleComponent: FC = ({ }} /> - + - + = ({ dataTestSubj: 'detectionEngineStepAboutRuleMitreThreat', }} /> - - - + + + + + + + + + + + + + + + + - + {({ severity }) => { diff --git a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx index 59ecebaeb9e4e..309557e5c9421 100644 --- a/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx @@ -22,6 +22,23 @@ import * as I18n from './translations'; const { emptyField } = fieldValidators; export const schema: FormSchema = { + author: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldAuthorLabel', + { + defaultMessage: 'Author', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldAuthorHelpText', + { + defaultMessage: + 'Type one or more author for this rule. Press enter after each author to add a new one.', + } + ), + labelAppend: OptionalFieldLabel, + }, name: { type: FIELD_TYPES.TEXT, label: i18n.translate( @@ -64,36 +81,44 @@ export const schema: FormSchema = { }, ], }, - severity: { - type: FIELD_TYPES.SUPER_SELECT, + isBuildingBlock: { + type: FIELD_TYPES.CHECKBOX, label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldBuildingBlockLabel', { - defaultMessage: 'Severity', + defaultMessage: 'Mark all generated alerts as "building block" alerts', } ), - validations: [ - { - validator: emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.severityFieldRequiredError', - { - defaultMessage: 'A severity is required.', - } - ) - ), - }, - ], + labelAppend: OptionalFieldLabel, + }, + severity: { + value: { + type: FIELD_TYPES.SUPER_SELECT, + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.severityFieldRequiredError', + { + defaultMessage: 'A severity is required.', + } + ) + ), + }, + ], + }, + mapping: { + type: FIELD_TYPES.TEXT, + }, }, riskScore: { - type: FIELD_TYPES.RANGE, - serializer: (input: string) => Number(input), - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRiskScoreLabel', - { - defaultMessage: 'Risk score', - } - ), + value: { + type: FIELD_TYPES.RANGE, + serializer: (input: string) => Number(input), + }, + mapping: { + type: FIELD_TYPES.TEXT, + }, }, references: { label: i18n.translate( @@ -135,6 +160,39 @@ export const schema: FormSchema = { ), labelAppend: OptionalFieldLabel, }, + license: { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldLicenseLabel', + { + defaultMessage: 'License', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldLicenseHelpText', + { + defaultMessage: 'Add a license name', + } + ), + labelAppend: OptionalFieldLabel, + }, + ruleNameOverride: { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRuleNameOverrideLabel', + { + defaultMessage: 'Rule name override', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRuleNameOverrideHelpText', + { + defaultMessage: + 'Choose a field from the source event to populate the rule name in the alert list.', + } + ), + labelAppend: OptionalFieldLabel, + }, threat: { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', @@ -166,6 +224,23 @@ export const schema: FormSchema = { }, ], }, + timestampOverride: { + type: FIELD_TYPES.TEXT, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimestampOverrideLabel', + { + defaultMessage: 'Timestamp override', + } + ), + helpText: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimestampOverrideHelpText', + { + defaultMessage: + 'Choose timestamp field used when executing rule. Pick field with timestamp closest to ingest time (e.g. event.ingested).', + } + ), + labelAppend: OptionalFieldLabel, + }, tags: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts index abba7c02cf875..46829b9cb8f7b 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/api.test.ts @@ -291,7 +291,7 @@ describe('Detections Rules API', () => { await duplicateRules({ rules: rulesMock.data }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_create', { body: - '[{"actions":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]', + '[{"actions":[],"author":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"risk_score_mapping":[],"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"author":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"risk_score_mapping":[],"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","severity_mapping":[],"tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]', method: 'POST', }); }); diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts index 59782e8a36338..fa11cfabcdf8b 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/mock.ts @@ -36,6 +36,7 @@ export const ruleMock: NewRule = { }; export const savedRuleMock: Rule = { + author: [], actions: [], created_at: 'mm/dd/yyyyTHH:MM:sssz', created_by: 'mockUser', @@ -58,11 +59,13 @@ export const savedRuleMock: Rule = { rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', language: 'kuery', risk_score: 75, + risk_score_mapping: [], name: 'Test rule', max_signals: 100, query: "user.email: 'root@elastic.co'", references: [], severity: 'high', + severity_mapping: [], tags: ['APM'], to: 'now', type: 'query', @@ -79,6 +82,7 @@ export const rulesMock: FetchRulesResponse = { data: [ { actions: [], + author: [], created_at: '2020-02-14T19:49:28.178Z', updated_at: '2020-02-14T19:49:28.320Z', created_by: 'elastic', @@ -96,12 +100,14 @@ export const rulesMock: FetchRulesResponse = { output_index: '.siem-signals-default', max_signals: 100, risk_score: 73, + risk_score_mapping: [], name: 'Credential Dumping - Detected - Elastic Endpoint', query: 'event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection', filters: [], references: [], severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: ['Elastic', 'Endpoint'], to: 'now', @@ -112,6 +118,7 @@ export const rulesMock: FetchRulesResponse = { }, { actions: [], + author: [], created_at: '2020-02-14T19:49:28.189Z', updated_at: '2020-02-14T19:49:28.326Z', created_by: 'elastic', @@ -129,11 +136,13 @@ export const rulesMock: FetchRulesResponse = { output_index: '.siem-signals-default', max_signals: 100, risk_score: 47, + risk_score_mapping: [], name: 'Adversary Behavior - Detected - Elastic Endpoint', query: 'event.kind:alert and event.module:endgame and event.action:rules_engine_event', filters: [], references: [], severity: 'medium', + severity_mapping: [], updated_by: 'elastic', tags: ['Elastic', 'Endpoint'], to: 'now', diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts index ab9b88fb81fa7..d991cc35b8dfe 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts @@ -7,6 +7,17 @@ import * as t from 'io-ts'; import { RuleTypeSchema } from '../../../../../common/detection_engine/types'; +/* eslint-disable @typescript-eslint/camelcase */ +import { + author, + building_block_type, + license, + risk_score_mapping, + rule_name_override, + severity_mapping, + timestamp_override, +} from '../../../../../common/detection_engine/schemas/common/schemas'; +/* eslint-enable @typescript-eslint/camelcase */ /** * Params is an "record", since it is a type of AlertActionParams which is action templates. @@ -76,6 +87,7 @@ const MetaRule = t.intersection([ export const RuleSchema = t.intersection([ t.type({ + author, created_at: t.string, created_by: t.string, description: t.string, @@ -89,8 +101,10 @@ export const RuleSchema = t.intersection([ max_signals: t.number, references: t.array(t.string), risk_score: t.number, + risk_score_mapping, rule_id: t.string, severity: t.string, + severity_mapping, tags: t.array(t.string), type: RuleTypeSchema, to: t.string, @@ -101,21 +115,25 @@ export const RuleSchema = t.intersection([ throttle: t.union([t.string, t.null]), }), t.partial({ + building_block_type, anomaly_threshold: t.number, filters: t.array(t.unknown), index: t.array(t.string), language: t.string, + license, last_failure_at: t.string, last_failure_message: t.string, meta: MetaRule, machine_learning_job_id: t.string, output_index: t.string, query: t.string, + rule_name_override, saved_id: t.string, status: t.string, status_date: t.string, timeline_id: t.string, timeline_title: t.string, + timestamp_override, note: t.string, version: t.number, }), diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx index 9bfbade060303..e3cc6878eabca 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule.test.tsx @@ -32,6 +32,7 @@ describe('useRule', () => { false, { actions: [], + author: [], created_at: 'mm/dd/yyyyTHH:MM:sssz', created_by: 'mockUser', description: 'some desc', @@ -56,8 +57,10 @@ describe('useRule', () => { query: "user.email: 'root@elastic.co'", references: [], risk_score: 75, + risk_score_mapping: [], rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', severity: 'high', + severity_mapping: [], tags: ['APM'], threat: [], throttle: null, diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx index f203eca42cde6..1f2c0c32d590f 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rule_status.test.tsx @@ -27,6 +27,7 @@ const testRule: Rule = { }, }, ], + author: [], created_at: 'mm/dd/yyyyTHH:MM:sssz', created_by: 'mockUser', description: 'some desc', @@ -51,8 +52,10 @@ const testRule: Rule = { query: "user.email: 'root@elastic.co'", references: [], risk_score: 75, + risk_score_mapping: [], rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', severity: 'high', + severity_mapping: [], tags: ['APM'], threat: [], throttle: null, diff --git a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx index ad34c39272bbf..76f2a5b58754e 100644 --- a/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/use_rules.test.tsx @@ -59,6 +59,7 @@ describe('useRules', () => { data: [ { actions: [], + author: [], created_at: '2020-02-14T19:49:28.178Z', created_by: 'elastic', description: @@ -79,8 +80,10 @@ describe('useRules', () => { 'event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection', references: [], risk_score: 73, + risk_score_mapping: [], rule_id: '571afc56-5ed9-465d-a2a9-045f099f6e7e', severity: 'high', + severity_mapping: [], tags: ['Elastic', 'Endpoint'], threat: [], throttle: null, @@ -92,6 +95,7 @@ describe('useRules', () => { }, { actions: [], + author: [], created_at: '2020-02-14T19:49:28.189Z', created_by: 'elastic', description: @@ -112,8 +116,10 @@ describe('useRules', () => { 'event.kind:alert and event.module:endgame and event.action:rules_engine_event', references: [], risk_score: 47, + risk_score_mapping: [], rule_id: '77a3c3df-8ec4-4da4-b758-878f551dee69', severity: 'medium', + severity_mapping: [], tags: ['Elastic', 'Endpoint'], threat: [], throttle: null, diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts index 1b43a513d0d29..f1416bfbc41b5 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -41,6 +41,7 @@ export const mockQueryBar: FieldValueQueryBar = { export const mockRule = (id: string): Rule => ({ actions: [], + author: [], created_at: '2020-01-10T21:11:45.839Z', updated_at: '2020-01-10T21:11:45.839Z', created_by: 'elastic', @@ -58,6 +59,7 @@ export const mockRule = (id: string): Rule => ({ output_index: '.siem-signals-default', max_signals: 100, risk_score: 21, + risk_score_mapping: [], name: 'Home Grown!', query: '', references: [], @@ -66,6 +68,7 @@ export const mockRule = (id: string): Rule => ({ timeline_title: 'Untitled timeline', meta: { from: '0m' }, severity: 'low', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', @@ -78,6 +81,7 @@ export const mockRule = (id: string): Rule => ({ export const mockRuleWithEverything = (id: string): Rule => ({ actions: [], + author: [], created_at: '2020-01-10T21:11:45.839Z', updated_at: '2020-01-10T21:11:45.839Z', created_by: 'elastic', @@ -113,9 +117,12 @@ export const mockRuleWithEverything = (id: string): Rule => ({ interval: '5m', rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', language: 'kuery', + license: 'Elastic License', output_index: '.siem-signals-default', max_signals: 100, risk_score: 21, + risk_score_mapping: [], + rule_name_override: 'message', name: 'Query with rule-id', query: 'user.name: root or user.name: admin', references: ['www.test.co'], @@ -124,6 +131,7 @@ export const mockRuleWithEverything = (id: string): Rule => ({ timeline_title: 'Titled timeline', meta: { from: '0m' }, severity: 'low', + severity_mapping: [], updated_by: 'elastic', tags: ['tag1', 'tag2'], to: 'now', @@ -146,16 +154,23 @@ export const mockRuleWithEverything = (id: string): Rule => ({ }, ], throttle: 'no_actions', + timestamp_override: 'event.ingested', note: '# this is some markdown documentation', version: 1, }); +// TODO: update types mapping export const mockAboutStepRule = (isNew = false): AboutStepRule => ({ isNew, + author: ['Elastic'], + isBuildingBlock: false, + timestampOverride: '', + ruleNameOverride: '', + license: 'Elastic License', name: 'Query with rule-id', description: '24/7', - severity: 'low', - riskScore: 21, + severity: { value: 'low', mapping: [] }, + riskScore: { value: 21, mapping: [] }, references: ['www.test.co'], falsePositives: ['test'], tags: ['tag1', 'tag2'], diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts index d9cbcfc8979a1..bbfbbaae058d4 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.test.ts @@ -339,13 +339,18 @@ describe('helpers', () => { test('returns formatted object as AboutStepRuleJson', () => { const result: AboutStepRuleJson = formatAboutStepData(mockData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, + risk_score_mapping: [], + rule_name_override: '', severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -364,6 +369,7 @@ describe('helpers', () => { ], }, ], + timestamp_override: '', }; expect(result).toEqual(expected); @@ -377,13 +383,18 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, + risk_score_mapping: [], + rule_name_override: '', severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -402,6 +413,7 @@ describe('helpers', () => { ], }, ], + timestamp_override: '', }; expect(result).toEqual(expected); @@ -414,12 +426,17 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + license: 'Elastic License', name: 'Query with rule-id', references: ['www.test.co'], risk_score: 21, + risk_score_mapping: [], + rule_name_override: '', severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -438,6 +455,7 @@ describe('helpers', () => { ], }, ], + timestamp_override: '', }; expect(result).toEqual(expected); @@ -481,13 +499,18 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], + license: 'Elastic License', description: '24/7', false_positives: ['test'], name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, + risk_score_mapping: [], + rule_name_override: '', severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -496,6 +519,7 @@ describe('helpers', () => { technique: [{ id: '456', name: 'technique1', reference: 'technique reference' }], }, ], + timestamp_override: '', }; expect(result).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts index d5ce57ce5b3a9..b7cf94bb4f319 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/helpers.ts @@ -122,11 +122,30 @@ export const formatScheduleStepData = (scheduleData: ScheduleStepRule): Schedule }; export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, threat, isNew, note, ...rest } = aboutStepData; - return { + const { + author, + falsePositives, + references, + riskScore, + severity, + threat, + isBuildingBlock, + isNew, + note, + ruleNameOverride, + timestampOverride, + ...rest + } = aboutStepData; + const resp = { + author: author.filter((item) => !isEmpty(item)), + ...(isBuildingBlock ? { building_block_type: 'default' } : {}), false_positives: falsePositives.filter((item) => !isEmpty(item)), references: references.filter((item) => !isEmpty(item)), - risk_score: riskScore, + risk_score: riskScore.value, + risk_score_mapping: riskScore.mapping, + rule_name_override: ruleNameOverride, + severity: severity.value, + severity_mapping: severity.mapping, threat: threat .filter((singleThreat) => singleThreat.tactic.name !== 'none') .map((singleThreat) => ({ @@ -137,9 +156,11 @@ export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRule return { id, name, reference }; }), })), + timestamp_override: timestampOverride, ...(!isEmpty(note) ? { note } : {}), ...rest, }; + return resp; }; export const formatActionsStepData = (actionsStepData: ActionsStepRule): ActionsStepRuleJson => { diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx index de3e23b11aaf8..4be408039d6f6 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/create/index.tsx @@ -358,6 +358,9 @@ const CreateRulePageComponent: React.FC = () => { { }, }; const aboutRuleStepData = { + author: [], description: '24/7', falsePositives: ['test'], + isBuildingBlock: false, isNew: false, + license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], - riskScore: 21, - severity: 'low', + riskScore: { value: 21, mapping: [] }, + ruleNameOverride: 'message', + severity: { value: 'low', mapping: [] }, tags: ['tag1', 'tag2'], threat: [ { @@ -106,6 +110,7 @@ describe('rule helpers', () => { ], }, ], + timestampOverride: 'event.ingested', }; const scheduleRuleStepData = { from: '0s', interval: '5m', isNew: false }; const ruleActionsStepData = { diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx index 2cd211a35e9ba..2a792f7d35eaa 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.tsx @@ -116,6 +116,13 @@ export const getHumanizedDuration = (from: string, interval: string): string => export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRule => { const { name, description, note } = determineDetailsValue(rule, detailsView); const { + author, + building_block_type: buildingBlockType, + license, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, + severity_mapping: severityMapping, + timestamp_override: timestampOverride, references, severity, false_positives: falsePositives, @@ -126,13 +133,24 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu return { isNew: false, + author, + isBuildingBlock: buildingBlockType !== undefined, + license: license ?? '', + ruleNameOverride: ruleNameOverride ?? '', + timestampOverride: timestampOverride ?? '', name, description, note: note!, references, - severity, + severity: { + value: severity, + mapping: severityMapping, + }, tags, - riskScore, + riskScore: { + value: riskScore, + mapping: riskScoreMapping, + }, falsePositives, threat: threat as IMitreEnterpriseAttack[], }; diff --git a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts index 5f81409010a28..f453b5a95994d 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/types.ts @@ -10,6 +10,15 @@ import { Filter } from '../../../../../../../../src/plugins/data/common'; import { FormData, FormHook } from '../../../../shared_imports'; import { FieldValueQueryBar } from '../../../components/rules/query_bar'; import { FieldValueTimeline } from '../../../components/rules/pick_timeline'; +import { + Author, + BuildingBlockType, + License, + RiskScoreMapping, + RuleNameOverride, + SeverityMapping, + TimestampOverride, +} from '../../../../../common/detection_engine/schemas/common/schemas'; export interface EuiBasicTableSortTypes { field: string; @@ -52,13 +61,18 @@ interface StepRuleData { isNew: boolean; } export interface AboutStepRule extends StepRuleData { + author: string[]; name: string; description: string; - severity: string; - riskScore: number; + isBuildingBlock: boolean; + severity: AboutStepSeverity; + riskScore: AboutStepRiskScore; references: string[]; falsePositives: string[]; + license: string; + ruleNameOverride: string; tags: string[]; + timestampOverride: string; threat: IMitreEnterpriseAttack[]; note: string; } @@ -68,6 +82,16 @@ export interface AboutStepRuleDetails { description: string; } +export interface AboutStepSeverity { + value: string; + mapping: SeverityMapping; +} + +export interface AboutStepRiskScore { + value: number; + mapping: RiskScoreMapping; +} + export interface DefineStepRule extends StepRuleData { anomalyThreshold: number; index: string[]; @@ -104,14 +128,21 @@ export interface DefineStepRuleJson { } export interface AboutStepRuleJson { + author: Author; + building_block_type?: BuildingBlockType; name: string; description: string; + license: License; severity: string; + severity_mapping: SeverityMapping; risk_score: number; + risk_score_mapping: RiskScoreMapping; references: string[]; false_positives: string[]; + rule_name_override: RuleNameOverride; tags: string[]; threat: IMitreEnterpriseAttack[]; + timestamp_override: TimestampOverride; note?: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 581946f2300b4..9ca102b437511 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -342,6 +342,8 @@ export const getResult = (): RuleAlertType => ({ alertTypeId: 'siem.signals', consumer: 'siem', params: { + author: ['Elastic'], + buildingBlockType: undefined, anomalyThreshold: undefined, description: 'Detecting root and admin users', ruleId: 'rule-1', @@ -352,6 +354,7 @@ export const getResult = (): RuleAlertType => ({ savedId: undefined, query: 'user.name: root or user.name: admin', language: 'kuery', + license: 'Elastic License', machineLearningJobId: undefined, outputIndex: '.siem-signals', timelineId: 'some-timeline-id', @@ -367,8 +370,11 @@ export const getResult = (): RuleAlertType => ({ }, ], riskScore: 50, + riskScoreMapping: [], + ruleNameOverride: undefined, maxSignals: 100, severity: 'high', + severityMapping: [], to: 'now', type: 'query', threat: [ @@ -388,6 +394,7 @@ export const getResult = (): RuleAlertType => ({ ], }, ], + timestampOverride: undefined, references: ['http://www.example.com', 'https://ww.example.com'], note: '# Investigative notes', version: 1, 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 7b7d3fbdea0bf..87903d1035903 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 @@ -36,6 +36,7 @@ export const getOutputRuleAlertForRest = (): Omit< RulesSchema, 'machine_learning_job_id' | 'anomaly_threshold' > => ({ + author: ['Elastic'], actions: [], created_by: 'elastic', created_at: '2019-12-13T16:40:33.400Z', @@ -49,14 +50,17 @@ export const getOutputRuleAlertForRest = (): Omit< index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', risk_score: 50, + risk_score_mapping: [], rule_id: 'rule-1', language: 'kuery', + license: 'Elastic License', max_signals: 100, name: 'Detect Root/Admin Users', output_index: '.siem-signals', query: 'user.name: root or user.name: admin', references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], throttle: 'no_actions', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json index dc20f0793a6f8..aa4166e93f4a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/signals_mapping.json @@ -46,6 +46,12 @@ "rule_id": { "type": "keyword" }, + "author": { + "type": "keyword" + }, + "building_block_type": { + "type": "keyword" + }, "false_positives": { "type": "keyword" }, @@ -64,6 +70,19 @@ "risk_score": { "type": "keyword" }, + "risk_score_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + } + } + }, "output_index": { "type": "keyword" }, @@ -85,9 +104,15 @@ "language": { "type": "keyword" }, + "license": { + "type": "keyword" + }, "name": { "type": "keyword" }, + "rule_name_override": { + "type": "keyword" + }, "query": { "type": "keyword" }, @@ -97,6 +122,22 @@ "severity": { "type": "keyword" }, + "severity_mapping": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "value": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + } + } + }, "tags": { "type": "keyword" }, @@ -136,6 +177,9 @@ "note": { "type": "text" }, + "timestamp_override": { + "type": "keyword" + }, "type": { "type": "keyword" }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 4b65ee5efdff0..fc2cc6551450c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -21,9 +21,12 @@ jest.mock('../../rules/get_prepackaged_rules', () => { getPrepackagedRules: (): AddPrepackagedRulesSchemaDecoded[] => { return [ { + author: ['Elastic'], tags: [], rule_id: 'rule-1', risk_score: 50, + risk_score_mapping: [], + severity_mapping: [], description: 'some description', from: 'now-5m', to: 'now', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 92a7ea17e7eaf..2942413057e37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -64,12 +64,15 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => const { actions: actionsRest, anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query: queryOrUndefined, language: languageOrUndefined, + license, machine_learning_job_id: machineLearningJobId, output_index: outputIndex, saved_id: savedId, @@ -80,11 +83,15 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, threat, throttle, + timestamp_override: timestampOverride, to, type, references, @@ -139,6 +146,8 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => const createdRule = await createRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, @@ -146,6 +155,7 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => immutable: false, query, language, + license, machineLearningJobId, outputIndex: finalIndex, savedId, @@ -157,13 +167,17 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => index, interval, maxSignals, - riskScore, name, + riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index 78d67e0e9366c..310a9da56282d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -47,12 +47,15 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void const { actions: actionsRest, anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query: queryOrUndefined, language: languageOrUndefined, + license, output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, @@ -65,11 +68,15 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, threat, throttle, + timestamp_override: timestampOverride, to, type, references, @@ -121,6 +128,8 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void const createdRule = await createRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, @@ -128,6 +137,7 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void immutable: false, query, language, + license, outputIndex: finalIndex, savedId, timelineId, @@ -139,13 +149,17 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void index, interval, maxSignals, - riskScore, name, + riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index a277f97ccf9f0..43aa1ecd31922 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -134,6 +134,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP } const { anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, @@ -141,6 +143,7 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP immutable, query: queryOrUndefined, language: languageOrUndefined, + license, machine_learning_job_id: machineLearningJobId, output_index: outputIndex, saved_id: savedId, @@ -151,10 +154,14 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, threat, + timestamp_override: timestampOverride, to, type, references, @@ -184,6 +191,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP await createRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, @@ -191,6 +200,7 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP immutable, query, language, + license, machineLearningJobId, outputIndex: signalsIndex, savedId, @@ -202,13 +212,17 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP index, interval, maxSignals, - riskScore, name, + riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, @@ -219,6 +233,8 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP } else if (rule != null && request.query.overwrite) { await patchRules({ alertsClient, + author, + buildingBlockType, savedObjectsClient, description, enabled, @@ -226,6 +242,7 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP from, query, language, + license, outputIndex, savedId, timelineId, @@ -237,9 +254,13 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, + timestampOverride, to, type, threat, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index b2a9fdd103a68..c3d6f920e47a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -55,12 +55,15 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => request.body.map(async (payloadRule) => { const { actions: actionsRest, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query, language, + license, output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, @@ -73,12 +76,16 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, + timestamp_override: timestampOverride, throttle, references, note, @@ -107,12 +114,15 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => const rule = await patchRules({ rule: existingRule, alertsClient, + author, + buildingBlockType, description, enabled, falsePositives, from, query, language, + license, outputIndex, savedId, savedObjectsClient, @@ -124,12 +134,16 @@ export const patchRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index 385eec0fe1180..eb9624e6412e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -46,12 +46,15 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { } const { actions: actionsRest, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query, language, + license, output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, @@ -64,12 +67,16 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, + timestamp_override: timestampOverride, throttle, references, note, @@ -105,12 +112,15 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { const ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient); const rule = await patchRules({ alertsClient, + author, + buildingBlockType, description, enabled, falsePositives, from, query, language, + license, outputIndex, savedId, savedObjectsClient, @@ -123,12 +133,16 @@ export const patchRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 1e6815a357154..c1ab1be2dbd0a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -57,12 +57,15 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => const { actions: actionsRest, anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query: queryOrUndefined, language: languageOrUndefined, + license, machine_learning_job_id: machineLearningJobId, output_index: outputIndex, saved_id: savedId, @@ -76,13 +79,17 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, throttle, + timestamp_override: timestampOverride, references, note, version, @@ -117,12 +124,15 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => const rule = await updateRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, from, query, language, + license, machineLearningJobId, outputIndex: finalIndex, savedId, @@ -137,12 +147,16 @@ export const updateRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index f2b47f195ca5c..717f388cfc1e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -47,12 +47,15 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { const { actions: actionsRest, anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query: queryOrUndefined, language: languageOrUndefined, + license, machine_learning_job_id: machineLearningJobId, output_index: outputIndex, saved_id: savedId, @@ -66,13 +69,17 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, throttle, + timestamp_override: timestampOverride, references, note, version, @@ -107,12 +114,15 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { const rule = await updateRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, from, query, language, + license, machineLearningJobId, outputIndex: finalIndex, savedId, @@ -127,12 +137,16 @@ export const updateRulesRoute = (router: IRouter, ml: SetupPlugins['ml']) => { interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 9320eba26df0b..9e93dc051a041 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -105,7 +105,9 @@ export const transformAlertToRule = ( ruleStatus?: SavedObject ): Partial => { return pickBy((value: unknown) => value != null, { + author: alert.params.author ?? [], actions: ruleActions?.actions ?? [], + building_block_type: alert.params.buildingBlockType, created_at: alert.createdAt.toISOString(), updated_at: alert.updatedAt.toISOString(), created_by: alert.createdBy ?? 'elastic', @@ -121,10 +123,13 @@ export const transformAlertToRule = ( interval: alert.schedule.interval, rule_id: alert.params.ruleId, language: alert.params.language, + license: alert.params.license, output_index: alert.params.outputIndex, max_signals: alert.params.maxSignals, machine_learning_job_id: alert.params.machineLearningJobId, risk_score: alert.params.riskScore, + risk_score_mapping: alert.params.riskScoreMapping ?? [], + rule_name_override: alert.params.ruleNameOverride, name: alert.name, query: alert.params.query, references: alert.params.references, @@ -133,12 +138,14 @@ export const transformAlertToRule = ( timeline_title: alert.params.timelineTitle, meta: alert.params.meta, severity: alert.params.severity, + severity_mapping: alert.params.severityMapping ?? [], updated_by: alert.updatedBy ?? 'elastic', tags: transformTags(alert.tags), to: alert.params.to, type: alert.params.type, threat: alert.params.threat ?? [], throttle: ruleActions?.ruleThrottle || 'no_actions', + timestamp_override: alert.params.timestampOverride, note: alert.params.note, version: alert.params.version, status: ruleStatus?.attributes.status ?? undefined, 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 0065696712628..4dafafe3153ef 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 @@ -18,6 +18,7 @@ import { getListArrayMock } from '../../../../../common/detection_engine/schemas export const ruleOutput: RulesSchema = { actions: [], + author: ['Elastic'], created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', created_by: 'elastic', @@ -30,13 +31,16 @@ export const ruleOutput: RulesSchema = { interval: '5m', rule_id: 'rule-1', language: 'kuery', + license: 'Elastic License', output_index: '.siem-signals', max_signals: 100, risk_score: 50, + risk_score_mapping: [], name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://www.example.com', 'https://ww.example.com'], severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', 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 d00bffb96ad05..a7e24a1ac1609 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 @@ -8,6 +8,8 @@ import { CreateRulesOptions } from './types'; import { alertsClientMock } from '../../../../../alerts/server/mocks'; export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, alertsClient: alertsClientMock.create(), anomalyThreshold: undefined, description: 'some description', @@ -16,6 +18,7 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ from: 'now-6m', query: 'user.name: root or user.name: admin', language: 'kuery', + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -28,11 +31,15 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Query with a rule id', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], @@ -43,6 +50,8 @@ export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ }); export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, alertsClient: alertsClientMock.create(), anomalyThreshold: 55, description: 'some description', @@ -51,6 +60,7 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ from: 'now-6m', query: undefined, language: undefined, + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -63,11 +73,15 @@ export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Machine Learning Job', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'machine_learning', references: ['http://www.example.com'], 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 83e9b0de16f06..b4e246718efd7 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 @@ -14,12 +14,15 @@ import { hasListsFeature } from '../feature_flags'; export const createRules = async ({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, from, query, language, + license, savedId, timelineId, timelineTitle, @@ -32,11 +35,15 @@ export const createRules = async ({ interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, outputIndex, name, severity, + severityMapping, tags, threat, + timestampOverride, to, type, references, @@ -55,6 +62,8 @@ export const createRules = async ({ consumer: APP_ID, params: { anomalyThreshold, + author, + buildingBlockType, description, ruleId, index, @@ -63,6 +72,7 @@ export const createRules = async ({ immutable, query, language, + license, outputIndex, savedId, timelineId, @@ -72,8 +82,12 @@ export const createRules = async ({ filters, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, threat, + timestampOverride, to, type, references, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index c4d7df61061bd..f2061ce1d36de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -49,16 +49,19 @@ describe('create_rules_stream_from_ndjson', () => { ]); expect(result).toEqual([ { + author: [], actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -73,16 +76,19 @@ describe('create_rules_stream_from_ndjson', () => { version: 1, }, { + author: [], actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -135,16 +141,19 @@ describe('create_rules_stream_from_ndjson', () => { ]); expect(result).toEqual([ { + author: [], actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -159,16 +168,19 @@ describe('create_rules_stream_from_ndjson', () => { version: 1, }, { + author: [], actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -204,16 +216,19 @@ describe('create_rules_stream_from_ndjson', () => { ]); expect(result).toEqual([ { + author: [], actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -228,16 +243,19 @@ describe('create_rules_stream_from_ndjson', () => { version: 1, }, { + author: [], actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -273,16 +291,19 @@ describe('create_rules_stream_from_ndjson', () => { ]); const resultOrError = result as Error[]; expect(resultOrError[0]).toEqual({ + author: [], actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -298,16 +319,19 @@ describe('create_rules_stream_from_ndjson', () => { }); expect(resultOrError[1].message).toEqual('Unexpected token , in JSON at position 1'); expect(resultOrError[2]).toEqual({ + author: [], actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -342,16 +366,19 @@ describe('create_rules_stream_from_ndjson', () => { ]); const resultOrError = result as BadRequestError[]; expect(resultOrError[0]).toEqual({ + author: [], actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, @@ -369,16 +396,19 @@ describe('create_rules_stream_from_ndjson', () => { 'Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "name",Invalid value "undefined" supplied to "severity",Invalid value "undefined" supplied to "type",Invalid value "undefined" supplied to "rule_id"' ); expect(resultOrError[2]).toEqual({ + author: [], actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, + risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', + severity_mapping: [], interval: '5m', type: 'query', enabled: true, 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 7d4bbfdced432..c8ea000dd0dcd 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 @@ -30,6 +30,7 @@ describe('getExportAll', () => { const exports = await getExportAll(alertsClient); expect(exports).toEqual({ rulesNdjson: `${JSON.stringify({ + author: ['Elastic'], actions: [], created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', @@ -45,9 +46,11 @@ describe('getExportAll', () => { interval: '5m', rule_id: 'rule-1', language: 'kuery', + license: 'Elastic License', output_index: '.siem-signals', max_signals: 100, risk_score: 50, + risk_score_mapping: [], name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://www.example.com', 'https://ww.example.com'], @@ -55,6 +58,7 @@ describe('getExportAll', () => { timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', 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 043e563a4c8b5..d5dffff00b896 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 @@ -38,6 +38,7 @@ describe('get_export_by_object_ids', () => { const exports = await getExportByObjectIds(alertsClient, objects); expect(exports).toEqual({ rulesNdjson: `${JSON.stringify({ + author: ['Elastic'], actions: [], created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', @@ -53,9 +54,11 @@ describe('get_export_by_object_ids', () => { interval: '5m', rule_id: 'rule-1', language: 'kuery', + license: 'Elastic License', output_index: '.siem-signals', max_signals: 100, risk_score: 50, + risk_score_mapping: [], name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://www.example.com', 'https://ww.example.com'], @@ -63,6 +66,7 @@ describe('get_export_by_object_ids', () => { timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', @@ -139,6 +143,7 @@ describe('get_export_by_object_ids', () => { rules: [ { actions: [], + author: ['Elastic'], created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', created_by: 'elastic', @@ -153,9 +158,11 @@ describe('get_export_by_object_ids', () => { interval: '5m', rule_id: 'rule-1', language: 'kuery', + license: 'Elastic License', output_index: '.siem-signals', max_signals: 100, risk_score: 50, + risk_score_mapping: [], name: 'Detect Root/Admin Users', query: 'user.name: root or user.name: admin', references: ['http://www.example.com', 'https://ww.example.com'], @@ -163,6 +170,7 @@ describe('get_export_by_object_ids', () => { timeline_title: 'some-timeline-title', meta: { someMeta: 'someField' }, severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', 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 a51acf99b570c..8a86a0f103371 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 @@ -18,12 +18,15 @@ export const installPrepackagedRules = ( rules.reduce>>((acc, rule) => { const { anomaly_threshold: anomalyThreshold, + author, + building_block_type: buildingBlockType, description, enabled, false_positives: falsePositives, from, query, language, + license, machine_learning_job_id: machineLearningJobId, saved_id: savedId, timeline_id: timelineId, @@ -35,12 +38,16 @@ export const installPrepackagedRules = ( interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, + timestamp_override: timestampOverride, references, note, version, @@ -54,6 +61,8 @@ export const installPrepackagedRules = ( createRules({ alertsClient, anomalyThreshold, + author, + buildingBlockType, description, enabled, falsePositives, @@ -61,6 +70,7 @@ export const installPrepackagedRules = ( immutable: true, // At the moment we force all prepackaged rules to be immutable query, language, + license, machineLearningJobId, outputIndex, savedId, @@ -73,12 +83,16 @@ export const installPrepackagedRules = ( interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, to, type, threat, + timestampOverride, references, note, version, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts index e711d8d2ac287..f3102a5ad2cf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts @@ -113,6 +113,8 @@ const rule: SanitizedAlert = { }; export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, alertsClient: alertsClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), anomalyThreshold: undefined, @@ -122,6 +124,7 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ from: 'now-6m', query: 'user.name: root or user.name: admin', language: 'kuery', + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -132,11 +135,15 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Query with a rule id', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], @@ -148,6 +155,8 @@ export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ }); export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, alertsClient: alertsClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), anomalyThreshold: 55, @@ -157,6 +166,7 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ from: 'now-6m', query: undefined, language: undefined, + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -167,11 +177,15 @@ export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Machine Learning Job', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'machine_learning', references: ['http://www.example.com'], 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 0c103b7176db4..577d8d426b63d 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 @@ -14,12 +14,15 @@ import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_save export const patchRules = async ({ alertsClient, + author, + buildingBlockType, savedObjectsClient, description, falsePositives, enabled, query, language, + license, outputIndex, savedId, timelineId, @@ -31,11 +34,15 @@ export const patchRules = async ({ interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, rule, name, severity, + severityMapping, tags, threat, + timestampOverride, to, type, references, @@ -51,10 +58,13 @@ export const patchRules = async ({ } const calculatedVersion = calculateVersion(rule.params.immutable, rule.params.version, { + author, + buildingBlockType, description, falsePositives, query, language, + license, outputIndex, savedId, timelineId, @@ -66,10 +76,14 @@ export const patchRules = async ({ interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, threat, + timestampOverride, to, type, references, @@ -85,11 +99,14 @@ export const patchRules = async ({ ...rule.params, }, { + author, + buildingBlockType, description, falsePositives, from, query, language, + license, outputIndex, savedId, timelineId, @@ -99,8 +116,12 @@ export const patchRules = async ({ index, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, threat, + timestampOverride, to, type, references, 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 fc95f0cfeb78e..7b793ffbdb362 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 @@ -73,6 +73,16 @@ import { LastSuccessMessage, LastFailureAt, LastFailureMessage, + Author, + AuthorOrUndefined, + LicenseOrUndefined, + RiskScoreMapping, + RiskScoreMappingOrUndefined, + SeverityMapping, + SeverityMappingOrUndefined, + TimestampOverrideOrUndefined, + BuildingBlockTypeOrUndefined, + RuleNameOverrideOrUndefined, } from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; import { Alert, SanitizedAlert } from '../../../../../alerts/common'; @@ -165,6 +175,8 @@ export const isRuleStatusFindTypes = ( export interface CreateRulesOptions { alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: Author; + buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; falsePositives: FalsePositives; @@ -181,13 +193,18 @@ export interface CreateRulesOptions { immutable: Immutable; index: IndexOrUndefined; interval: Interval; + license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; + riskScoreMapping: RiskScoreMapping; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; + severityMapping: SeverityMapping; tags: Tags; threat: Threat; + timestampOverride: TimestampOverrideOrUndefined; to: To; type: Type; references: References; @@ -202,6 +219,8 @@ export interface UpdateRulesOptions { savedObjectsClient: SavedObjectsClientContract; alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: Author; + buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; falsePositives: FalsePositives; @@ -217,13 +236,18 @@ export interface UpdateRulesOptions { ruleId: RuleIdOrUndefined; index: IndexOrUndefined; interval: Interval; + license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; + riskScoreMapping: RiskScoreMapping; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; + severityMapping: SeverityMapping; tags: Tags; threat: Threat; + timestampOverride: TimestampOverrideOrUndefined; to: To; type: Type; references: References; @@ -237,6 +261,8 @@ export interface PatchRulesOptions { savedObjectsClient: SavedObjectsClientContract; alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: DescriptionOrUndefined; enabled: EnabledOrUndefined; falsePositives: FalsePositivesOrUndefined; @@ -251,13 +277,18 @@ export interface PatchRulesOptions { filters: PartialFilter[]; index: IndexOrUndefined; interval: IntervalOrUndefined; + license: LicenseOrUndefined; maxSignals: MaxSignalsOrUndefined; riskScore: RiskScoreOrUndefined; + riskScoreMapping: RiskScoreMappingOrUndefined; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndexOrUndefined; name: NameOrUndefined; severity: SeverityOrUndefined; + severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; threat: ThreatOrUndefined; + timestampOverride: TimestampOverrideOrUndefined; to: ToOrUndefined; type: TypeOrUndefined; references: ReferencesOrUndefined; 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 c4792eaa97ee1..6466cc596d891 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 @@ -20,11 +20,14 @@ export const updatePrepackagedRules = async ( await Promise.all( rules.map(async (rule) => { const { + author, + building_block_type: buildingBlockType, description, false_positives: falsePositives, from, query, language, + license, saved_id: savedId, meta, filters: filtersObject, @@ -33,12 +36,16 @@ export const updatePrepackagedRules = async ( interval, max_signals: maxSignals, risk_score: riskScore, + risk_score_mapping: riskScoreMapping, + rule_name_override: ruleNameOverride, name, severity, + severity_mapping: severityMapping, tags, to, type, threat, + timestamp_override: timestampOverride, references, version, note, @@ -58,11 +65,14 @@ export const updatePrepackagedRules = async ( // or enable rules on the user when they were not expecting it if a rule updates return patchRules({ alertsClient, + author, + buildingBlockType, description, falsePositives, from, query, language, + license, outputIndex, rule: existingRule, savedId, @@ -73,9 +83,13 @@ export const updatePrepackagedRules = async ( interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, + timestampOverride, to, type, threat, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index 7812c66a74d1f..fdc0a61274e75 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -9,6 +9,8 @@ import { alertsClientMock } from '../../../../../alerts/server/mocks'; import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', alertsClient: alertsClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), @@ -19,6 +21,7 @@ export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ from: 'now-6m', query: 'user.name: root or user.name: admin', language: 'kuery', + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -30,11 +33,15 @@ export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Query with a rule id', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'query', references: ['http://www.example.com'], @@ -45,6 +52,8 @@ export const getUpdateRulesOptionsMock = (): UpdateRulesOptions => ({ }); export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ + author: ['Elastic'], + buildingBlockType: undefined, id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', alertsClient: alertsClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(), @@ -55,6 +64,7 @@ export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ from: 'now-6m', query: undefined, language: undefined, + license: 'Elastic License', savedId: 'savedId-123', timelineId: 'timelineid-123', timelineTitle: 'timeline-title-123', @@ -66,11 +76,15 @@ export const getUpdateMlRulesOptionsMock = (): UpdateRulesOptions => ({ interval: '5m', maxSignals: 100, riskScore: 80, + riskScoreMapping: [], + ruleNameOverride: undefined, outputIndex: 'output-1', name: 'Machine Learning Job', severity: 'high', + severityMapping: [], tags: [], threat: [], + timestampOverride: undefined, to: 'now', type: 'machine_learning', references: ['http://www.example.com'], 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 b3f327857dbb3..5cc68db25afc8 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 @@ -15,12 +15,15 @@ import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_save export const updateRules = async ({ alertsClient, + author, + buildingBlockType, savedObjectsClient, description, falsePositives, enabled, query, language, + license, outputIndex, savedId, timelineId, @@ -34,10 +37,14 @@ export const updateRules = async ({ interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, threat, + timestampOverride, to, type, references, @@ -54,10 +61,13 @@ export const updateRules = async ({ } const calculatedVersion = calculateVersion(rule.params.immutable, rule.params.version, { + author, + buildingBlockType, description, falsePositives, query, language, + license, outputIndex, savedId, timelineId, @@ -69,10 +79,14 @@ export const updateRules = async ({ interval, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, name, severity, + severityMapping, tags, threat, + timestampOverride, to, type, references, @@ -95,6 +109,8 @@ export const updateRules = async ({ actions: actions.map(transformRuleToAlertAction), throttle: null, params: { + author, + buildingBlockType, description, ruleId: rule.params.ruleId, falsePositives, @@ -102,6 +118,7 @@ export const updateRules = async ({ immutable: rule.params.immutable, query, language, + license, outputIndex, savedId, timelineId, @@ -111,8 +128,12 @@ export const updateRules = async ({ index, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, threat, + timestampOverride, to, type, references, 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 0f65b2a78ec4c..aa0512678b073 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 @@ -28,10 +28,13 @@ describe('utils', () => { test('returning the same version number if given an immutable but no updated version number', () => { expect( calculateVersion(true, 1, { + author: [], + buildingBlockType: undefined, description: 'some description change', falsePositives: undefined, query: undefined, language: undefined, + license: undefined, outputIndex: undefined, savedId: undefined, timelineId: undefined, @@ -43,11 +46,15 @@ describe('utils', () => { interval: undefined, maxSignals: undefined, riskScore: undefined, + riskScoreMapping: undefined, + ruleNameOverride: undefined, name: undefined, severity: undefined, + severityMapping: undefined, tags: undefined, threat: undefined, to: undefined, + timestampOverride: undefined, type: undefined, references: undefined, version: undefined, @@ -62,10 +69,13 @@ describe('utils', () => { test('returning an updated version number if given an immutable and an updated version number', () => { expect( calculateVersion(true, 2, { + author: [], + buildingBlockType: undefined, description: 'some description change', falsePositives: undefined, query: undefined, language: undefined, + license: undefined, outputIndex: undefined, savedId: undefined, timelineId: undefined, @@ -77,11 +87,15 @@ describe('utils', () => { interval: undefined, maxSignals: undefined, riskScore: undefined, + riskScoreMapping: undefined, + ruleNameOverride: undefined, name: undefined, severity: undefined, + severityMapping: undefined, tags: undefined, threat: undefined, to: undefined, + timestampOverride: undefined, type: undefined, references: undefined, version: undefined, @@ -96,10 +110,13 @@ describe('utils', () => { test('returning an updated version number if not given an immutable but but an updated description', () => { expect( calculateVersion(false, 1, { + author: [], + buildingBlockType: undefined, description: 'some description change', falsePositives: undefined, query: undefined, language: undefined, + license: undefined, outputIndex: undefined, savedId: undefined, timelineId: undefined, @@ -111,11 +128,15 @@ describe('utils', () => { interval: undefined, maxSignals: undefined, riskScore: undefined, + riskScoreMapping: undefined, + ruleNameOverride: undefined, name: undefined, severity: undefined, + severityMapping: undefined, tags: undefined, threat: undefined, to: undefined, + timestampOverride: undefined, type: undefined, references: undefined, version: 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 5c620a5df61f8..861d02a8203e6 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 @@ -31,6 +31,13 @@ import { ThreatOrUndefined, TypeOrUndefined, ReferencesOrUndefined, + AuthorOrUndefined, + BuildingBlockTypeOrUndefined, + LicenseOrUndefined, + RiskScoreMappingOrUndefined, + RuleNameOverrideOrUndefined, + SeverityMappingOrUndefined, + TimestampOverrideOrUndefined, } from '../../../../common/detection_engine/schemas/common/schemas'; import { PartialFilter } from '../types'; import { ListArrayOrUndefined } from '../../../../common/detection_engine/schemas/types'; @@ -49,11 +56,14 @@ export const calculateInterval = ( }; export interface UpdateProperties { + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: DescriptionOrUndefined; falsePositives: FalsePositivesOrUndefined; from: FromOrUndefined; query: QueryOrUndefined; language: LanguageOrUndefined; + license: LicenseOrUndefined; savedId: SavedIdOrUndefined; timelineId: TimelineIdOrUndefined; timelineTitle: TimelineTitleOrUndefined; @@ -64,11 +74,15 @@ export interface UpdateProperties { interval: IntervalOrUndefined; maxSignals: MaxSignalsOrUndefined; riskScore: RiskScoreOrUndefined; + riskScoreMapping: RiskScoreMappingOrUndefined; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndexOrUndefined; name: NameOrUndefined; severity: SeverityOrUndefined; + severityMapping: SeverityMappingOrUndefined; tags: TagsOrUndefined; threat: ThreatOrUndefined; + timestampOverride: TimestampOverrideOrUndefined; to: ToOrUndefined; type: TypeOrUndefined; references: ReferencesOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/post_rule.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/post_rule.sh index 432045634ba7b..ac551781d2042 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/post_rule.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/post_rule.sh @@ -24,7 +24,7 @@ do { -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules \ -d @${RULE} \ - | jq .; + | jq -S .; } & done diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json new file mode 100644 index 0000000000000..f0d7cb4ec914b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/rules/queries/query_with_mappings.json @@ -0,0 +1,44 @@ +{ + "description": "Makes external events actionable within Elastic Security! 🎬", + "enabled": false, + "index": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "packetbeat-*", + "winlogbeat-*" + ], + "language": "kuery", + "risk_score": 50, + "severity": "high", + "name": "External alerts", + "query": "event.type: \"alert\"", + "type": "query", + "author": ["Elastic"], + "building_block_type": "default", + "license": "Elastic License", + "risk_score_mapping": [ + { + "field": "event.risk_score", + "operator": "equals", + "value": "0" + } + ], + "rule_name_override": "event.message", + "severity_mapping":[ + { + "field": "event.severity", + "operator": "equals", + "value": "low", + "severity": "low" + }, + { + "field": "event.severity", + "operator": "equals", + "value": "medium", + "severity": "medium" + } + ], + "timestamp_override": "event.ingested" +} 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 50f6e7d9e9c10..7492422968062 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 @@ -20,6 +20,8 @@ export const sampleRuleAlertParams = ( maxSignals?: number | undefined, riskScore?: number | undefined ): RuleTypeParams => ({ + author: ['Elastic'], + buildingBlockType: 'default', ruleId: 'rule-1', description: 'Detecting root and admin users', falsePositives: [], @@ -29,11 +31,15 @@ export const sampleRuleAlertParams = ( from: 'now-6m', to: 'now', severity: 'high', + severityMapping: [], query: 'user.name: root or user.name: admin', language: 'kuery', + license: 'Elastic License', outputIndex: '.siem-signals', references: ['http://google.com'], riskScore: riskScore ? riskScore : 50, + riskScoreMapping: [], + ruleNameOverride: undefined, maxSignals: maxSignals ? maxSignals : 10000, note: '', anomalyThreshold: undefined, @@ -42,6 +48,7 @@ export const sampleRuleAlertParams = ( savedId: undefined, timelineId: undefined, timelineTitle: undefined, + timestampOverride: undefined, meta: undefined, threat: undefined, version: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index ad43932818836..e840ae96cf3c1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -64,11 +64,14 @@ describe('buildBulkBody', () => { status: 'open', rule: { actions: [], + author: ['Elastic'], + building_block_type: 'default', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', rule_id: 'rule-1', false_positives: [], max_signals: 10000, risk_score: 50, + risk_score_mapping: [], output_index: '.siem-signals', description: 'Detecting root and admin users', from: 'now-6m', @@ -76,10 +79,12 @@ describe('buildBulkBody', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', language: 'kuery', + license: 'Elastic License', name: 'rule-name', query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], throttle: 'no_actions', @@ -160,11 +165,14 @@ describe('buildBulkBody', () => { status: 'open', rule: { actions: [], + author: ['Elastic'], + building_block_type: 'default', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', rule_id: 'rule-1', false_positives: [], max_signals: 10000, risk_score: 50, + risk_score_mapping: [], output_index: '.siem-signals', description: 'Detecting root and admin users', from: 'now-6m', @@ -172,10 +180,12 @@ describe('buildBulkBody', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', language: 'kuery', + license: 'Elastic License', name: 'rule-name', query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', @@ -254,11 +264,14 @@ describe('buildBulkBody', () => { status: 'open', rule: { actions: [], + author: ['Elastic'], + building_block_type: 'default', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', rule_id: 'rule-1', false_positives: [], max_signals: 10000, risk_score: 50, + risk_score_mapping: [], output_index: '.siem-signals', description: 'Detecting root and admin users', from: 'now-6m', @@ -266,10 +279,12 @@ describe('buildBulkBody', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', language: 'kuery', + license: 'Elastic License', name: 'rule-name', query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', + severity_mapping: [], threat: [], tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', @@ -341,11 +356,14 @@ describe('buildBulkBody', () => { status: 'open', rule: { actions: [], + author: ['Elastic'], + building_block_type: 'default', id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', rule_id: 'rule-1', false_positives: [], max_signals: 10000, risk_score: 50, + risk_score_mapping: [], output_index: '.siem-signals', description: 'Detecting root and admin users', from: 'now-6m', @@ -353,10 +371,12 @@ describe('buildBulkBody', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: '5m', language: 'kuery', + license: 'Elastic License', name: 'rule-name', query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], type: 'query', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts index 9aef5a370b86a..ed632ee2576dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts @@ -43,6 +43,8 @@ describe('buildRule', () => { }); const expected: Partial = { actions: [], + author: ['Elastic'], + building_block_type: 'default', created_by: 'elastic', description: 'Detecting root and admin users', enabled: false, @@ -53,14 +55,17 @@ describe('buildRule', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: 'some interval', language: 'kuery', + license: 'Elastic License', max_signals: 10000, name: 'some-name', output_index: '.siem-signals', query: 'user.name: root or user.name: admin', references: ['http://google.com'], risk_score: 50, + risk_score_mapping: [], rule_id: 'rule-1', severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], to: 'now', @@ -106,6 +111,8 @@ describe('buildRule', () => { }); const expected: Partial = { actions: [], + author: ['Elastic'], + building_block_type: 'default', created_by: 'elastic', description: 'Detecting root and admin users', enabled: true, @@ -116,14 +123,17 @@ describe('buildRule', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: 'some interval', language: 'kuery', + license: 'Elastic License', max_signals: 10000, name: 'some-name', output_index: '.siem-signals', query: 'user.name: root or user.name: admin', references: ['http://google.com'], risk_score: 50, + risk_score_mapping: [], rule_id: 'rule-1', severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], to: 'now', @@ -158,6 +168,8 @@ describe('buildRule', () => { }); const expected: Partial = { actions: [], + author: ['Elastic'], + building_block_type: 'default', created_by: 'elastic', description: 'Detecting root and admin users', enabled: true, @@ -168,6 +180,7 @@ describe('buildRule', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], interval: 'some interval', language: 'kuery', + license: 'Elastic License', max_signals: 10000, name: 'some-name', note: '', @@ -175,8 +188,10 @@ describe('buildRule', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], risk_score: 50, + risk_score_mapping: [], rule_id: 'rule-1', severity: 'high', + severity_mapping: [], tags: ['some fake tag 1', 'some fake tag 2'], threat: [], to: 'now', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts index bde9c970b0c8c..fc8b26450c852 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts @@ -42,13 +42,16 @@ export const buildRule = ({ id, rule_id: ruleParams.ruleId ?? '(unknown rule_id)', actions, + author: ruleParams.author ?? [], + building_block_type: ruleParams.buildingBlockType, false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, timeline_id: ruleParams.timelineId, timeline_title: ruleParams.timelineTitle, meta: ruleParams.meta, max_signals: ruleParams.maxSignals, - risk_score: ruleParams.riskScore, + risk_score: ruleParams.riskScore, // TODO: Risk Score Override via risk_score_mapping + risk_score_mapping: ruleParams.riskScoreMapping ?? [], output_index: ruleParams.outputIndex, description: ruleParams.description, note: ruleParams.note, @@ -57,10 +60,13 @@ export const buildRule = ({ index: ruleParams.index, interval, language: ruleParams.language, - name, + license: ruleParams.license, + name, // TODO: Rule Name Override via rule_name_override query: ruleParams.query, references: ruleParams.references, - severity: ruleParams.severity, + rule_name_override: ruleParams.ruleNameOverride, + severity: ruleParams.severity, // TODO: Severity Override via severity_mapping + severity_mapping: ruleParams.severityMapping ?? [], tags, type: ruleParams.type, to: ruleParams.to, @@ -69,6 +75,7 @@ export const buildRule = ({ created_by: createdBy, updated_by: updatedBy, threat: ruleParams.threat ?? [], + timestamp_override: ruleParams.timestampOverride, // TODO: Timestamp Override via timestamp_override throttle, version: ruleParams.version, created_at: createdAt, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts index d60509b28f7da..0c56ed300cb48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.mock.ts @@ -19,6 +19,8 @@ export const getSignalParamsSchemaMock = (): Partial => ({ }); export const getSignalParamsSchemaDecodedMock = (): SignalParamsSchema => ({ + author: [], + buildingBlockType: null, description: 'Detecting root and admin users', falsePositives: [], filters: null, @@ -26,6 +28,7 @@ export const getSignalParamsSchemaDecodedMock = (): SignalParamsSchema => ({ immutable: false, index: null, language: 'kuery', + license: null, maxSignals: 100, meta: null, note: null, @@ -33,12 +36,16 @@ export const getSignalParamsSchemaDecodedMock = (): SignalParamsSchema => ({ query: 'user.name: root or user.name: admin', references: [], riskScore: 55, + riskScoreMapping: null, + ruleNameOverride: null, ruleId: 'rule-1', savedId: null, severity: 'high', + severityMapping: null, threat: null, timelineId: null, timelineTitle: null, + timestampOverride: null, to: 'now', type: 'query', version: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index 5f95f635a6bd8..2583cf2c8da91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -10,6 +10,8 @@ import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; const signalSchema = schema.object({ anomalyThreshold: schema.maybe(schema.number()), + author: schema.arrayOf(schema.string(), { defaultValue: [] }), + buildingBlockType: schema.nullable(schema.string()), description: schema.string(), note: schema.nullable(schema.string()), falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), @@ -18,6 +20,7 @@ const signalSchema = schema.object({ immutable: schema.boolean({ defaultValue: false }), index: schema.nullable(schema.arrayOf(schema.string())), language: schema.nullable(schema.string()), + license: schema.nullable(schema.string()), outputIndex: schema.nullable(schema.string()), savedId: schema.nullable(schema.string()), timelineId: schema.nullable(schema.string()), @@ -28,8 +31,13 @@ const signalSchema = schema.object({ filters: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), riskScore: schema.number(), + // TODO: Specify types explicitly since they're known? + riskScoreMapping: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + ruleNameOverride: schema.nullable(schema.string()), severity: schema.string(), + severityMapping: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), threat: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + timestampOverride: schema.nullable(schema.string()), to: schema.string(), type: schema.string(), references: schema.arrayOf(schema.string(), { defaultValue: [] }), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index 0fb743c9c3ed6..365222d62d322 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -28,6 +28,13 @@ import { Version, MetaOrUndefined, RuleId, + AuthorOrUndefined, + BuildingBlockTypeOrUndefined, + LicenseOrUndefined, + RiskScoreMappingOrUndefined, + RuleNameOverrideOrUndefined, + SeverityMappingOrUndefined, + TimestampOverrideOrUndefined, } from '../../../common/detection_engine/schemas/common/schemas'; import { LegacyCallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; @@ -38,6 +45,8 @@ export type PartialFilter = Partial; export interface RuleTypeParams { anomalyThreshold: AnomalyThresholdOrUndefined; + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; note: NoteOrUndefined; falsePositives: FalsePositives; @@ -46,6 +55,7 @@ export interface RuleTypeParams { immutable: Immutable; index: IndexOrUndefined; language: LanguageOrUndefined; + license: LicenseOrUndefined; outputIndex: OutputIndex; savedId: SavedIdOrUndefined; timelineId: TimelineIdOrUndefined; @@ -56,8 +66,12 @@ export interface RuleTypeParams { filters: PartialFilter[] | undefined; maxSignals: MaxSignals; riskScore: RiskScore; + riskScoreMapping: RiskScoreMappingOrUndefined; + ruleNameOverride: RuleNameOverrideOrUndefined; severity: Severity; + severityMapping: SeverityMappingOrUndefined; threat: ThreatOrUndefined; + timestampOverride: TimestampOverrideOrUndefined; to: To; type: RuleType; references: References; diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 816df9c133ea1..6ad9cf4cd5baf 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -179,6 +179,7 @@ export const binaryToString = (res: any, callback: any): void => { */ export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], + author: [], created_by: 'elastic', description: 'Simple Rule Query', enabled: true, @@ -192,10 +193,12 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => output_index: '.siem-signals-default', max_signals: 100, risk_score: 1, + risk_score_mapping: [], name: 'Simple Rule Query', query: 'user.name: root or user.name: admin', references: [], severity: 'high', + severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', @@ -307,6 +310,7 @@ export const ruleToNdjson = (rule: Partial): Buffer => { */ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ actions: [], + author: [], name: 'Complex Rule Query', description: 'Complex Rule Query', false_positives: [ @@ -314,6 +318,7 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ 'some text string about why another condition could be a false positive', ], risk_score: 1, + risk_score_mapping: [], rule_id: ruleId, filters: [ { @@ -340,6 +345,7 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ to: 'now', from: 'now-6m', severity: 'high', + severity_mapping: [], language: 'kuery', type: 'query', threat: [ @@ -391,6 +397,7 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ */ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], + author: [], created_by: 'elastic', name: 'Complex Rule Query', description: 'Complex Rule Query', @@ -399,6 +406,7 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => 'some text string about why another condition could be a false positive', ], risk_score: 1, + risk_score_mapping: [], rule_id: ruleId, filters: [ { @@ -426,6 +434,7 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => to: 'now', from: 'now-6m', severity: 'high', + severity_mapping: [], language: 'kuery', type: 'query', threat: [