From 3f6e1130f227b765954262fb8407acb2a743eb9b Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 29 Jun 2020 23:39:54 -0600 Subject: [PATCH 1/4] Adds additional fields to rules schema and create rule UI --- src/cli/cluster/cluster_manager.ts | 5 + .../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.ts | 14 ++ .../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/risk_score_mapping/index.tsx | 172 ++++++++++++++++ .../rules/risk_score_mapping/translations.tsx | 57 ++++++ .../rules/severity_mapping/index.tsx | 191 ++++++++++++++++++ .../rules/severity_mapping/translations.tsx | 57 ++++++ .../components/rules/step_about_rule/data.tsx | 2 +- .../rules/step_about_rule/default_value.ts | 7 +- .../rules/step_about_rule/index.test.tsx | 14 +- .../rules/step_about_rule/index.tsx | 184 +++++++++++------ .../rules/step_about_rule/schema.tsx | 114 +++++++++-- .../detection_engine/rules/types.ts | 18 ++ .../rules/all/__mocks__/mock.ts | 8 +- .../rules/create/helpers.test.ts | 28 ++- .../detection_engine/rules/create/index.tsx | 3 + .../detection_engine/rules/helpers.test.tsx | 7 +- .../pages/detection_engine/rules/helpers.tsx | 16 +- .../pages/detection_engine/rules/types.ts | 9 +- .../routes/__mocks__/request_responses.ts | 7 + .../routes/__mocks__/utils.ts | 4 + .../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 | 28 +++ .../rules/update_prepacked_rules.ts | 14 ++ .../rules/update_rules.mock.ts | 14 ++ .../detection_engine/rules/update_rules.ts | 14 ++ .../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/signal_params_schema.mock.ts | 7 + .../signals/signal_params_schema.ts | 8 + .../server/lib/detection_engine/types.ts | 14 ++ .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 70 files changed, 1605 insertions(+), 104 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/src/cli/cluster/cluster_manager.ts b/src/cli/cluster/cluster_manager.ts index 09f9bb2333dbe..db8821ac3fc16 100644 --- a/src/cli/cluster/cluster_manager.ts +++ b/src/cli/cluster/cluster_manager.ts @@ -266,6 +266,11 @@ export class ClusterManager { fromRoot('x-pack/plugins/apm/e2e'), fromRoot('x-pack/plugins/apm/scripts'), fromRoot('x-pack/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes, + fromRoot('x-pack/plugins/case/server/scripts'), + fromRoot('x-pack/plugins/lists/scripts'), + fromRoot('x-pack/plugins/lists/server/scripts'), + fromRoot('x-pack/plugins/security_solution/server/lib/detection_engine/scripts'), + fromRoot('x-pack/plugins/security_solution/scripts'), 'plugins/java_languageserver', ]; 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.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts index 9803a80f57857..5f68c49a11bfa 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,6 +55,13 @@ import { filters, meta, note, + author, + building_block_type, + license, + rule_name_override, + timestamp_override, + risk_score_mapping, + severity_mapping, } from '../common/schemas'; import { DefaultListArray } from '../types/lists_default_array'; @@ -120,9 +127,16 @@ export const dependentRulesSchema = t.partial({ */ export const partialRulesSchema = t.partial({ actions, + author, + building_block_type, + license, throttle, + risk_score_mapping, + rule_name_override, + severity_mapping, 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/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..39eab38e842ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/risk_score_mapping/index.tsx @@ -0,0 +1,172 @@ +/* + * 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, { 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'; + +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 isInvalid = field.errors.length > 0 && form.isSubmitted; + // const errorMessage = field.errors.length ? (field.errors[0].message as string) : null; + const [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected] = useState(false); + // const [riskScoreField, setRiskScoreField] = useState(); + + 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} + + + + + + + + {}} + aria-label="Use aria labels when no actual label is in use" + /> + + + + + + {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..69673b4df6719 --- /dev/null +++ b/x-pack/plugins/security_solution/public/alerts/components/rules/severity_mapping/index.tsx @@ -0,0 +1,191 @@ +/* + * 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, { 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'; + +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, + options, +}: SeverityFieldProps) => { + // const isInvalid = field.errors.length > 0 && form.isSubmitted; + // const errorMessage = field.errors.length ? (field.errors[0].message as string) : null; + const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); + // const [severityField, setSeverityField] = useState(); + + 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..9a69df4fd056e 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', + severity: { value: 'low' }, riskScore: 50, 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..528b198f4aa94 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', + severity: { value: 'low' }, 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', + severity: { value: 'low' }, 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..db49120217207 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,39 @@ 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', - } - ), }, references: { label: i18n.translate( @@ -135,6 +155,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 +219,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/types.ts b/x-pack/plugins/security_solution/public/alerts/containers/detection_engine/rules/types.ts index ab9b88fb81fa7..8566cfb91f60b 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. @@ -101,21 +112,28 @@ export const RuleSchema = t.intersection([ throttle: t.union([t.string, t.null]), }), t.partial({ + author, + 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, + risk_score_mapping, + rule_name_override, saved_id: t.string, + severity_mapping, 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/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..8859a228fee70 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 @@ -150,11 +150,17 @@ export const mockRuleWithEverything = (id: string): Rule => ({ 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', + severity: { value: 'low' }, riskScore: 21, references: ['www.test.co'], falsePositives: ['test'], 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..d7e767a34116b 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,17 @@ describe('helpers', () => { test('returns formatted object as AboutStepRuleJson', () => { const result: AboutStepRuleJson = formatAboutStepData(mockData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + isBuildingBlock: false, + license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, - severity: 'low', + ruleNameOverride: '', + severity: { value: 'low' }, tags: ['tag1', 'tag2'], threat: [ { @@ -364,6 +368,7 @@ describe('helpers', () => { ], }, ], + timestampOverride: '', }; expect(result).toEqual(expected); @@ -377,13 +382,17 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + isBuildingBlock: false, + license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, - severity: 'low', + ruleNameOverride: '', + severity: { value: 'low' }, tags: ['tag1', 'tag2'], threat: [ { @@ -402,6 +411,7 @@ describe('helpers', () => { ], }, ], + timestampOverride: '', }; expect(result).toEqual(expected); @@ -414,12 +424,16 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], description: '24/7', false_positives: ['test'], + isBuildingBlock: false, + license: 'Elastic License', name: 'Query with rule-id', references: ['www.test.co'], risk_score: 21, - severity: 'low', + ruleNameOverride: '', + severity: { value: 'low' }, tags: ['tag1', 'tag2'], threat: [ { @@ -438,6 +452,7 @@ describe('helpers', () => { ], }, ], + timestampOverride: '', }; expect(result).toEqual(expected); @@ -481,13 +496,17 @@ describe('helpers', () => { }; const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { + author: ['Elastic'], + isBuildingBlock: false, + 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, - severity: 'low', + ruleNameOverride: '', + severity: { value: 'low' }, tags: ['tag1', 'tag2'], threat: [ { @@ -496,6 +515,7 @@ describe('helpers', () => { technique: [{ id: '456', name: 'technique1', reference: 'technique reference' }], }, ], + timestampOverride: '', }; expect(result).toEqual(expected); 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', + ruleNameOverride: '', + severity: { value: 'low' }, tags: ['tag1', 'tag2'], threat: [ { @@ -106,6 +110,7 @@ describe('rule helpers', () => { ], }, ], + timestampOverride: '', }; 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..ae20c2fa7db3a 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,14 @@ export const getHumanizedDuration = (from: string, interval: string): string => export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRule => { const { name, description, note } = determineDetailsValue(rule, detailsView); const { + // TODO: Update types mapping + // 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, @@ -124,13 +132,19 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu threat, } = rule; + // TODO: Update types mapping return { isNew: false, + author: [], + isBuildingBlock: false, + license: 'Elastic License', + ruleNameOverride: '', + timestampOverride: '', name, description, note: note!, references, - severity, + severity: { value: severity }, tags, riskScore, falsePositives, 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..b99edf1e3b738 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 @@ -52,13 +52,18 @@ interface StepRuleData { isNew: boolean; } export interface AboutStepRule extends StepRuleData { + author: string[]; name: string; description: string; - severity: string; + isBuildingBlock: boolean; + severity: { value: string }; riskScore: number; references: string[]; falsePositives: string[]; + license: string; + ruleNameOverride: string; tags: string[]; + timestampOverride: string; threat: IMitreEnterpriseAttack[]; note: string; } @@ -106,7 +111,7 @@ export interface DefineStepRuleJson { export interface AboutStepRuleJson { name: string; description: string; - severity: string; + severity: { value: string }; risk_score: number; references: string[]; false_positives: 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/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..48ee21600391c 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..5e342a571fce5 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,13 @@ import { LastSuccessMessage, LastFailureAt, LastFailureMessage, + AuthorOrUndefined, + LicenseOrUndefined, + RiskScoreMappingOrUndefined, + 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 +172,8 @@ export const isRuleStatusFindTypes = ( export interface CreateRulesOptions { alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; falsePositives: FalsePositives; @@ -181,13 +190,18 @@ export interface CreateRulesOptions { immutable: Immutable; index: IndexOrUndefined; interval: Interval; + license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; + riskScoreMapping: RiskScoreMappingOrUndefined; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; + severityMapping: SeverityMappingOrUndefined; tags: Tags; threat: Threat; + timestampOverride: TimestampOverrideOrUndefined; to: To; type: Type; references: References; @@ -202,6 +216,8 @@ export interface UpdateRulesOptions { savedObjectsClient: SavedObjectsClientContract; alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; falsePositives: FalsePositives; @@ -217,13 +233,18 @@ export interface UpdateRulesOptions { ruleId: RuleIdOrUndefined; index: IndexOrUndefined; interval: Interval; + license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; + riskScoreMapping: RiskScoreMappingOrUndefined; + ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; + severityMapping: SeverityMappingOrUndefined; tags: Tags; threat: Threat; + timestampOverride: TimestampOverrideOrUndefined; to: To; type: Type; references: References; @@ -237,6 +258,8 @@ export interface PatchRulesOptions { savedObjectsClient: SavedObjectsClientContract; alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; + author: AuthorOrUndefined; + buildingBlockType: BuildingBlockTypeOrUndefined; description: DescriptionOrUndefined; enabled: EnabledOrUndefined; falsePositives: FalsePositivesOrUndefined; @@ -251,13 +274,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..23fafdd3c4ce8 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, 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/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 90484a46dc6d3..3a72f6d32a20f 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 { CallAPIOptions } 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/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e6e9111e6b43c..3a9f2234f1fb7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13770,8 +13770,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel": "MITRE ATT&CK\\u2122", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel": "名前", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel": "参照URL", - "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRiskScoreLabel": "リスクスコア", - "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel": "深刻度", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsHelpText": "このルールの1つ以上のカスタム識別タグを入力します。新しいタグを開始するには、各タグの後でEnterを押します。", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsLabel": "タグ", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText": "生成されたシグナルを調査するときにテンプレートとして使用する既存のタイムラインを選択します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7086b48290c7f..4af8c9db40de5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13775,8 +13775,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel": "MITRE ATT&CK\\u2122", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNameLabel": "名称", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel": "引用 URL", - "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldRiskScoreLabel": "风险分数", - "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel": "严重性", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsHelpText": "为此规则键入一个或多个定制识别标记。在每个标记后按 Enter 键可开始新的标记。", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTagsLabel": "标记", "xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateHelpText": "选择现有时间线以将其用作调查生成的信号时的模板。", From 37f791572896918f5a6457f928e0d2c00f8716b2 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 30 Jun 2020 20:02:32 -0600 Subject: [PATCH 2/4] Fixing api integration tests --- .../server/lib/detection_engine/rules/update_rules.ts | 7 +++++++ x-pack/test/detection_engine_api_integration/utils.ts | 9 +++++++++ 2 files changed, 16 insertions(+) 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 23fafdd3c4ce8..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 @@ -109,6 +109,8 @@ export const updateRules = async ({ actions: actions.map(transformRuleToAlertAction), throttle: null, params: { + author, + buildingBlockType, description, ruleId: rule.params.ruleId, falsePositives, @@ -116,6 +118,7 @@ export const updateRules = async ({ immutable: rule.params.immutable, query, language, + license, outputIndex, savedId, timelineId, @@ -125,8 +128,12 @@ export const updateRules = async ({ index, maxSignals, riskScore, + riskScoreMapping, + ruleNameOverride, severity, + severityMapping, threat, + timestampOverride, to, type, 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: [ From a3d0075df76b6025e0231e9022023794f3bb4574 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Wed, 1 Jul 2020 17:13:16 -0600 Subject: [PATCH 3/4] Reworks form state, adds fields to signals mapping, and fixes rehydration in edit mode --- .../rules/description_step/index.tsx | 19 ++++++-- .../rules/risk_score_mapping/index.tsx | 30 +++++++++--- .../rules/severity_mapping/index.tsx | 47 ++++++++++++++----- .../rules/step_about_rule/default_value.ts | 4 +- .../rules/step_about_rule/index.test.tsx | 8 ++-- .../rules/step_about_rule/schema.tsx | 9 +++- .../rules/all/__mocks__/mock.ts | 10 +++- .../rules/create/helpers.test.ts | 36 +++++++------- .../detection_engine/rules/create/helpers.ts | 27 +++++++++-- .../detection_engine/rules/helpers.test.tsx | 8 ++-- .../pages/detection_engine/rules/helpers.tsx | 36 +++++++------- .../pages/detection_engine/rules/types.ts | 32 +++++++++++-- .../routes/index/signals_mapping.json | 44 +++++++++++++++++ .../lib/detection_engine/rules/types.ts | 2 +- .../signals/build_bulk_body.test.ts | 20 ++++++++ .../signals/build_rule.test.ts | 15 ++++++ .../detection_engine/signals/build_rule.ts | 13 +++-- 17 files changed, 281 insertions(+), 79 deletions(-) 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 index 39eab38e842ff..1a2d7549d58bc 100644 --- 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 @@ -15,7 +15,7 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useMemo, useState } from 'react'; +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'; @@ -41,10 +41,23 @@ interface RiskScoreFieldProps { } export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskScoreFieldProps) => { - // const isInvalid = field.errors.length > 0 && form.isSubmitted; - // const errorMessage = field.errors.length ? (field.errors[0].message as string) : null; const [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected] = useState(false); - // const [riskScoreField, setRiskScoreField] = useState(); + + const updateRiskScoreMapping = useCallback( + (event) => { + field.setValue({ + value: field.value.value, + mapping: [ + { + field: event.target.value, + operator: 'equals', + value: '', + }, + ], + }); + }, + [field] + ); const severityLabel = useMemo(() => { return ( @@ -97,7 +110,7 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco describedByIds={idAria ? [idAria] : undefined} > {}} - aria-label="Use aria labels when no actual label is in use" + data-test-subj={`detectionEngineStepAboutRuleRiskScoreMappingValue`} + idAria={`detectionEngineStepAboutRuleRiskScoreMappingValue`} + isDisabled={false} + onChange={updateRiskScoreMapping.bind(null)} + value={field.value.mapping?.[0]?.field ?? ''} /> 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 index 69673b4df6719..bcf68877a6433 100644 --- 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 @@ -15,7 +15,7 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useMemo, useState } from 'react'; +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'; @@ -46,13 +46,29 @@ export const SeverityField = ({ dataTestSubj, field, idAria, - indices, + indices, // TODO: To be used with autocomplete fields once https://github.com/elastic/kibana/pull/67013 is merged options, }: SeverityFieldProps) => { - // const isInvalid = field.errors.length > 0 && form.isSubmitted; - // const errorMessage = field.errors.length ? (field.errors[0].message as string) : null; const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); - // const [severityField, setSeverityField] = useState(); + + const updateSeverityMapping = useCallback( + (index: number, severity: string, mappingField: string, event) => { + field.setValue({ + value: field.value.value, + mapping: [ + ...field.value.mapping.slice(0, index), + { + ...field.value.mapping[index], + [mappingField]: event.target.value, + operator: 'equals', + severity, + }, + ...field.value.mapping.slice(index + 1), + ], + }); + }, + [field] + ); const severityLabel = useMemo(() => { return ( @@ -159,18 +175,23 @@ export const SeverityField = ({ - - {}} /> + 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 9a69df4fd056e..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 @@ -20,8 +20,8 @@ export const stepAboutDefaultValue: AboutStepRule = { description: '', isBuildingBlock: false, isNew: true, - severity: { value: 'low' }, - riskScore: 50, + severity: { value: 'low', mapping: [] }, + riskScore: { value: 50, mapping: [] }, references: [''], falsePositives: [''], license: '', 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 528b198f4aa94..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 @@ -174,8 +174,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: 50, - severity: { value: 'low' }, + riskScore: { value: 50, mapping: [] }, + severity: { value: 'low', mapping: [] }, tags: [], threat: [ { @@ -232,8 +232,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: 80, - severity: { value: '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/schema.tsx b/x-pack/plugins/security_solution/public/alerts/components/rules/step_about_rule/schema.tsx index db49120217207..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 @@ -112,8 +112,13 @@ export const schema: FormSchema = { }, }, riskScore: { - type: FIELD_TYPES.RANGE, - serializer: (input: string) => Number(input), + value: { + type: FIELD_TYPES.RANGE, + serializer: (input: string) => Number(input), + }, + mapping: { + type: FIELD_TYPES.TEXT, + }, }, references: { label: i18n.translate( 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 8859a228fee70..0862d803eb268 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 @@ -78,6 +78,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 +114,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 +128,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,6 +151,7 @@ export const mockRuleWithEverything = (id: string): Rule => ({ }, ], throttle: 'no_actions', + timestamp_override: 'event.ingested', note: '# this is some markdown documentation', version: 1, }); @@ -160,8 +166,8 @@ export const mockAboutStepRule = (isNew = false): AboutStepRule => ({ license: 'Elastic License', name: 'Query with rule-id', description: '24/7', - severity: { value: '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 d7e767a34116b..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 @@ -342,14 +342,15 @@ describe('helpers', () => { author: ['Elastic'], description: '24/7', false_positives: ['test'], - isBuildingBlock: false, license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, - ruleNameOverride: '', - severity: { value: 'low' }, + risk_score_mapping: [], + rule_name_override: '', + severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -368,7 +369,7 @@ describe('helpers', () => { ], }, ], - timestampOverride: '', + timestamp_override: '', }; expect(result).toEqual(expected); @@ -385,14 +386,15 @@ describe('helpers', () => { author: ['Elastic'], description: '24/7', false_positives: ['test'], - isBuildingBlock: false, license: 'Elastic License', name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, - ruleNameOverride: '', - severity: { value: 'low' }, + risk_score_mapping: [], + rule_name_override: '', + severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -411,7 +413,7 @@ describe('helpers', () => { ], }, ], - timestampOverride: '', + timestamp_override: '', }; expect(result).toEqual(expected); @@ -427,13 +429,14 @@ describe('helpers', () => { author: ['Elastic'], description: '24/7', false_positives: ['test'], - isBuildingBlock: false, license: 'Elastic License', name: 'Query with rule-id', references: ['www.test.co'], risk_score: 21, - ruleNameOverride: '', - severity: { value: 'low' }, + risk_score_mapping: [], + rule_name_override: '', + severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -452,7 +455,7 @@ describe('helpers', () => { ], }, ], - timestampOverride: '', + timestamp_override: '', }; expect(result).toEqual(expected); @@ -497,7 +500,6 @@ describe('helpers', () => { const result: AboutStepRuleJson = formatAboutStepData(mockStepData); const expected = { author: ['Elastic'], - isBuildingBlock: false, license: 'Elastic License', description: '24/7', false_positives: ['test'], @@ -505,8 +507,10 @@ describe('helpers', () => { note: '# this is some markdown documentation', references: ['www.test.co'], risk_score: 21, - ruleNameOverride: '', - severity: { value: 'low' }, + risk_score_mapping: [], + rule_name_override: '', + severity: 'low', + severity_mapping: [], tags: ['tag1', 'tag2'], threat: [ { @@ -515,7 +519,7 @@ describe('helpers', () => { technique: [{ id: '456', name: 'technique1', reference: 'technique reference' }], }, ], - timestampOverride: '', + 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/helpers.test.tsx b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.test.tsx index 70320e1668a57..b467f3334508d 100644 --- a/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/alerts/pages/detection_engine/rules/helpers.test.tsx @@ -89,9 +89,9 @@ describe('rule helpers', () => { name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], - riskScore: 21, - ruleNameOverride: '', - severity: { value: 'low' }, + riskScore: { value: 21, mapping: [] }, + ruleNameOverride: 'message', + severity: { value: 'low', mapping: [] }, tags: ['tag1', 'tag2'], threat: [ { @@ -110,7 +110,7 @@ describe('rule helpers', () => { ], }, ], - timestampOverride: '', + 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 ae20c2fa7db3a..d440519290583 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,14 +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 { - // TODO: Update types mapping - // author, - // building_block_type: buildingBlockType, - // license, - // risk_score_mapping: riskScoreMapping, - // rule_name_override: ruleNameOverride, - // severity_mapping: severityMapping, - // timestamp_override: timestampOverride, + 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, @@ -132,21 +131,26 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu threat, } = rule; - // TODO: Update types mapping return { isNew: false, - author: [], - isBuildingBlock: false, - license: 'Elastic License', - ruleNameOverride: '', - timestampOverride: '', + author, + isBuildingBlock: buildingBlockType !== undefined, + license, + ruleNameOverride, + timestampOverride, name, description, note: note!, references, - severity: { value: 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 b99edf1e3b738..921deb121bc21 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; @@ -56,8 +65,8 @@ export interface AboutStepRule extends StepRuleData { name: string; description: string; isBuildingBlock: boolean; - severity: { value: string }; - riskScore: number; + severity: AboutStepSeverity; + riskScore: AboutStepRiskScore; references: string[]; falsePositives: string[]; license: string; @@ -73,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[]; @@ -109,14 +128,21 @@ export interface DefineStepRuleJson { } export interface AboutStepRuleJson { + author: Author; + building_block_type: BuildingBlockType; name: string; description: string; - severity: { value: 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/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/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 5e342a571fce5..d9acad2de5ea2 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 @@ -176,7 +176,7 @@ export interface CreateRulesOptions { buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; - falsePositives: FalsePositives; + falsePositives: FalsePositives; // TODO: Why isn't this FalsePositivesOrUndefined? from: From; query: QueryOrUndefined; language: LanguageOrUndefined; 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..7d9376ed3dc13 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, From ac86c2b4e748d3890e37a1f58ef583c1e2c2e363 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Wed, 1 Jul 2020 20:50:08 -0600 Subject: [PATCH 4/4] Type fixes and pr comments --- .../schemas/response/rules_schema.mocks.ts | 3 +++ .../schemas/response/rules_schema.ts | 14 +++++++----- .../rules/risk_score_mapping/index.tsx | 12 +++++----- .../rules/severity_mapping/index.tsx | 22 ++++++++++--------- .../detection_engine/rules/api.test.ts | 2 +- .../containers/detection_engine/rules/mock.ts | 9 ++++++++ .../detection_engine/rules/types.ts | 6 ++--- .../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 | 3 +++ .../pages/detection_engine/rules/helpers.tsx | 6 ++--- .../pages/detection_engine/rules/types.ts | 2 +- .../detection_engine/routes/rules/utils.ts | 6 ++--- .../lib/detection_engine/rules/types.ts | 17 ++++++++------ .../detection_engine/signals/build_rule.ts | 6 ++--- 16 files changed, 78 insertions(+), 42 deletions(-) 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 5f68c49a11bfa..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,15 +55,17 @@ import { filters, meta, note, - author, building_block_type, license, rule_name_override, timestamp_override, - risk_score_mapping, - severity_mapping, } 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 @@ -71,6 +73,7 @@ import { DefaultListArray } from '../types/lists_default_array'; * output schema. */ export const requiredRulesSchema = t.type({ + author: DefaultStringArray, description, enabled, false_positives, @@ -82,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, @@ -127,13 +132,10 @@ export const dependentRulesSchema = t.partial({ */ export const partialRulesSchema = t.partial({ actions, - author, building_block_type, license, throttle, - risk_score_mapping, rule_name_override, - severity_mapping, status: job_status, status_date, timestamp_override, 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 index 1a2d7549d58bc..bdf1ac600faef 100644 --- 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 @@ -20,6 +20,7 @@ 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; @@ -45,8 +46,9 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco const updateRiskScoreMapping = useCallback( (event) => { + const values = field.value as AboutStepRiskScore; field.setValue({ - value: field.value.value, + value: values.value, mapping: [ { field: event.target.value, @@ -163,11 +165,11 @@ export const RiskScoreField = ({ dataTestSubj, field, idAria, indices }: RiskSco 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 index bcf68877a6433..47c45a6bdf88d 100644 --- 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 @@ -21,6 +21,7 @@ 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; @@ -53,17 +54,18 @@ export const SeverityField = ({ const updateSeverityMapping = useCallback( (index: number, severity: string, mappingField: string, event) => { + const values = field.value as AboutStepSeverity; field.setValue({ - value: field.value.value, + value: values.value, mapping: [ - ...field.value.mapping.slice(0, index), + ...values.mapping.slice(0, index), { - ...field.value.mapping[index], + ...values.mapping[index], [mappingField]: event.target.value, operator: 'equals', severity, }, - ...field.value.mapping.slice(index + 1), + ...values.mapping.slice(index + 1), ], }); }, @@ -177,20 +179,20 @@ export const SeverityField = ({ 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 8566cfb91f60b..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 @@ -87,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, @@ -100,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, @@ -112,7 +115,6 @@ export const RuleSchema = t.intersection([ throttle: t.union([t.string, t.null]), }), t.partial({ - author, building_block_type, anomaly_threshold: t.number, filters: t.array(t.unknown), @@ -125,10 +127,8 @@ export const RuleSchema = t.intersection([ machine_learning_job_id: t.string, output_index: t.string, query: t.string, - risk_score_mapping, rule_name_override, saved_id: t.string, - severity_mapping, status: t.string, status_date: t.string, timeline_id: t.string, 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 0862d803eb268..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', 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 d440519290583..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 @@ -135,9 +135,9 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu isNew: false, author, isBuildingBlock: buildingBlockType !== undefined, - license, - ruleNameOverride, - timestampOverride, + license: license ?? '', + ruleNameOverride: ruleNameOverride ?? '', + timestampOverride: timestampOverride ?? '', name, description, note: note!, 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 921deb121bc21..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 @@ -129,7 +129,7 @@ export interface DefineStepRuleJson { export interface AboutStepRuleJson { author: Author; - building_block_type: BuildingBlockType; + building_block_type?: BuildingBlockType; name: string; description: string; license: License; 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 48ee21600391c..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,7 @@ export const transformAlertToRule = ( ruleStatus?: SavedObject ): Partial => { return pickBy((value: unknown) => value != null, { - author: alert.params.author, + author: alert.params.author ?? [], actions: ruleActions?.actions ?? [], building_block_type: alert.params.buildingBlockType, created_at: alert.createdAt.toISOString(), @@ -128,7 +128,7 @@ export const transformAlertToRule = ( max_signals: alert.params.maxSignals, machine_learning_job_id: alert.params.machineLearningJobId, risk_score: alert.params.riskScore, - risk_score_mapping: alert.params.riskScoreMapping, + risk_score_mapping: alert.params.riskScoreMapping ?? [], rule_name_override: alert.params.ruleNameOverride, name: alert.name, query: alert.params.query, @@ -138,7 +138,7 @@ export const transformAlertToRule = ( timeline_title: alert.params.timelineTitle, meta: alert.params.meta, severity: alert.params.severity, - severity_mapping: alert.params.severityMapping, + severity_mapping: alert.params.severityMapping ?? [], updated_by: alert.updatedBy ?? 'elastic', tags: transformTags(alert.tags), to: alert.params.to, 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 d9acad2de5ea2..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,9 +73,12 @@ import { LastSuccessMessage, LastFailureAt, LastFailureMessage, + Author, AuthorOrUndefined, LicenseOrUndefined, + RiskScoreMapping, RiskScoreMappingOrUndefined, + SeverityMapping, SeverityMappingOrUndefined, TimestampOverrideOrUndefined, BuildingBlockTypeOrUndefined, @@ -172,11 +175,11 @@ export const isRuleStatusFindTypes = ( export interface CreateRulesOptions { alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; - author: AuthorOrUndefined; + author: Author; buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; - falsePositives: FalsePositives; // TODO: Why isn't this FalsePositivesOrUndefined? + falsePositives: FalsePositives; from: From; query: QueryOrUndefined; language: LanguageOrUndefined; @@ -193,12 +196,12 @@ export interface CreateRulesOptions { license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; - riskScoreMapping: RiskScoreMappingOrUndefined; + riskScoreMapping: RiskScoreMapping; ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; - severityMapping: SeverityMappingOrUndefined; + severityMapping: SeverityMapping; tags: Tags; threat: Threat; timestampOverride: TimestampOverrideOrUndefined; @@ -216,7 +219,7 @@ export interface UpdateRulesOptions { savedObjectsClient: SavedObjectsClientContract; alertsClient: AlertsClient; anomalyThreshold: AnomalyThresholdOrUndefined; - author: AuthorOrUndefined; + author: Author; buildingBlockType: BuildingBlockTypeOrUndefined; description: Description; enabled: Enabled; @@ -236,12 +239,12 @@ export interface UpdateRulesOptions { license: LicenseOrUndefined; maxSignals: MaxSignals; riskScore: RiskScore; - riskScoreMapping: RiskScoreMappingOrUndefined; + riskScoreMapping: RiskScoreMapping; ruleNameOverride: RuleNameOverrideOrUndefined; outputIndex: OutputIndex; name: Name; severity: Severity; - severityMapping: SeverityMappingOrUndefined; + severityMapping: SeverityMapping; tags: Tags; threat: Threat; timestampOverride: TimestampOverrideOrUndefined; 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 7d9376ed3dc13..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,7 +42,7 @@ export const buildRule = ({ id, rule_id: ruleParams.ruleId ?? '(unknown rule_id)', actions, - author: ruleParams.author, + author: ruleParams.author ?? [], building_block_type: ruleParams.buildingBlockType, false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, @@ -51,7 +51,7 @@ export const buildRule = ({ meta: ruleParams.meta, max_signals: ruleParams.maxSignals, risk_score: ruleParams.riskScore, // TODO: Risk Score Override via risk_score_mapping - risk_score_mapping: ruleParams.riskScoreMapping, + risk_score_mapping: ruleParams.riskScoreMapping ?? [], output_index: ruleParams.outputIndex, description: ruleParams.description, note: ruleParams.note, @@ -66,7 +66,7 @@ export const buildRule = ({ references: ruleParams.references, rule_name_override: ruleParams.ruleNameOverride, severity: ruleParams.severity, // TODO: Severity Override via severity_mapping - severity_mapping: ruleParams.severityMapping, + severity_mapping: ruleParams.severityMapping ?? [], tags, type: ruleParams.type, to: ruleParams.to,