From d0959ec3418a7f0a42df8471b1d1dacfd79cb161 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Wed, 29 Jun 2022 11:43:01 -0700 Subject: [PATCH] Remove more duplicated schemas --- .../add_prepackaged_rules_schema.mock.ts | 92 +-- .../add_prepackaged_rules_schema.test.ts | 676 +++--------------- .../request/add_prepackaged_rules_schema.ts | 183 +---- ..._prepackaged_rules_type_dependents.test.ts | 74 -- .../add_prepackaged_rules_type_dependents.ts | 96 +-- .../request/import_rules_schema.mock.ts | 90 +-- .../request/import_rules_schema.test.ts | 657 ++--------------- .../schemas/request/import_rules_schema.ts | 192 +---- .../import_rules_type_dependents.test.ts | 26 - .../request/import_rules_type_dependents.ts | 93 +-- .../request/patch_rules_bulk_schema.ts | 2 +- .../request/patch_rules_schema.mock.ts | 5 +- .../request/patch_rules_schema.test.ts | 133 ++-- .../schemas/request/patch_rules_schema.ts | 121 +--- .../patch_rules_type_dependents.test.ts | 64 -- .../request/patch_rules_type_dependents.ts | 57 +- .../schemas/request/rule_schemas.ts | 50 +- .../rules/add_prepackaged_rules_route.test.ts | 6 +- .../rules/add_prepackaged_rules_route.ts | 3 +- .../routes/rules/create_rules_bulk_route.ts | 28 +- .../routes/rules/create_rules_route.ts | 23 +- .../routes/rules/import_rules_route.ts | 4 +- .../rules/patch_rules_bulk_route.test.ts | 10 +- .../routes/rules/patch_rules_bulk_route.ts | 4 +- .../routes/rules/patch_rules_route.test.ts | 10 +- .../routes/rules/patch_rules_route.ts | 14 +- .../routes/rules/preview_rules_route.ts | 2 +- .../routes/rules/utils.test.ts | 4 +- .../detection_engine/routes/rules/utils.ts | 2 +- .../check_rule_exception_references.test.ts | 12 +- .../utils/check_rule_exception_references.ts | 2 +- .../gather_referenced_exceptions.test.ts | 4 +- .../utils/gather_referenced_exceptions.ts | 2 +- .../rules/utils/import_rules_utils.test.ts | 14 +- .../routes/rules/utils/import_rules_utils.ts | 18 +- .../lib/detection_engine/routes/utils.ts | 2 +- .../rules/create_rules.mock.ts | 196 ----- .../rules/create_rules.test.ts | 52 +- .../detection_engine/rules/create_rules.ts | 156 +--- .../create_rules_stream_from_ndjson.test.ts | 140 +--- .../rules/create_rules_stream_from_ndjson.ts | 4 +- .../rules/get_prepackaged_rules.test.ts | 12 +- .../rules/get_prepackaged_rules.ts | 2 +- .../rules/get_rules_to_install.test.ts | 24 +- .../rules/get_rules_to_install.ts | 2 +- .../rules/get_rules_to_update.test.ts | 48 +- .../rules/get_rules_to_update.ts | 2 +- .../rules/install_prepacked_rules.ts | 20 +- .../rules/patch_rules.mock.ts | 120 ---- .../rules/patch_rules.test.ts | 148 ++-- .../lib/detection_engine/rules/types.ts | 122 +--- .../rules/update_prepacked_rules.test.ts | 40 +- .../rules/update_prepacked_rules.ts | 36 +- .../lib/detection_engine/rules/utils.test.ts | 218 ------ .../lib/detection_engine/rules/utils.ts | 179 +---- .../schemas/rule_converters.ts | 100 ++- .../server/lib/detection_engine/types.ts | 98 --- .../build_validation/route_validation.ts | 11 + .../read_stream/create_stream_from_ndjson.ts | 6 +- 59 files changed, 610 insertions(+), 3901 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts 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 248b3e4c8beb7..20f25c07fdd1c 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 @@ -5,11 +5,7 @@ * 2.0. */ -import { - AddPrepackagedRulesSchema, - AddPrepackagedRulesSchemaDecoded, -} from './add_prepackaged_rules_schema'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; +import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ description: 'some description', @@ -23,33 +19,6 @@ export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => version: 1, }); -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: [], - enabled: false, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', -}); - export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ description: 'some description', name: 'Query with a rule id', @@ -92,62 +61,3 @@ export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRul }, ], }); - -export const getAddPrepackagedThreatMatchRulesSchemaDecodedMock = - (): AddPrepackagedRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'threat_match', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - threat_query: '*:*', - threat_index: ['list-index'], - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, - }, - ], - filter: [], - should: [], - must_not: [], - }, - }, - ], - }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts index 9d0456c507772..f8c901b3311df 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts @@ -7,7 +7,6 @@ import { addPrepackagedRulesSchema, - AddPrepackagedRulesSchemaDecoded, AddPrepackagedRulesSchema, } from './add_prepackaged_rules_schema'; @@ -16,11 +15,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { getAddPrepackagedRulesSchemaMock, - getAddPrepackagedRulesSchemaDecodedMock, getAddPrepackagedThreatMatchRulesSchemaMock, - getAddPrepackagedThreatMatchRulesSchemaDecodedMock, } from './add_prepackaged_rules_schema.mock'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; describe('add prepackaged rules schema', () => { @@ -30,15 +26,24 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "rule_id"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -63,14 +68,21 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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 "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -83,13 +95,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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 "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -103,13 +120,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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 "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -124,13 +146,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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 "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -146,12 +173,15 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -168,11 +198,12 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -262,33 +293,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -337,34 +341,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate', () => { @@ -393,31 +369,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index, version] does not validate because output_index is not allowed', () => { - const payload: Partial & { output_index: string } = { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - version: 1, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "output_index"']); - expect(message.schema).toEqual({}); - }); - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { const payload: Partial = { rule_id: 'rule-1', @@ -437,32 +388,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - version: 1, - actions: [], - enabled: false, - exceptions_list: [], - false_positives: [], - max_signals: 100, - references: [], - tags: [], - threat: [], - throttle: null, - }; - expect(message.schema).toEqual(expected); }); test('You can send in a namespace', () => { @@ -475,11 +400,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - namespace: 'a namespace', - }; - expect(message.schema).toEqual(expected); }); test('You can send in an empty array to threat', () => { @@ -492,11 +412,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - threat: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { @@ -535,48 +450,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - threat: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('allows references to be sent as valid', () => { @@ -589,24 +462,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - references: ['index-1'], - }; - expect(message.schema).toEqual(expected); - }); - - test('defaults references to an array if it is not sent in', () => { - const { references, ...noReferences } = getAddPrepackagedRulesSchemaMock(); - const decoded = addPrepackagedRulesSchema.decode(noReferences); - const checked = exactCheck(noReferences, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - references: [], - }; - expect(message.schema).toEqual(expected); }); test('immutable cannot be set in a pre-packaged rule', () => { @@ -622,17 +477,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('defaults enabled to false', () => { - const payload: AddPrepackagedRulesSchema = getAddPrepackagedRulesSchemaMock(); - delete payload.enabled; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as unknown as AddPrepackagedRulesSchemaDecoded).enabled).toEqual(false); - }); - test('rule_id is required', () => { const payload: AddPrepackagedRulesSchema = getAddPrepackagedRulesSchemaMock(); // @ts-expect-error @@ -673,49 +517,8 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('defaults interval to 5 min', () => { - const { interval, ...noInterval } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noInterval, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { interval: expectedInterval, ...expectedNoInterval } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoInterval, - interval: '5m', - }; - expect(message.schema).toEqual(expected); - }); - - test('defaults max signals to 100', () => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { max_signals, ...noMaxSignals } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noMaxSignals, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoMaxSignals, - max_signals: 100, - }; - expect(message.schema).toEqual(expected); - }); - test('saved_query type can have filters with it', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), filters: [], }; @@ -724,11 +527,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - filters: [], - }; - expect(message.schema).toEqual(expected); }); test('filters cannot be a string', () => { @@ -747,7 +545,7 @@ describe('add prepackaged rules schema', () => { }); test('language validates with kuery', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), language: 'kuery', }; @@ -756,15 +554,10 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - language: 'kuery', - }; - expect(message.schema).toEqual(expected); }); test('language validates with lucene', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), language: 'lucene', }; @@ -773,11 +566,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - language: 'lucene', - }; - expect(message.schema).toEqual(expected); }); test('language does not validate with something made up', () => { @@ -833,11 +621,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - max_signals: 1, - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of tags', () => { @@ -850,11 +633,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - tags: ['tag_1', 'tag_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of tags that are numbers', () => { @@ -955,20 +733,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - threat: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of false positives', () => { @@ -981,11 +745,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - false_positives: ['false_1', 'false_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of false positives that are numbers', () => { @@ -1043,11 +802,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - risk_score: 0, - }; - expect(message.schema).toEqual(expected); }); test('You can set the risk_score to 100', () => { @@ -1060,11 +814,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - risk_score: 100, - }; - expect(message.schema).toEqual(expected); }); test('You can set meta to any object you want', () => { @@ -1079,13 +828,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }; - expect(message.schema).toEqual(expected); }); test('You cannot create meta as a string', () => { @@ -1103,26 +845,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('You can omit the query string when filters are present', () => { - const { query, ...noQuery } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noQuery, - filters: [], - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { query: expectedQuery, ...expectedNoQuery } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoQuery, - filters: [], - }; - expect(message.schema).toEqual(expected); - }); - test('validates with timeline_id and timeline_title', () => { const payload: AddPrepackagedRulesSchema = { ...getAddPrepackagedRulesSchemaMock(), @@ -1134,50 +856,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - timeline_id: 'timeline-id', - timeline_title: 'timeline-title', - }; - expect(message.schema).toEqual(expected); - }); - - test('The default for "from" will be "now-6m"', () => { - const { from, ...noFrom } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noFrom, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { from: expectedFrom, ...expectedNoFrom } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoFrom, - from: 'now-6m', - }; - expect(message.schema).toEqual(expected); - }); - - test('The default for "to" will be "now"', () => { - const { to, ...noTo } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noTo, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { to: expectedTo, ...expectedNoTo } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoTo, - to: 'now', - }; - expect(message.schema).toEqual(expected); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { @@ -1193,26 +871,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('The default for "actions" will be an empty array', () => { - const { actions, ...noActions } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noActions, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { actions: expectedActions, ...expectedNoActions } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoActions, - actions: [], - }; - expect(message.schema).toEqual(expected); - }); - test('You cannot send in an array of actions that are missing "group"', () => { const payload: Omit = { ...getAddPrepackagedRulesSchemaMock(), @@ -1295,26 +953,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('The default for "throttle" will be null', () => { - const { throttle, ...noThrottle } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noThrottle, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { throttle: expectedThrottle, ...expectedNoThrottle } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoThrottle, - throttle: null, - }; - expect(message.schema).toEqual(expected); - }); - describe('note', () => { test('You can set note to a string', () => { const payload: AddPrepackagedRulesSchema = { @@ -1326,11 +964,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - note: '# documentation markdown here', - }; - expect(message.schema).toEqual(expected); }); test('You can set note to an empty string', () => { @@ -1343,11 +976,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - note: '', - }; - expect(message.schema).toEqual(expected); }); test('You cannot create note as an object', () => { @@ -1387,33 +1015,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1440,47 +1041,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - exceptions_list: [ - { - id: 'some_uuid', - list_id: 'list_id_single', - namespace_type: 'single', - type: 'detection', - }, - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, version, and empty exceptions_list] does validate', () => { @@ -1505,40 +1065,10 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, version, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1587,34 +1117,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - filters: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1624,9 +1126,7 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getAddPrepackagedThreatMatchRulesSchemaDecodedMock(); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); }); }); }); 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 72c48a139ca1d..a20571686743d 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 @@ -7,188 +7,29 @@ import * as t from 'io-ts'; -import { - Actions, - DefaultActionsArray, - DefaultFromString, - DefaultIntervalString, - DefaultMaxSignalsNumber, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - DefaultThreatArray, - DefaultThrottleNull, - DefaultToString, - From, - RiskScoreMapping, - machine_learning_job_id, - risk_score, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - Threats, - type, - language, - severity, - SeverityMapping, - ThrottleOrNull, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { - version, - DefaultStringArray, - DefaultBooleanFalse, -} from '@kbn/securitysolution-io-ts-types'; +import { version } from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray, ListArray } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - index, - data_view_id, - saved_id, - timeline_id, - timeline_title, - meta, - name, - Tags, - To, - threshold, - note, - References, - Enabled, - FalsePositives, - Interval, - query, - rule_id, - building_block_type, - license, - rule_name_override, - timestamp_override, - Author, - timestamp_field, - event_category_override, - tiebreaker_field, - namespace, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../common'; +import { rule_id, RelatedIntegrationArray, RequiredFieldArray, SetupGuide } from '../common'; +import { baseCreateParams, createTypeSpecific } from './rule_schemas'; /** * Big differences between this schema and the createRulesSchema * - rule_id is required here - * - output_index is not allowed (and instead the space index must be used) - * - immutable is forbidden but defaults to true instead of to false and it can only ever be true (This is forced directly in the route and not here) - * - enabled defaults to false instead of true * - version is a required field that must exist - * - index is a required field that must exist if type !== machine_learning (Checked within the runtime type dependent system) */ +// TODO: test `version` overwriting `version` from baseParams export const addPrepackagedRulesSchema = t.intersection([ - t.exact( - t.type({ - description, - risk_score, - name, - severity, - type, - rule_id, - version, - }) - ), + baseCreateParams, + createTypeSpecific, + // version is required in addPrepackagedRulesSchema, so this supercedes the defaultable + // version in baseParams + t.exact(t.type({ rule_id, version })), t.exact( 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 - timestamp_field, // defaults to "undefined" if not set during decode - event_category_override, // defaults to "undefined" if not set during decode - tiebreaker_field, // defaults to "undefined" 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 - from: DefaultFromString, // defaults to "now-6m" if not set during decode - index, // defaults to undefined if not set during decode - data_view_id, // defaults to undefined if not set during decode - 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 - related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode - required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode - risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode - rule_name_override, // defaults to "undefined" if not set during decode - setup: SetupGuide, // defaults to "undefined" if not set during decode - severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode - tags: DefaultStringArray, // defaults to empty string array if not set during decode - to: DefaultToString, // defaults to "now" if not set during decode - threat: DefaultThreatArray, // defaults to empty array if not set during decode - threshold, // defaults to "undefined" 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 - threat_filters, // defaults to "undefined" if not set during decode - threat_mapping, // defaults to "undefined" if not set during decode - threat_query, // defaults to "undefined" if not set during decode - threat_index, // defaults to "undefined" if not set during decode - threat_language, // defaults "undefined" if not set during decode - threat_indicator_path, // defaults "undefined" if not set during decode - concurrent_searches, // defaults to "undefined" if not set during decode - items_per_search, // defaults to "undefined" if not set during decode - namespace, // defaults to "undefined" if not set during decode + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, }) ), ]); export type AddPrepackagedRulesSchema = t.TypeOf; - -// This type is used after a decode since some things are defaults after a decode. -export type AddPrepackagedRulesSchemaDecoded = Omit< - AddPrepackagedRulesSchema, - | 'author' - | 'references' - | 'actions' - | 'enabled' - | 'false_positives' - | 'from' - | 'interval' - | 'max_signals' - | 'namespace' - | 'risk_score_mapping' - | 'severity_mapping' - | 'tags' - | 'to' - | 'threat' - | 'throttle' - | 'exceptions_list' -> & { - author: Author; - references: References; - actions: Actions; - enabled: Enabled; - false_positives: FalsePositives; - from: From; - interval: Interval; - max_signals: MaxSignals; - risk_score_mapping: RiskScoreMapping; - severity_mapping: SeverityMapping; - tags: Tags; - to: To; - threat: Threats; - throttle: ThrottleOrNull; - exceptions_list: ListArray; - namespace?: string; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts index 738825bb166cc..2939427ce676a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts @@ -10,26 +10,6 @@ import { addPrepackagedRuleValidateTypeDependents } from './add_prepackaged_rule import { getAddPrepackagedRulesSchemaMock } from './add_prepackaged_rules_schema.mock'; describe('add_prepackaged_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and will not validate without out', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'saved_query', - }; - delete schema.saved_id; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "saved_query", "saved_id" is required']); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: AddPrepackagedRulesSchema = { ...getAddPrepackagedRulesSchemaMock(), @@ -69,58 +49,4 @@ describe('add_prepackaged_rules_type_dependents', () => { const errors = addPrepackagedRuleValidateTypeDependents(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); - - test('threshold is required when type is threshold and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'threshold', - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); - - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'threshold', - threshold: { - field: '', - value: -1, - }, - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); - }); - - test('threshold.field should contain 3 items or less', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'threshold', - threshold: { - field: ['field-1', 'field-2', 'field-3', 'field-4'], - value: 1, - }, - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Number of fields must be 3 or less']); - }); - - test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'threshold', - threshold: { - field: ['field-1', 'field-2', 'field-3'], - value: 1, - cardinality: [ - { - field: 'field-1', - value: 2, - }, - ], - }, - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); - }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts index fb47e4a90c1bd..30e0d4ff114e7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts @@ -5,70 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; -export const validateAnomalyThreshold = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.anomaly_threshold == null) { - return ['when "type" is "machine_learning" anomaly_threshold is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateQuery = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateSavedId = (rule: AddPrepackagedRulesSchema): string[] => { - if (rule.type === 'saved_query') { - if (rule.saved_id == null) { - return ['when "type" is "saved_query", "saved_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateMachineLearningJobId = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.machine_learning_job_id == null) { - return ['when "type" is "machine_learning", "machine_learning_job_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: AddPrepackagedRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -95,40 +33,8 @@ export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[] return []; }; -export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => { - const errors: string[] = []; - if (isThresholdRule(rule.type)) { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if (rule.threshold.value <= 0) { - errors.push('"threshold.value" has to be bigger than 0'); - } - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } - } - } - return errors; -}; - export const addPrepackagedRuleValidateTypeDependents = ( rule: AddPrepackagedRulesSchema ): string[] => { - return [ - ...validateAnomalyThreshold(rule), - ...validateQuery(rule), - ...validateLanguage(rule), - ...validateSavedId(rule), - ...validateMachineLearningJobId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; + return [...validateTimelineId(rule), ...validateTimelineTitle(rule)]; }; 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 6c7c03b0b4fa5..a827852a4166d 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 @@ -5,8 +5,7 @@ * 2.0. */ -import { ImportRulesSchema, ImportRulesSchemaDecoded } from './import_rules_schema'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; +import { ImportRulesSchema } from './import_rules_schema'; export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ description: 'some description', @@ -31,34 +30,6 @@ export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSc rule_id: ruleId, }); -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: [], - enabled: true, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - immutable: false, -}); - /** * Given an array of rules, builds an NDJSON string of rules * as we might import/export @@ -109,62 +80,3 @@ export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): ImportRu }, ], }); - -export const getImportThreatMatchRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'threat_match', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - immutable: false, - threat_query: '*:*', - threat_index: ['index-123'], - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, - }, - ], - filter: [], - should: [], - must_not: [], - }, - }, - ], -}); 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 d3efcaf0f5df5..4386415bc605b 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 @@ -13,15 +13,11 @@ import { ImportRulesPayloadSchema, ImportRulesSchema, importRulesSchema, - ImportRulesSchemaDecoded, } from './import_rules_schema'; import { getImportRulesSchemaMock, - getImportRulesSchemaDecodedMock, getImportThreatMatchRulesSchemaMock, - getImportThreatMatchRulesSchemaDecodedMock, } from './import_rules_schema.mock'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; describe('import rules schema', () => { @@ -31,14 +27,21 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "rule_id"' + ); expect(message.schema).toEqual({}); }); @@ -63,13 +66,18 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -82,12 +90,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -101,12 +112,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -121,12 +135,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - '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"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -142,11 +159,12 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -163,10 +181,9 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); expect(message.schema).toEqual({}); }); @@ -252,34 +269,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -326,35 +315,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { @@ -378,36 +338,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { @@ -428,33 +358,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { @@ -476,34 +379,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('You can send in an empty array to threat', () => { @@ -516,11 +391,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - threat: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { @@ -559,50 +429,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - immutable: false, - threat: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('allows references to be sent as valid', () => { @@ -615,11 +441,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - references: ['index-1'], - }; - expect(message.schema).toEqual(expected); }); test('defaults references to an array if it is not sent in', () => { @@ -628,11 +449,6 @@ describe('import rules schema', () => { const checked = exactCheck(noReferences, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - references: [], - }; - expect(message.schema).toEqual(expected); }); test('references cannot be numbers', () => { @@ -671,13 +487,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { interval: expectedInterval, ...expectedNoInterval } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoInterval, - interval: '5m', - }; - expect(message.schema).toEqual(expected); }); test('defaults max signals to 100', () => { @@ -691,18 +500,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = - getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoMaxSignals, - max_signals: 100, - }; - expect(message.schema).toEqual(expected); }); test('saved_query type can have filters with it', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), filters: [], }; @@ -711,11 +512,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - filters: [], - }; - expect(message.schema).toEqual(expected); }); test('filters cannot be a string', () => { @@ -734,7 +530,7 @@ describe('import rules schema', () => { }); test('language validates with kuery', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), language: 'kuery', }; @@ -743,15 +539,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - language: 'kuery', - }; - expect(message.schema).toEqual(expected); }); test('language validates with lucene', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), language: 'lucene', }; @@ -760,11 +551,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - language: 'lucene', - }; - expect(message.schema).toEqual(expected); }); test('language does not validate with something made up', () => { @@ -820,11 +606,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - max_signals: 1, - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of tags', () => { @@ -837,11 +618,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - tags: ['tag_1', 'tag_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of tags that are numbers', () => { @@ -942,20 +718,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - threat: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of false positives', () => { @@ -968,11 +730,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - false_positives: ['false_1', 'false_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of false positives that are numbers', () => { @@ -1014,7 +771,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getImportRulesSchemaDecodedMock()); }); test('You cannot set the immutable to be true', () => { @@ -1083,11 +839,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - risk_score: 0, - }; - expect(message.schema).toEqual(expected); }); test('You can set the risk_score to 100', () => { @@ -1100,11 +851,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - risk_score: 100, - }; - expect(message.schema).toEqual(expected); }); test('You can set meta to any object you want', () => { @@ -1119,13 +865,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }; - expect(message.schema).toEqual(expected); }); test('You cannot create meta as a string', () => { @@ -1143,26 +882,6 @@ describe('import rules schema', () => { expect(message.schema).toEqual({}); }); - test('You can omit the query string when filters are present', () => { - const { query, ...noQuery } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { - ...noQuery, - filters: [], - }; - - const decoded = importRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { query: expectedQuery, ...expectedNoQuery } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoQuery, - filters: [], - }; - expect(message.schema).toEqual(expected); - }); - test('validates with timeline_id and timeline_title', () => { const payload: ImportRulesSchema = { ...getImportRulesSchemaMock(), @@ -1174,12 +893,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - timeline_id: 'timeline-id', - timeline_title: 'timeline-title', - }; - expect(message.schema).toEqual(expected); }); test('rule_id is required and you cannot get by with just id', () => { @@ -1212,14 +925,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - created_at: '2020-01-09T06:15:24.749Z', - updated_at: '2020-01-09T06:15:24.749Z', - created_by: 'Braden Hassanabad', - updated_by: 'Evan Hassanabad', - }; - expect(message.schema).toEqual(expected); }); test('it does not validate with epoch strings for created_at', () => { @@ -1300,13 +1005,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { from: expectedFrom, ...expectedNoFrom } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoFrom, - from: 'now-6m', - }; - expect(message.schema).toEqual(expected); }); test('The default for "to" will be "now"', () => { @@ -1319,13 +1017,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { to: expectedTo, ...expectedNoTo } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoTo, - to: 'now', - }; - expect(message.schema).toEqual(expected); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { @@ -1351,13 +1042,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { actions: expectedActions, ...expectedNoActions } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoActions, - actions: [], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of actions that are missing "group"', () => { const payload: Omit = { @@ -1451,13 +1135,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { throttle: expectedThrottle, ...expectedNoThrottle } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoThrottle, - throttle: null, - }; - expect(message.schema).toEqual(expected); }); describe('note', () => { @@ -1471,11 +1148,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - note: '# documentation markdown here', - }; - expect(message.schema).toEqual(expected); }); test('You can set note to an empty string', () => { @@ -1488,11 +1160,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - note: '', - }; - expect(message.schema).toEqual(expected); }); test('You cannot create note as an object', () => { @@ -1531,34 +1198,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - immutable: false, - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1584,48 +1223,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - immutable: false, - exceptions_list: [ - { - id: 'some_uuid', - list_id: 'list_id_single', - namespace_type: 'single', - type: 'detection', - }, - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { @@ -1649,41 +1246,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - immutable: false, - filters: [], - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1730,35 +1296,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - immutable: false, - exceptions_list: [], - filters: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1768,9 +1305,7 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getImportThreatMatchRulesSchemaDecodedMock(); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); }); }); @@ -1795,35 +1330,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: [], - data_view_id: 'logs-*', - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); // Both can be defined, but if a data_view_id is defined, rule will use that one @@ -1847,35 +1353,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); 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', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['auditbeat-*'], - data_view_id: 'logs-*', - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('data_view_id cannot be a number', () => { 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 6e419e97775e7..b3d533a167a7a 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 @@ -7,86 +7,19 @@ import * as t from 'io-ts'; +import { OnlyFalseAllowed } from '@kbn/securitysolution-io-ts-types'; import { - Actions, - DefaultActionsArray, - DefaultFromString, - DefaultIntervalString, - DefaultMaxSignalsNumber, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - DefaultThreatArray, - DefaultThrottleNull, - DefaultToString, - From, - machine_learning_job_id, - risk_score, - RiskScoreMapping, - threat_index, - items_per_search, - concurrent_searches, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - Threats, - type, - language, - severity, - SeverityMapping, - ThrottleOrNull, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import { - DefaultVersionNumber, - Version, - DefaultStringArray, - DefaultBooleanTrue, - OnlyFalseAllowed, -} from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray, ListArray } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - RuleId, - index, - data_view_id, - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - name, - Tags, - To, - threshold, - note, - References, - Enabled, - FalsePositives, - Interval, - query, rule_id, id, created_at, updated_at, created_by, updated_by, - building_block_type, - license, - rule_name_override, - timestamp_override, - Author, - timestamp_field, - event_category_override, - tiebreaker_field, RelatedIntegrationArray, RequiredFieldArray, SetupGuide, } from '../common'; +import { baseCreateParams, createTypeSpecific } from './rule_schemas'; /** * Differences from this and the createRulesSchema are @@ -99,121 +32,26 @@ import { * - updated_by is optional (but ignored in the import code) */ export const importRulesSchema = t.intersection([ - t.exact( - t.type({ - description, - risk_score, - name, - severity, - type, - rule_id, - }) - ), + baseCreateParams, + createTypeSpecific, + t.exact(t.type({ rule_id })), t.exact( t.partial({ - 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 - timestamp_field, // defaults to "undefined" if not set during decode - event_category_override, // defaults to "undefined" if not set during decode - tiebreaker_field, // defaults to "undefined" 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 - from: DefaultFromString, // defaults to "now-6m" if not set during decode - index, // defaults to undefined if not set during decode - data_view_id, // defaults to undefined if not set during decode - immutable: OnlyFalseAllowed, // defaults to "false" if not set during decode - 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 - 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 - related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode - required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode - risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode - rule_name_override, // defaults to "undefined" if not set during decode - setup: SetupGuide, // defaults to "undefined" if not set during decode - severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode - tags: DefaultStringArray, // defaults to empty string array if not set during decode - to: DefaultToString, // defaults to "now" if not set during decode - threat: DefaultThreatArray, // defaults to empty array if not set during decode - threshold, // defaults to "undefined" 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 - exceptions_list: DefaultListArray, // defaults to empty array if not set during decode - created_at, // defaults "undefined" if not set during decode - updated_at, // defaults "undefined" if not set during decode - created_by, // defaults "undefined" if not set during decode - updated_by, // defaults "undefined" if not set during decode - threat_filters, // defaults to "undefined" if not set during decode - threat_mapping, // defaults to "undefined" if not set during decode - threat_query, // defaults to "undefined" if not set during decode - threat_index, // defaults to "undefined" if not set during decode - threat_language, // defaults "undefined" if not set during decode - threat_indicator_path, // defaults to "undefined" if not set during decode - concurrent_searches, // defaults to "undefined" if not set during decode - items_per_search, // defaults to "undefined" if not set during decode + id, + immutable: OnlyFalseAllowed, + updated_at, + updated_by, + created_at, + created_by, + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, }) ), ]); 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' - | 'false_positives' - | 'from' - | 'interval' - | 'max_signals' - | 'risk_score_mapping' - | 'severity_mapping' - | 'tags' - | 'to' - | 'threat' - | 'throttle' - | 'version' - | 'exceptions_list' - | 'rule_id' - | 'immutable' -> & { - author: Author; - references: References; - actions: Actions; - enabled: Enabled; - false_positives: FalsePositives; - from: From; - interval: Interval; - max_signals: MaxSignals; - risk_score_mapping: RiskScoreMapping; - severity_mapping: SeverityMapping; - tags: Tags; - to: To; - threat: Threats; - throttle: ThrottleOrNull; - version: Version; - exceptions_list: ListArray; - rule_id: RuleId; - immutable: false; -}; - export const importRulesPayloadSchema = t.exact( t.type({ file: t.object, @@ -221,5 +59,3 @@ export const importRulesPayloadSchema = t.exact( ); export type ImportRulesPayloadSchema = t.TypeOf; - -export type ImportRulesPayloadSchemaDecoded = ImportRulesPayloadSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts index c3f4ca78e3b8d..211f649a218e4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts @@ -10,23 +10,6 @@ import { ImportRulesSchema } from './import_rules_schema'; import { importRuleValidateTypeDependents } from './import_rules_type_dependents'; describe('import_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and will not validate without out', () => { - const schema: ImportRulesSchema = { ...getImportRulesSchemaMock(), type: 'saved_query' }; - delete schema.saved_id; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "saved_query", "saved_id" is required']); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: ImportRulesSchema = { ...getImportRulesSchemaMock(), @@ -66,13 +49,4 @@ describe('import_rules_type_dependents', () => { const errors = importRuleValidateTypeDependents(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); - - test('threshold is required when type is threshold and validates with it', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - type: 'threshold', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts index 9bc90e63238e7..afeb2dd70d804 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts @@ -5,70 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; import { ImportRulesSchema } from './import_rules_schema'; -export const validateAnomalyThreshold = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.anomaly_threshold == null) { - return ['when "type" is "machine_learning" anomaly_threshold is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateQuery = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateSavedId = (rule: ImportRulesSchema): string[] => { - if (rule.type === 'saved_query') { - if (rule.saved_id == null) { - return ['when "type" is "saved_query", "saved_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateMachineLearningJobId = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.machine_learning_job_id == null) { - return ['when "type" is "machine_learning", "machine_learning_job_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: ImportRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -95,35 +33,6 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { return []; }; -export const validateThreshold = (rule: ImportRulesSchema): string[] => { - const errors: string[] = []; - if (isThresholdRule(rule.type)) { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } - } - } - return errors; -}; - export const importRuleValidateTypeDependents = (rule: ImportRulesSchema): string[] => { - return [ - ...validateAnomalyThreshold(rule), - ...validateQuery(rule), - ...validateLanguage(rule), - ...validateSavedId(rule), - ...validateMachineLearningJobId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; + return [...validateTimelineId(rule), ...validateTimelineTitle(rule)]; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts index cb807a7a8eaa7..0472c62228c4d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; -import { patchRulesSchema } from './rule_schemas'; +import { patchRulesSchema } from './patch_rules_schema'; export const patchRulesBulkSchema = t.array(patchRulesSchema); export type PatchRulesBulkSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts index c476a8e46a621..af3201ddbf7c5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PatchRulesSchema, PatchRulesSchemaDecoded } from './patch_rules_schema'; +import { PatchRulesSchema } from './patch_rules_schema'; export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ description: 'some description', @@ -17,6 +17,3 @@ export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ language: 'kuery', rule_id: 'rule-1', }); - -export const getPatchRulesSchemaDecodedMock = (): PatchRulesSchemaDecoded => - getPatchRulesSchemaMock(); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts index 5ac49deb32e0d..2d94ce9431493 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { patchRulesSchema, PatchRulesSchema, PatchRulesSchemaDecoded } from './patch_rules_schema'; -import { getPatchRulesSchemaMock, getPatchRulesSchemaDecodedMock } from './patch_rules_schema.mock'; +import { patchRulesSchema, PatchRulesSchema } from './patch_rules_schema'; +import { getPatchRulesSchemaMock } from './patch_rules_schema.mock'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; @@ -35,7 +35,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; expect(message.schema).toEqual(expected); @@ -50,7 +50,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', }; expect(message.schema).toEqual(expected); @@ -66,7 +66,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', }; @@ -83,7 +83,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; @@ -100,7 +100,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; @@ -118,7 +118,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -138,7 +138,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -159,7 +159,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -181,7 +181,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -204,7 +204,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -228,7 +228,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -253,7 +253,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -279,7 +279,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -306,7 +306,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -334,7 +334,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -363,7 +363,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -394,7 +394,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -427,7 +427,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -455,7 +455,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -482,7 +482,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -513,7 +513,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -547,7 +547,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -573,7 +573,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).references).toEqual(undefined); + expect((message.schema as PatchRulesSchema).references).toEqual(undefined); }); test('does not default interval', () => { @@ -585,7 +585,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).interval).toEqual(undefined); + expect((message.schema as PatchRulesSchema).interval).toEqual(undefined); }); test('does not default max_signals', () => { @@ -597,7 +597,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).max_signals).toEqual(undefined); + expect((message.schema as PatchRulesSchema).max_signals).toEqual(undefined); }); test('references cannot be numbers', () => { @@ -622,7 +622,7 @@ describe('patch_rules_schema', () => { const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "index,[5]"']); expect(message.schema).toEqual({}); }); @@ -636,7 +636,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; @@ -644,7 +644,7 @@ describe('patch_rules_schema', () => { }); test('saved_id validates with type:saved_query', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), type: 'saved_query', saved_id: 'some id', @@ -654,8 +654,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), type: 'saved_query', saved_id: 'some id', }; @@ -663,7 +663,7 @@ describe('patch_rules_schema', () => { }); test('saved_query type can have filters with it', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), saved_id: 'some id', filters: [], @@ -673,8 +673,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), saved_id: 'some id', filters: [], }; @@ -682,7 +682,7 @@ describe('patch_rules_schema', () => { }); test('language validates with kuery', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'kuery', @@ -692,8 +692,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', language: 'kuery', }; @@ -701,7 +701,7 @@ describe('patch_rules_schema', () => { }); test('language validates with lucene', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'lucene', @@ -712,8 +712,8 @@ describe('patch_rules_schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', language: 'lucene', }; @@ -721,7 +721,7 @@ describe('patch_rules_schema', () => { }); test('language does not validate with something made up', () => { - const payload: Omit & { language: string } = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'something-made-up', @@ -730,14 +730,14 @@ describe('patch_rules_schema', () => { const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "something-made-up" supplied to "language"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "something-made-up" supplied to "language"' + ); expect(message.schema).toEqual({}); }); test('max_signals cannot be negative', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: -1, @@ -753,7 +753,7 @@ describe('patch_rules_schema', () => { }); test('max_signals cannot be zero', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 0, @@ -767,7 +767,7 @@ describe('patch_rules_schema', () => { }); test('max_signals can be 1', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 1, @@ -777,8 +777,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 1, }; @@ -795,8 +795,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected: PatchRulesSchema = { + ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; expect(message.schema).toEqual(expected); @@ -826,9 +826,9 @@ describe('patch_rules_schema', () => { const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "should not work" supplied to "filters"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "should not work" supplied to "filters"' + ); expect(message.schema).toEqual({}); }); @@ -867,7 +867,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).threat).toEqual(undefined); + expect((message.schema as PatchRulesSchema).threat).toEqual(undefined); }); test('threat is not defaulted to undefined on patch with empty array', () => { @@ -880,7 +880,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).threat).toEqual([]); + expect((message.schema as PatchRulesSchema).threat).toEqual([]); }); test('threat is valid when updated with all sub-objects', () => { @@ -1007,8 +1007,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected: PatchRulesSchema = { + ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', }; @@ -1047,7 +1047,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1072,7 +1072,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', note: '# new documentation markdown', }; @@ -1185,7 +1185,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1236,7 +1236,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1255,9 +1255,7 @@ describe('patch_rules_schema', () => { }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1280,7 +1278,6 @@ describe('patch_rules_schema', () => { 'Invalid value "undefined" supplied to "exceptions_list,list_id"', 'Invalid value "undefined" supplied to "exceptions_list,type"', 'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"', - 'Invalid value "[{"id":"uuid_here","namespace_type":"not a namespace type"}]" supplied to "exceptions_list"', ]); expect(message.schema).toEqual({}); }); @@ -1305,7 +1302,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', 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 80a267ab6c2da..a591575f1785f 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 @@ -7,127 +7,10 @@ import * as t from 'io-ts'; -import { - actions, - from, - machine_learning_job_id, - risk_score, - risk_score_mapping, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - threats, - type, - language, - severity, - severity_mapping, - max_signals, - throttle, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import { version } from '@kbn/securitysolution-io-ts-types'; - -import { listArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - index, - data_view_id, - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - rule_id, - name, - note, - false_positives, - interval, - enabled, - tags, - threshold, - references, - to, - query, - id, - building_block_type, - author, - license, - rule_name_override, - timestamp_override, - timestamp_field, - event_category_override, - tiebreaker_field, -} from '../common'; +import { patchTypeSpecific, sharedPatchSchema } from './rule_schemas'; /** * All of the patch elements should default to undefined if not set */ -export const patchRulesSchema = t.exact( - t.partial({ - author, - building_block_type, - description, - risk_score, - name, - severity, - type, - id, - actions, - anomaly_threshold, - enabled, - timestamp_field, - event_category_override, - tiebreaker_field, - false_positives, - filters, - from, - rule_id, - index, - data_view_id, - interval, - query, - language, - license, - // TODO: output_index: This should be removed eventually - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - machine_learning_job_id, - max_signals, - risk_score_mapping, - rule_name_override, - severity_mapping, - tags, - to, - threat: threats, - threshold, - throttle, - timestamp_override, - references, - note, - version, - exceptions_list: listArrayOrUndefined, - threat_index, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - concurrent_searches, - items_per_search, - }) -); - +export const patchRulesSchema = t.intersection([patchTypeSpecific, sharedPatchSchema]); export type PatchRulesSchema = t.TypeOf; - -// This type is used after a decode since some things are defaults after a decode. -export type PatchRulesSchemaDecoded = PatchRulesSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts index 0c4783acd0705..8ffaa7d959f24 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts @@ -10,16 +10,6 @@ import { PatchRulesSchema } from './patch_rules_schema'; import { patchRuleValidateTypeDependents } from './patch_rules_type_dependents'; describe('patch_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: PatchRulesSchema = { ...getPatchRulesSchemaMock(), @@ -79,58 +69,4 @@ describe('patch_rules_type_dependents', () => { const errors = patchRuleValidateTypeDependents(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); - - test('threshold is required when type is threshold and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); - - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', - threshold: { - field: '', - value: -1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); - }); - - test('threshold.field should contain 3 items or less', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', - threshold: { - field: ['field-1', 'field-2', 'field-3', 'field-4'], - value: 1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Number of fields must be 3 or less']); - }); - - test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', - threshold: { - field: ['field-1', 'field-2', 'field-3'], - value: 1, - cardinality: [ - { - field: 'field-1', - value: 2, - }, - ], - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); - }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts index 7229b403c92ad..182f0bd7485b1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts @@ -5,33 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; import { PatchRulesSchema } from './patch_rules_schema'; -export const validateQuery = (rule: PatchRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: PatchRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: PatchRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -68,36 +43,6 @@ export const validateId = (rule: PatchRulesSchema): string[] => { } }; -export const validateThreshold = (rule: PatchRulesSchema): string[] => { - const errors: string[] = []; - if (rule.type === 'threshold') { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (rule.threshold.value <= 0) { - errors.push('"threshold.value" has to be bigger than 0'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } - } - } - return errors; -}; - export const patchRuleValidateTypeDependents = (rule: PatchRulesSchema): string[] => { - return [ - ...validateId(rule), - ...validateQuery(rule), - ...validateLanguage(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; + return [...validateId(rule), ...validateTimelineId(rule), ...validateTimelineTitle(rule)]; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 91214d009207d..14273220fd4b2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -27,7 +27,7 @@ import { throttle, } from '@kbn/securitysolution-io-ts-alerting-types'; import { listArray } from '@kbn/securitysolution-io-ts-list-types'; -import { OnlyFalseAllowed, version } from '@kbn/securitysolution-io-ts-types'; +import { version } from '@kbn/securitysolution-io-ts-types'; import { id, @@ -190,6 +190,7 @@ const { patch: basePatchParams, response: baseResponseParams, } = buildAPISchemas(baseParams); +export { baseCreateParams }; // "shared" types are the same across all rule types, and built from "baseParams" above // with some variations for each route. These intersect with type specific schemas below @@ -209,8 +210,7 @@ export type SharedUpdateSchema = t.TypeOf; export const sharedPatchSchema = t.intersection([ basePatchParams, - t.exact(t.partial({ rule_id })), - t.exact(t.partial({ id })), + t.exact(t.partial({ rule_id, id })), ]); // START type specific parameter definitions @@ -359,7 +359,7 @@ export { machineLearningCreateParams }; // --------------------------------------- // END type specific parameter definitions -const createTypeSpecific = t.union([ +export const createTypeSpecific = t.union([ eqlCreateParams, threatMatchCreateParams, queryCreateParams, @@ -389,43 +389,6 @@ export const previewRulesSchema = t.intersection([ ]); export type PreviewRulesSchema = t.TypeOf; -// TODO: test `version` overwriting `version` from baseParams -export const addPrepackagedRulesSchema = t.intersection([ - baseCreateParams, - createTypeSpecific, - // version is required in addPrepackagedRulesSchema, so this supercedes the defaultable - // version in baseParams - t.exact(t.type({ rule_id, version })), - t.exact( - t.partial({ - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, - }) - ), -]); -export type AddPrepackagedRulesSchema = t.TypeOf; - -export const importRulesSchema = t.intersection([ - baseCreateParams, - createTypeSpecific, - t.exact(t.type({ rule_id })), - t.exact( - t.partial({ - id, - immutable: OnlyFalseAllowed, - updated_at, - updated_by, - created_at, - created_by, - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, - }) - ), -]); -export type ImportRulesSchema = t.TypeOf; - type UpdateSchema = SharedUpdateSchema & T; export type EqlUpdateSchema = UpdateSchema>; export type ThreatMatchUpdateSchema = UpdateSchema>; @@ -436,7 +399,7 @@ export type MachineLearningUpdateSchema = UpdateSchema< t.TypeOf >; -const patchTypeSpecific = t.union([ +export const patchTypeSpecific = t.union([ eqlPatchParams, threatMatchPatchParams, queryPatchParams, @@ -493,9 +456,6 @@ export const machineLearningFullPatchSchema = t.intersection([ ]); export type MachineLearningFullPatchSchema = t.TypeOf; -export const patchRulesSchema = t.intersection([patchTypeSpecific, sharedPatchSchema]); -export type PatchRulesSchema = t.TypeOf; - const responseRequiredFields = { id, rule_id, 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 8db750ba220af..911eb988b39e1 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 @@ -13,7 +13,7 @@ import { getBasicEmptySearchResponse, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock } from '../__mocks__'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import { ExceptionListClient } from '@kbn/lists-plugin/server'; @@ -33,7 +33,7 @@ jest.mock('../../rules/utils', () => { jest.mock('../../rules/get_prepackaged_rules', () => { return { - getLatestPrepackagedRules: async (): Promise => { + getLatestPrepackagedRules: async (): Promise => { return [ { author: ['Elastic'], @@ -58,7 +58,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { false_positives: [], max_signals: 100, threat: [], - throttle: null, + throttle: undefined, exceptions_list: [], version: 2, // set one higher than the mocks which is set to 1 to trigger updates }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 7a9d30b2ec8cf..b46dcec9f5155 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -119,7 +119,7 @@ export const createPrepackagedRules = async ( const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules); - await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall, siemClient)); + await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall)); const timeline = await installPrepackagedTimelines( maxTimelineImportExportSize, frameworkRequest, @@ -133,7 +133,6 @@ export const createPrepackagedRules = async ( rulesClient, savedObjectsClient, rulesToUpdate, - siemClient, context.getRuleExecutionLog() ); 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 d0028bbf7752b..a68a8febb603e 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 @@ -11,10 +11,7 @@ import { createRuleValidateTypeDependents } from '../../../../../common/detectio import { createRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { - DETECTION_ENGINE_RULES_BULK_CREATE, - NOTIFICATION_THROTTLE_NO_ACTIONS, -} from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwAuthzError } from '../../../machine_learning/validation'; @@ -24,8 +21,8 @@ import { transformValidateBulkError } from './validate'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils'; -import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { createRules } from '../../rules/create_rules'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead @@ -54,7 +51,6 @@ export const createRulesBulkRoute = ( const rulesClient = ctx.alerting.getRulesClient(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, @@ -84,32 +80,28 @@ export const createRulesBulkRoute = ( }); } } - const internalRule = convertCreateAPIToInternalSchema(payloadRule, siemClient); + try { const validationErrors = createRuleValidateTypeDependents(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ - ruleId: internalRule.params.ruleId, + ruleId: payloadRule.rule_id, statusCode: 400, message: validationErrors.join(), }); } - throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(payloadRule.type)); - const createdRule = await rulesClient.create({ - data: internalRule, + const createdRule = await createRules({ + rulesClient, + params: payloadRule, }); - // mutes if we are creating the rule with the explicit "no_actions" - if (payloadRule.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.muteAll({ id: createdRule.id }); - } - - return transformValidateBulkError(internalRule.params.ruleId, createdRule, null); + return transformValidateBulkError(createdRule.params.ruleId, createdRule, null); } catch (err) { return transformBulkError( - internalRule.params.ruleId, + payloadRule.rule_id, err as Error & { statusCode?: number } ); } 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 b44b2fd0bd6a5..11e5f34fbe9a6 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 @@ -7,10 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { - DETECTION_ENGINE_RULES_URL, - NOTIFICATION_THROTTLE_NO_ACTIONS, -} from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -21,7 +18,7 @@ import { buildSiemResponse } from '../utils'; import { createRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { newTransformValidate } from './validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; -import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; +import { createRules } from '../../rules/create_rules'; export const createRulesRoute = ( router: SecuritySolutionPluginRouter, @@ -56,7 +53,6 @@ export const createRulesRoute = ( const rulesClient = ctx.alerting.getRulesClient(); const ruleExecutionLog = ctx.securitySolution.getRuleExecutionLog(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); if (request.body.rule_id != null) { const rule = await readRules({ @@ -72,28 +68,21 @@ export const createRulesRoute = ( } } - const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient); - const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, ml, request, savedObjectsClient, }); - throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); // This will create the endpoint list if it does not exist yet await ctx.lists?.getExceptionListClient().createEndpointList(); - - const createdRule = await rulesClient.create({ - data: internalRule, + const createdRule = await createRules({ + rulesClient, + params: request.body, }); - // mutes if we are creating the rule with the explicit "no_actions" - if (request.body.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.muteAll({ id: createdRule.id }); - } - const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(createdRule.id); const [validated, errors] = newTransformValidate(createdRule, ruleExecutionSummary); 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 38a9bb622768d..51c494b183b88 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 @@ -45,7 +45,7 @@ import { } from './utils/import_rules_utils'; import { getReferencedExceptionLists } from './utils/gather_referenced_exceptions'; import { importRuleExceptions } from './utils/import_rule_exceptions'; -import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; const CHUNK_PARSED_OBJECT_SIZE = 50; @@ -91,7 +91,6 @@ export const importRulesRoute = ( }); const savedObjectsClient = ctx.core.savedObjects.client; const exceptionsClient = ctx.lists?.getExceptionListClient(); - const siemClient = ctx.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, @@ -172,7 +171,6 @@ export const importRulesRoute = ( exceptionsClient, spaceId: ctx.securitySolution.getSpaceId(), existingLists: foundReferencedExceptionLists, - siemClient, }); const errorsResp = importRuleResponse.filter((resp) => isBulkError(resp)) as BulkError[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index fa75be49a61c5..da2808e572cf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -21,7 +21,7 @@ import { import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { patchRulesBulkRoute } from './patch_rules_bulk_route'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { legacyMigrate } from '../../rules/utils'; @@ -80,6 +80,12 @@ describe('patch_rules_bulk', () => { }); test('allows ML Params to be patched', async () => { + clients.rulesClient.get.mockResolvedValueOnce(getRuleMock(getMlRuleParams())); + clients.rulesClient.find.mockResolvedValueOnce({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getMlRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/bulk_update`, @@ -201,7 +207,7 @@ describe('patch_rules_bulk', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown_type" supplied to "type"' + 'Invalid value "unknown_type" supplied to "type",Invalid value "kuery" supplied to "language"' ); }); 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 5aae18f6e0df0..bb4ba585dccaf 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 @@ -8,7 +8,7 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import { Logger } from '@kbn/core/server'; import { patchRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_bulk_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; +import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; @@ -35,7 +35,7 @@ export const patchRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidation(patchRulesBulkSchema), + body: buildRouteValidationNonExact(patchRulesBulkSchema), }, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index 87c2e79922457..40a903ba1b61b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -20,7 +20,7 @@ import { import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { patchRulesRoute } from './patch_rules_route'; import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { legacyMigrate } from '../../rules/utils'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -108,6 +108,12 @@ describe('patch_rules', () => { }); test('allows ML Params to be patched', async () => { + clients.rulesClient.get.mockResolvedValueOnce(getRuleMock(getMlRuleParams())); + clients.rulesClient.find.mockResolvedValueOnce({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getMlRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, @@ -208,7 +214,7 @@ describe('patch_rules', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown_type" supplied to "type"' + 'Invalid value "unknown_type" supplied to "type",Invalid value "kuery" supplied to "language"' ); }); 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 36078d7d1b4e8..e43709602c730 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 @@ -6,8 +6,9 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { patchRulesSchema } from '../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents'; +import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; +import { patchRulesSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -26,7 +27,10 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP { path: DETECTION_ENGINE_RULES_URL, validate: { - body: buildRouteValidation(patchRulesSchema), + // Use non-exact validation because everything is optional in patch - since everything is optional, + // io-ts can't find the right schema from the type specific union and the exact check breaks. + // We do type specific exact validation after fetching the existing rule so we know the rule type. + body: buildRouteValidationNonExact(patchRulesSchema), }, options: { tags: ['access:securitySolution'], @@ -34,6 +38,10 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); + const validationErrors = patchRuleValidateTypeDependents(request.body); + if (validationErrors.length) { + return siemResponse.error({ statusCode: 400, body: validationErrors }); + } try { const params = request.body; const rulesClient = (await context.alerting).getRulesClient(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index de60e82e336ef..8fe7f9cc3a5fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -105,7 +105,7 @@ export const previewRulesRoute = async ( }); } - const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient); + const internalRule = convertCreateAPIToInternalSchema(request.body); const previewRuleParams = internalRule.params; const mlAuthz = buildMlAuthz({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 2fb1dccc64f80..6f3891a2c1dfb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -28,7 +28,7 @@ import { getOutputRuleAlertForRest } from '../__mocks__/utils'; import { PartialRule } from '@kbn/alerting-plugin/server'; import { createRulesAndExceptionsStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; -import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request'; import { @@ -46,7 +46,7 @@ import { LegacyRuleAlertAction } from '../../rule_actions/legacy_types'; import { RuleExceptionsPromiseFromStreams } from './utils/import_rules_utils'; import { partition } from 'lodash/fp'; -type PromiseFromStreams = ImportRulesSchemaDecoded | Error; +type PromiseFromStreams = ImportRulesSchema | Error; const createMockImportRule = async (rule: ReturnType) => { const ndJsonStream = new Readable({ 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 004d38932ea70..15c7c551f699f 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 @@ -15,7 +15,7 @@ import { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; import { RuleExecutionSummary } from '../../../../../common/detection_engine/schemas/common'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; -import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { RuleAlertType, isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError, OutputError } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts index c46efeabd1dac..d1fb4881ae9a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; @@ -13,7 +13,7 @@ describe('checkRuleExceptionReferences', () => { it('returns empty array if rule has no exception list references', () => { const result = checkRuleExceptionReferences({ existingLists: {}, - rule: { ...getImportRulesSchemaDecodedMock(), exceptions_list: [] }, + rule: { ...getImportRulesSchemaMock(), exceptions_list: [] }, }); expect(result).toEqual([[], []]); @@ -30,7 +30,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -54,7 +54,7 @@ describe('checkRuleExceptionReferences', () => { const result = checkRuleExceptionReferences({ existingLists: {}, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -87,7 +87,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -119,7 +119,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts index f068ba04faa8c..4ad7dc51821cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts @@ -6,7 +6,7 @@ */ import { ListArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { BulkError, createBulkErrorObject } from '../../utils'; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts index b67db94bcef00..5206e85e8fdb2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getReferencedExceptionLists } from './gather_referenced_exceptions'; -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); @@ -51,7 +51,7 @@ describe('getReferencedExceptionLists', () => { const result = await getReferencedExceptionLists({ rules: [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts index df57fc54086c1..5158b361bdd64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts @@ -11,7 +11,7 @@ import { getAllListTypes, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; -import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; /** * Helper that takes rules, goes through their referenced exception lists and diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts index 1a9d10d2a3d29..c98bac15d457c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts @@ -13,7 +13,7 @@ import { getFindResultWithSingleHit, } from '../../__mocks__/request_responses'; import { getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; import { createRules } from '../../../rules/create_rules'; import { patchRules } from '../../../rules/patch_rules'; @@ -81,7 +81,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -108,7 +108,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -140,7 +140,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -167,7 +167,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -202,7 +202,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -236,7 +236,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts index 4951854622761..4c913d5a16e5f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts @@ -16,14 +16,13 @@ import { RulesClient } from '@kbn/alerting-plugin/server'; import { ExceptionListClient } from '@kbn/lists-plugin/server'; import { legacyMigrate } from '../../../rules/utils'; import { createBulkErrorObject, ImportRuleResponse } from '../../utils'; +import { createRules } from '../../../rules/create_rules'; import { readRules } from '../../../rules/read_rules'; import { patchRules } from '../../../rules/patch_rules'; -import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/rule_schemas'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { MlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; -import { convertCreateAPIToInternalSchema } from '../../../schemas/rule_converters'; -import { AppClient } from '../../../../../types'; export type PromiseFromStreams = ImportRulesSchema | Error; export interface RuleExceptionsPromiseFromStreams { @@ -58,7 +57,6 @@ export const importRules = async ({ exceptionsClient, spaceId, existingLists, - siemClient, }: { ruleChunks: PromiseFromStreams[][]; rulesResponseAcc: ImportRuleResponse[]; @@ -69,7 +67,6 @@ export const importRules = async ({ exceptionsClient: ExceptionListClient | undefined; spaceId: string; existingLists: Record; - siemClient: AppClient; }) => { let importRuleResponse: ImportRuleResponse[] = [...rulesResponseAcc]; @@ -112,14 +109,9 @@ export const importRules = async ({ }); if (rule == null) { - const internalRule = convertCreateAPIToInternalSchema( - parsedRule, - siemClient, - false - ); - - await rulesClient.create({ - data: internalRule, + await createRules({ + rulesClient, + params: parsedRule, }); resolve({ rule_id: parsedRule.rule_id, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 662e20368ff99..9246beea7bb7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -97,7 +97,7 @@ export const isImportRegular = ( }; export const transformBulkError = ( - ruleId: string, + ruleId: string | undefined, err: Error & { statusCode?: number } ): BulkError => { if (err instanceof CustomHttpRequestError) { 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 deleted file mode 100644 index 00a8b381b05d7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { CreateRulesOptions } from './types'; -import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - -export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - 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', - meta: {}, - machineLearningJobId: undefined, - filters: [], - ruleId: 'rule-1', - immutable: false, - index: ['index-123'], - dataViewId: undefined, - interval: '5m', - maxSignals: 100, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Query with a rule id', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - threatQuery: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threshold: undefined, - timestampOverride: undefined, - throttle: null, - to: 'now', - type: 'query', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], -}); - -export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: 55, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: undefined, - language: undefined, - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: 'new_job_id', - filters: [], - ruleId: 'rule-1', - immutable: false, - index: ['index-123'], - dataViewId: undefined, - interval: '5m', - maxSignals: 100, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Machine Learning Job', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatMapping: undefined, - threatQuery: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - threshold: undefined, - timestampOverride: undefined, - throttle: null, - to: 'now', - type: 'machine_learning', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], -}); - -export const getCreateThreatMatchRulesOptionsMock = (): CreateRulesOptions => ({ - actions: [], - anomalyThreshold: undefined, - author: ['Elastic'], - buildingBlockType: undefined, - concurrentSearches: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - exceptionsList: [], - falsePositives: ['false positive 1', 'false positive 2'], - filters: [], - from: 'now-1m', - immutable: false, - index: ['*'], - dataViewId: undefined, - interval: '5m', - itemsPerSearch: undefined, - language: 'kuery', - license: 'Elastic License', - machineLearningJobId: undefined, - maxSignals: 100, - meta: {}, - name: 'Query with a rule id', - note: '# sample markdown', - outputIndex: 'output-1', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com'], - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleId: 'rule-1', - ruleNameOverride: undefined, - rulesClient: rulesClientMock.create(), - savedId: 'savedId-123', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatIndex: ['filebeat-*'], - threatIndicatorPath: 'threat.indicator', - threatLanguage: 'kuery', - threatMapping: [ - { - entries: [ - { - field: 'file.hash.md5', - type: 'mapping', - value: 'threat.indicator.file.hash.md5', - }, - ], - }, - ], - threatQuery: '*:*', - threshold: undefined, - throttle: null, - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - timestampOverride: undefined, - to: 'now', - type: 'threat_match', - version: 1, -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts index ab683effd4c14..fb62434563183 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts @@ -5,23 +5,24 @@ * 2.0. */ +import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { createRules } from './create_rules'; -import { - getCreateMlRulesOptionsMock, - getCreateThreatMatchRulesOptionsMock, -} from './create_rules.mock'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../common/constants'; +import { + getCreateMachineLearningRulesSchemaMock, + getCreateThreatMatchRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; describe('createRules', () => { it('calls the rulesClient with legacy ML params', async () => { - const ruleOptions = getCreateMlRulesOptionsMock(); - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ rulesClient, params: getCreateMachineLearningRulesSchemaMock() }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, - machineLearningJobId: ['new_job_id'], + anomalyThreshold: 58, + machineLearningJobId: ['typical-ml-job-id'], }), }), }) @@ -29,16 +30,19 @@ describe('createRules', () => { }); it('calls the rulesClient with ML params', async () => { - const ruleOptions = { - ...getCreateMlRulesOptionsMock(), - machineLearningJobId: ['new_job_1', 'new_job_2'], - }; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ + rulesClient, + params: { + ...getCreateMachineLearningRulesSchemaMock(), + machine_learning_job_id: ['new_job_1', 'new_job_2'], + }, + }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, + anomalyThreshold: 58, machineLearningJobId: ['new_job_1', 'new_job_2'], }), }), @@ -47,10 +51,11 @@ describe('createRules', () => { }); it('populates a threatIndicatorPath value for threat_match rule if empty', async () => { - const ruleOptions = getCreateThreatMatchRulesOptionsMock(); - delete ruleOptions.threatIndicatorPath; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + const params = getCreateThreatMatchRulesSchemaMock(); + delete params.threat_indicator_path; + await createRules({ rulesClient, params }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ @@ -62,10 +67,9 @@ describe('createRules', () => { }); it('does not populate a threatIndicatorPath value for other rules if empty', async () => { - const ruleOptions = getCreateMlRulesOptionsMock(); - delete ruleOptions.threatIndicatorPath; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).not.toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ rulesClient, params: getCreateMachineLearningRulesSchemaMock() }); + expect(rulesClient.create).not.toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ 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 9e32ab79f2d07..45cba7c0032be 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 @@ -5,161 +5,29 @@ * 2.0. */ -import { ruleTypeMappings } from '@kbn/securitysolution-rules'; - -import { RuleTypeParams, SanitizedRule } from '@kbn/alerting-plugin/common'; -import { - normalizeMachineLearningJobIds, - normalizeThresholdObject, -} from '../../../../common/detection_engine/utils'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { - DEFAULT_INDICATOR_SOURCE_PATH, - NOTIFICATION_THROTTLE_NO_ACTIONS, - SERVER_APP_ID, -} from '../../../../common/constants'; +import { SanitizedRule } from '@kbn/alerting-plugin/common'; +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../common/constants'; import { CreateRulesOptions } from './types'; -import { PartialFilter } from '../types'; -import { transformToAlertThrottle, transformToNotifyWhen } from './utils'; +import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; +import { RuleParams } from '../schemas/rule_schemas'; export const createRules = async ({ rulesClient, - anomalyThreshold, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - savedId, - timelineId, - timelineTitle, - meta, - machineLearningJobId, - filters, - ruleId, - immutable, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - outputIndex, - name, - setup, - severity, - severityMapping, - tags, - threat, - threatFilters, - threatIndex, - threatIndicatorPath, - threatLanguage, - concurrentSearches, - itemsPerSearch, - threatQuery, - threatMapping, - threshold, - timestampOverride, - throttle, - to, - type, - references, - namespace, - note, - version, - exceptionsList, - actions, + params, id, -}: CreateRulesOptions): Promise> => { - const rule = await rulesClient.create({ + immutable = false, + defaultEnabled = true, +}: CreateRulesOptions): Promise> => { + const internalRule = convertCreateAPIToInternalSchema(params, immutable, defaultEnabled); + const rule = await rulesClient.create({ options: { id, }, - data: { - name, - tags, - alertTypeId: ruleTypeMappings[type], - consumer: SERVER_APP_ID, - params: { - anomalyThreshold, - author, - buildingBlockType, - description, - ruleId, - index, - dataViewId, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - immutable, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - machineLearningJobId: machineLearningJobId - ? normalizeMachineLearningJobIds(machineLearningJobId) - : undefined, - filters, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - setup, - severity, - severityMapping, - threat, - threshold: threshold ? normalizeThresholdObject(threshold) : undefined, - /** - * TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions` - */ - threatFilters: threatFilters as PartialFilter[] | undefined, - threatIndex, - threatIndicatorPath: - threatIndicatorPath ?? - (type === 'threat_match' ? DEFAULT_INDICATOR_SOURCE_PATH : undefined), - threatQuery, - concurrentSearches, - itemsPerSearch, - threatMapping, - threatLanguage, - timestampOverride, - to, - type, - references, - namespace, - note, - version, - exceptionsList, - }, - schedule: { interval }, - enabled, - actions: actions.map(transformRuleToAlertAction), - throttle: transformToAlertThrottle(throttle), - notifyWhen: transformToNotifyWhen(throttle), - }, + data: internalRule, }); // Mute the rule if it is first created with the explicit no actions - if (throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { + if (params.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { await rulesClient.muteAll({ id: rule.id }); } 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 44c2d82fb00e1..50c4b8d378598 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 @@ -9,14 +9,14 @@ import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesAndExceptionsStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { ImportRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; import { getOutputDetailsSample, getSampleDetailsAsNdjson, } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; import { RuleExceptionsPromiseFromStreams } from '../routes/rules/utils/import_rules_utils'; -export const getOutputSample = (): Partial => ({ +export const getOutputSample = (): Partial => ({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -30,7 +30,7 @@ export const getOutputSample = (): Partial => ({ type: 'query', }); -export const getSampleAsNdjson = (sample: Partial): string => { +export const getSampleAsNdjson = (sample: Partial): string => { return `${JSON.stringify(sample)}\n`; }; @@ -53,58 +53,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); 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, - false_positives: [], immutable: false, - exceptions_list: [], - max_signals: 100, - tags: [], - threat: [], - throttle: null, - references: [], - 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, - false_positives: [], immutable: false, - exceptions_list: [], - max_signals: 100, - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -148,58 +122,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); 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, - false_positives: [], immutable: false, - max_signals: 100, - tags: [], - exceptions_list: [], - threat: [], - throttle: null, - references: [], - 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -223,58 +171,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -297,59 +219,33 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); }); @@ -371,61 +267,35 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); - expect(resultOrError[1].message).toEqual( - '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[1].message).toContain( + 'Invalid value "undefined" supplied to "name",Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "severity"' ); 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, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index f1e3ea1653f44..f9e2fdd1b6ba5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -27,7 +27,7 @@ import { importRuleValidateTypeDependents } from '../../../../common/detection_e import { importRulesSchema, ImportRulesSchema, -} from '../../../../common/detection_engine/schemas/request/rule_schemas'; +} from '../../../../common/detection_engine/schemas/request/import_rules_schema'; import { parseNdjsonStrings, createRulesLimitStream, @@ -62,7 +62,7 @@ export const validateRules = ( if (validationErrors.length) { return new BadRequestError(validationErrors.join()); } else { - return schema as ImportRulesSchema; + return schema; } }; return pipe(checked, fold(onLeft, onRight)); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts index 2d92731dbbdfd..433366b69a9da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts @@ -7,7 +7,7 @@ import { getPrepackagedRules } from './get_prepackaged_rules'; import { isEmpty } from 'lodash/fp'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; describe('get_existing_prepackaged_rules', () => { test('should not throw any errors with the existing checked in pre-packaged rules', () => { @@ -16,7 +16,7 @@ describe('get_existing_prepackaged_rules', () => { test('no rule should have the same rule_id as another rule_id', () => { const prePackagedRules = getPrepackagedRules(); - let existingRuleIds: AddPrepackagedRulesSchemaDecoded[] = []; + let existingRuleIds: AddPrepackagedRulesSchema[] = []; prePackagedRules.forEach((rule) => { const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { if (existingRule.rule_id === rule.rule_id) { @@ -35,17 +35,13 @@ describe('get_existing_prepackaged_rules', () => { test('should throw an exception if a pre-packaged rule is not valid', () => { // @ts-expect-error intentionally invalid argument - expect(() => getPrepackagedRules([{ not_valid_made_up_key: true }])).toThrow( - 'name: "(rule name unknown)", rule_id: "(rule rule_id unknown)" within the folder rules/prepackaged_rules is not a valid detection engine rule. Expect the system to not work with pre-packaged rules until this rule is fixed or the file is removed. Error is: 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",Invalid value "undefined" supplied to "version", Full rule contents are:\n{\n "not_valid_made_up_key": true\n}' - ); + expect(() => getPrepackagedRules([{ not_valid_made_up_key: true }])).toThrow(); }); test('should throw an exception with a message having rule_id and name in it', () => { expect(() => // @ts-expect-error intentionally invalid argument getPrepackagedRules([{ name: 'rule name', rule_id: 'id-123' }]) - ).toThrow( - 'name: "rule name", rule_id: "id-123" within the folder rules/prepackaged_rules is not a valid detection engine rule. Expect the system to not work with pre-packaged rules until this rule is fixed or the file is removed. Error is: Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "severity",Invalid value "undefined" supplied to "type",Invalid value "undefined" supplied to "version", Full rule contents are:\n{\n "name": "rule name",\n "rule_id": "id-123"\n}' - ); + ).toThrow(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts index b806bc9681c2f..795cff421b4ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts @@ -14,7 +14,7 @@ import { SavedObjectAttributes } from '@kbn/core/types'; import { addPrepackagedRulesSchema, AddPrepackagedRulesSchema, -} from '../../../../common/detection_engine/schemas/request/rule_schemas'; +} from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; // TODO: convert rules files to TS and add explicit type definitions import { rawRules } from './prepackaged_rules'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 5fcc0aec0f15e..ac86aa6dd860a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -7,9 +7,8 @@ import { getRulesToInstall } from './get_rules_to_install'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request'; describe('get_rules_to_install', () => { test('should return empty array if both rule sets are empty', () => { @@ -18,8 +17,7 @@ describe('get_rules_to_install', () => { }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -29,8 +27,7 @@ describe('get_rules_to_install', () => { }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -40,12 +37,10 @@ describe('get_rules_to_install', () => { }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -55,16 +50,13 @@ describe('get_rules_to_install', () => { }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getRuleMock(getQueryRuleParams()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts index 332dc1756acca..c930a4493b513 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { RuleAlertType } from './types'; export const getRulesToInstall = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts index a1d8458488486..986d68f0d3ffc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts @@ -7,7 +7,7 @@ import { filterInstalledRules, getRulesToUpdate, mergeExceptionLists } from './get_rules_to_update'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; describe('get_rules_to_update', () => { @@ -17,7 +17,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the rule_id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -29,7 +29,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -41,7 +41,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -53,7 +53,7 @@ describe('get_rules_to_update', () => { }); test('should return the rule to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -67,7 +67,7 @@ describe('get_rules_to_update', () => { }); test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -86,11 +86,11 @@ describe('get_rules_to_update', () => { }); test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -112,7 +112,7 @@ describe('get_rules_to_update', () => { }); test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -134,7 +134,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -166,7 +166,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -195,7 +195,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; @@ -217,12 +217,12 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list for multiple rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -259,12 +259,12 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list for mixed rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -315,7 +315,7 @@ describe('get_rules_to_update', () => { describe('filterInstalledRules', () => { test('should return "false" if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -327,7 +327,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -339,7 +339,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -351,7 +351,7 @@ describe('filterInstalledRules', () => { }); test('should return "true" to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -367,7 +367,7 @@ describe('filterInstalledRules', () => { describe('mergeExceptionLists', () => { test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -389,7 +389,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -421,7 +421,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -450,7 +450,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts index c5d7fa2fa9b79..4a6e08e945879 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { RuleAlertType } from './types'; /** 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 15467fd82b22c..00818abbab596 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 @@ -7,27 +7,19 @@ import { SanitizedRule, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { RulesClient } from '@kbn/alerting-plugin/server'; -import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; -import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; -import { AppClient } from '../../../types'; -import { InternalRuleCreate } from '../schemas/rule_schemas'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { createRules } from './create_rules'; export const installPrepackagedRules = ( rulesClient: RulesClient, - rules: AddPrepackagedRulesSchema[], - siemClient: AppClient + rules: AddPrepackagedRulesSchema[] ): Array>> => rules.reduce>>>((acc, rule) => { - const internalRuleCreate: InternalRuleCreate = convertCreateAPIToInternalSchema( - rule, - siemClient, - true, - false - ); return [ ...acc, - rulesClient.create({ - data: internalRuleCreate, + createRules({ + rulesClient, + params: rule, }), ]; }, []); 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 deleted file mode 100644 index 9b45413ddcb0c..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - -import { PatchRulesOptions } from './types'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; - -export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - 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', - meta: {}, - machineLearningJobId: undefined, - filters: [], - index: ['index-123'], - interval: '5m', - maxSignals: 100, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Query with a rule id', - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - throttle: null, - concurrentSearches: undefined, - itemsPerSearch: undefined, - timestampOverride: undefined, - to: 'now', - type: 'query', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], - rule: getRuleMock(getQueryRuleParams()), -}); - -export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: 55, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: undefined, - language: undefined, - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: 'new_job_id', - filters: [], - index: ['index-123'], - interval: '5m', - maxSignals: 100, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Machine Learning Job', - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - throttle: null, - concurrentSearches: undefined, - itemsPerSearch: undefined, - timestampOverride: undefined, - to: 'now', - type: 'machine_learning', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], - rule: getRuleMock(getMlRuleParams()), -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts index e2e09e130d5c3..d79f36f029a98 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts @@ -5,71 +5,67 @@ * 2.0. */ -import { RulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; +import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; import { patchRules } from './patch_rules'; -import { getPatchRulesOptionsMock, getPatchMlRulesOptionsMock } from './patch_rules.mock'; -import { PatchRulesOptions } from './types'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { + getCreateMachineLearningRulesSchemaMock, + getCreateRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; describe('patchRules', () => { it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), enabled: false, }; - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.disable).toHaveBeenCalledWith( + const rule = { + ...getRuleMock(getQueryRuleParams()), + enabled: true, + }; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.disable).toHaveBeenCalledWith( expect.objectContaining({ - id: ruleOptions.rule?.id, + id: rule.id, }) ); }); it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), enabled: true, }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.enable).toHaveBeenCalledWith( + const rule = { + ...getRuleMock(getQueryRuleParams()), + enabled: false, + }; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.enable).toHaveBeenCalledWith( expect.objectContaining({ - id: ruleOptions.rule?.id, + id: rule.id, }) ); }); it('calls the rulesClient with legacy ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, - enabled: true, - }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + const params = getCreateMachineLearningRulesSchemaMock(); + const rule = getRuleMock(getMlRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, - machineLearningJobId: ['new_job_id'], + anomalyThreshold: 58, + machineLearningJobId: ['typical-ml-job-id'], }), }), }) @@ -77,24 +73,19 @@ describe('patchRules', () => { }); it('calls the rulesClient with new ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, - machineLearningJobId: ['new_job_1', 'new_job_2'], - enabled: true, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateMachineLearningRulesSchemaMock(), + machine_learning_job_id: ['new_job_1', 'new_job_2'], }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rule = getRuleMock(getMlRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, + anomalyThreshold: 58, machineLearningJobId: ['new_job_1', 'new_job_2'], }), }), @@ -104,9 +95,9 @@ describe('patchRules', () => { describe('regression tests', () => { it("updates the rule's actions if provided", async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), actions: [ { action_type_id: '.slack', @@ -118,11 +109,10 @@ describe('patchRules', () => { }, ], }; - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rule = getRuleMock(getQueryRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ actions: [ @@ -141,25 +131,23 @@ describe('patchRules', () => { }); it('does not update actions if none are specified', async () => { - const ruleOptions = getPatchRulesOptionsMock(); - delete ruleOptions.actions; - if (ruleOptions.rule != null) { - ruleOptions.rule.actions = [ - { - actionTypeId: '.slack', - id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', - }, - group: 'default', + const rulesClient = rulesClientMock.create(); + const params = getCreateRulesSchemaMock(); + delete params.actions; + const rule = getRuleMock(getQueryRuleParams()); + rule.actions = [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', }, - ]; - } - (ruleOptions.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + group: 'default', + }, + ]; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ actions: [ 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 1da37d2487408..620e557d1dbfb 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 @@ -8,84 +8,27 @@ import { Readable } from 'stream'; import { SavedObjectAttributes, SavedObjectsClientContract } from '@kbn/core/server'; -import type { - MachineLearningJobIdOrUndefined, - From, - RiskScore, - RiskScoreMapping, - ThreatIndexOrUndefined, - ThreatQueryOrUndefined, - ThreatMappingOrUndefined, - ThreatFiltersOrUndefined, - ThreatLanguageOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatIndicatorPathOrUndefined, - Threats, - Type, - LanguageOrUndefined, - SeverityMapping, - Severity, - MaxSignals, - ThrottleOrNull, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import type { Version } from '@kbn/securitysolution-io-ts-types'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; -import type { ListArray } from '@kbn/securitysolution-io-ts-list-types'; import { RulesClient, PartialRule, BulkEditOperation } from '@kbn/alerting-plugin/server'; import { SanitizedRule } from '@kbn/alerting-plugin/common'; import { UpdateRulesSchema } from '../../../../common/detection_engine/schemas/request'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { - FalsePositives, - RuleId, - Immutable, - Interval, - OutputIndex, - Name, - Tags, - To, - References, - AnomalyThresholdOrUndefined, - QueryOrUndefined, - SavedIdOrUndefined, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - IndexOrUndefined, - NoteOrUndefined, - MetaOrUndefined, - Description, - Enabled, Id, IdOrUndefined, RuleIdOrUndefined, - ThresholdOrUndefined, PerPageOrUndefined, PageOrUndefined, SortFieldOrUndefined, QueryFilterOrUndefined, FieldsOrUndefined, SortOrderOrUndefined, - Author, - LicenseOrUndefined, - TimestampOverrideOrUndefined, - BuildingBlockTypeOrUndefined, - RuleNameOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, - NamespaceOrUndefined, - DataViewIdOrUndefined, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, } from '../../../../common/detection_engine/schemas/common'; -import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { IRuleExecutionLogForRoutes } from '../rule_execution_log'; -import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { CreateRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; export type RuleAlertType = SanitizedRule; @@ -125,65 +68,12 @@ export const isAlertType = ( return ruleTypeValues.includes(partialAlert.alertTypeId as string); }; -export interface CreateRulesOptions { +export interface CreateRulesOptions { rulesClient: RulesClient; - anomalyThreshold: AnomalyThresholdOrUndefined; - author: Author; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: Description; - enabled: Enabled; - timestampField: TimestampFieldOrUndefined; - eventCategoryOverride: EventCategoryOverrideOrUndefined; - tiebreakerField: TiebreakerFieldOrUndefined; - falsePositives: FalsePositives; - from: From; - query: QueryOrUndefined; - language: LanguageOrUndefined; - savedId: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId: MachineLearningJobIdOrUndefined; - filters: PartialFilter[]; - ruleId: RuleId; - immutable: Immutable; - index: IndexOrUndefined; - dataViewId: DataViewIdOrUndefined; - interval: Interval; - license: LicenseOrUndefined; - maxSignals: MaxSignals; - relatedIntegrations: RelatedIntegrationArray | undefined; - requiredFields: RequiredFieldArray | undefined; - riskScore: RiskScore; - riskScoreMapping: RiskScoreMapping; - ruleNameOverride: RuleNameOverrideOrUndefined; - outputIndex: OutputIndex; - name: Name; - setup: SetupGuide | undefined; - severity: Severity; - severityMapping: SeverityMapping; - tags: Tags; - threat: Threats; - threshold: ThresholdOrUndefined; - threatFilters: ThreatFiltersOrUndefined; - threatIndex: ThreatIndexOrUndefined; - threatIndicatorPath: ThreatIndicatorPathOrUndefined; - threatQuery: ThreatQueryOrUndefined; - threatMapping: ThreatMappingOrUndefined; - concurrentSearches: ConcurrentSearchesOrUndefined; - itemsPerSearch: ItemsPerSearchOrUndefined; - threatLanguage: ThreatLanguageOrUndefined; - throttle: ThrottleOrNull; - timestampOverride: TimestampOverrideOrUndefined; - to: To; - type: Type; - references: References; - note: NoteOrUndefined; - version: Version; - exceptionsList: ListArray; - actions: RuleAlertAction[]; - namespace?: NamespaceOrUndefined; + params: T; id?: string; + immutable?: boolean; + defaultEnabled?: boolean; } export interface UpdateRulesOptions { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 2a54ce1e976d4..dc28f2184323c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -10,10 +10,13 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { getRuleMock, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; import { updatePrepackagedRules } from './update_prepacked_rules'; import { patchRules } from './patch_rules'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { + getAddPrepackagedRulesSchemaMock, + getAddPrepackagedThreatMatchRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { ruleExecutionLogMock } from '../rule_execution_log/__mocks__'; import { legacyMigrate } from './utils'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getQueryRuleParams, getThreatRuleParams } from '../schemas/rule_schemas.mock'; jest.mock('./patch_rules'); @@ -47,27 +50,29 @@ describe('updatePrepackagedRules', () => { params: {}, }, ]; - const outputIndex = 'outputIndex'; - const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); + const prepackagedRule = getAddPrepackagedRulesSchemaMock(); rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); await updatePrepackagedRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, actions }], - outputIndex, ruleExecutionLog ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - actions: undefined, + params: expect.objectContaining({ + actions: undefined, + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - enabled: undefined, + params: expect.objectContaining({ + enabled: undefined, + }), }) ); }); @@ -78,32 +83,41 @@ describe('updatePrepackagedRules', () => { threat_indicator_path: 'test.path', threat_query: 'threat:*', }; - const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const prepackagedRule = getAddPrepackagedThreatMatchRulesSchemaMock(); + rulesClient.find.mockResolvedValue({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getThreatRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getThreatRuleParams())); await updatePrepackagedRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, ...updatedThreatParams }], - 'output-index', ruleExecutionLog ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatIndicatorPath: 'test.path', + params: expect.objectContaining({ + threat_indicator_path: 'test.path', + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatIndex: ['test-index'], + params: expect.objectContaining({ + threat_index: ['test-index'], + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatQuery: 'threat:*', + params: expect.objectContaining({ + threat_query: 'threat:*', + }), }) ); }); 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 754e4b1fa90af..0cc97ee6b1194 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 @@ -8,17 +8,17 @@ import { chunk } from 'lodash/fp'; import { SavedObjectsClientContract } from '@kbn/core/server'; import { RulesClient, PartialRule } from '@kbn/alerting-plugin/server'; -import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../common/constants'; import { patchRules } from './patch_rules'; import { readRules } from './read_rules'; -import { InternalRuleCreate, RuleParams } from '../schemas/rule_schemas'; +import { RuleParams } from '../schemas/rule_schemas'; import { legacyMigrate } from './utils'; import { deleteRules } from './delete_rules'; import { PrepackagedRulesError } from '../routes/rules/add_prepackaged_rules_route'; import { IRuleExecutionLogForRoutes } from '../rule_execution_log'; -import { AppClient } from '../../../types'; -import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; +import { createRules } from './create_rules'; +import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions'; /** * Updates the prepackaged rules given a set of rules and output index. @@ -27,13 +27,11 @@ import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; * @param rulesClient Alerting client * @param spaceId Current user spaceId * @param rules The rules to apply the update for - * @param outputIndex The output index to apply the update to. */ export const updatePrepackagedRules = async ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, rules: AddPrepackagedRulesSchema[], - siemClient: AppClient, ruleExecutionLog: IRuleExecutionLogForRoutes ): Promise => { const ruleChunks = chunk(MAX_RULES_TO_UPDATE_IN_PARALLEL, rules); @@ -42,7 +40,6 @@ export const updatePrepackagedRules = async ( rulesClient, savedObjectsClient, ruleChunk, - siemClient, ruleExecutionLog ); await Promise.all(rulePromises); @@ -54,14 +51,12 @@ export const updatePrepackagedRules = async ( * @param rulesClient Alerting client * @param spaceId Current user spaceId * @param rules The rules to apply the update for - * @param outputIndex The output index to apply the update to. * @returns Promise of what was updated. */ export const createPromises = ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, rules: AddPrepackagedRulesSchema[], - siemClient: AppClient, ruleExecutionLog: IRuleExecutionLogForRoutes ): Array | null>> => { return rules.map(async (rule) => { @@ -91,15 +86,15 @@ export const createPromises = ( ruleExecutionLog, }); - const internalRuleCreate: InternalRuleCreate = { - ...convertCreateAPIToInternalSchema(rule, siemClient, true), - // Force the prepackaged rule to use the enabled state from the existing rule, - // regardless of what the prepackaged rule says - enabled: migratedRule.enabled, - }; - - return rulesClient.create({ - data: internalRuleCreate, + return createRules({ + rulesClient, + params: { + ...rule, + // Force the prepackaged rule to use the enabled state from the existing rule, + // regardless of what the prepackaged rule says + enabled: migratedRule.enabled, + actions: migratedRule.actions.map(transformAlertToRuleAction), + }, }); } else { return patchRules({ @@ -107,8 +102,9 @@ export const createPromises = ( rule: migratedRule, params: { ...rule, - // Again, force enabled to use the enabled state from the existing rule - enabled: migratedRule.enabled, + // Force enabled to use the enabled state from the existing rule by passing in undefined to patchRules + enabled: undefined, + actions: undefined, }, }); } 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 e59b135b636e1..163fc81691739 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 @@ -6,9 +6,6 @@ */ import { - calculateInterval, - calculateVersion, - calculateName, transformToNotifyWhen, transformToAlertThrottle, transformFromAlertThrottle, @@ -88,221 +85,6 @@ const getRuleLegacyActions = (): SanitizedRule => } as unknown as SanitizedRule); describe('utils', () => { - describe('#calculateInterval', () => { - test('given a undefined interval, it returns the ruleInterval ', () => { - const interval = calculateInterval(undefined, '10m'); - expect(interval).toEqual('10m'); - }); - - test('given a undefined ruleInterval, it returns a undefined interval ', () => { - const interval = calculateInterval('10m', undefined); - expect(interval).toEqual('10m'); - }); - - test('given both an undefined ruleInterval and a undefined interval, it returns 5m', () => { - const interval = calculateInterval(undefined, undefined); - expect(interval).toEqual('5m'); - }); - }); - - describe('#calculateVersion', () => { - test('returning the same version number if given an immutable but no updated version number', () => { - expect( - calculateVersion(true, 1, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - to: undefined, - timestampOverride: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(1); - }); - - test('returning an updated version number if given an immutable and an updated version number', () => { - expect( - calculateVersion(true, 2, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - to: undefined, - timestampOverride: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(2); - }); - - test('returning an updated version number if not given an immutable but but an updated description', () => { - expect( - calculateVersion(false, 1, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - to: undefined, - timestampOverride: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(2); - }); - }); - - describe('#calculateName', () => { - test('should return the updated name when it and originalName is there', () => { - const name = calculateName({ updatedName: 'updated', originalName: 'original' }); - expect(name).toEqual('updated'); - }); - - test('should return the updated name when originalName is undefined', () => { - const name = calculateName({ updatedName: 'updated', originalName: undefined }); - expect(name).toEqual('updated'); - }); - - test('should return the original name when updatedName is undefined', () => { - const name = calculateName({ updatedName: undefined, originalName: 'original' }); - expect(name).toEqual('original'); - }); - - test('should return untitled when both updatedName and originalName is undefined', () => { - const name = calculateName({ updatedName: undefined, originalName: undefined }); - expect(name).toEqual('untitled'); - }); - }); - describe('#transformToNotifyWhen', () => { test('"null" throttle returns "null" notify', () => { expect(transformToNotifyWhen(null)).toEqual(null); 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 35060927f3762..6f68e6b57f799 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 @@ -5,65 +5,10 @@ * 2.0. */ -import { pickBy, isEmpty } from 'lodash/fp'; -import type { - FromOrUndefined, - MachineLearningJobIdOrUndefined, - RiskScoreMappingOrUndefined, - RiskScoreOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatFiltersOrUndefined, - ThreatIndexOrUndefined, - ThreatIndicatorPathOrUndefined, - ThreatLanguageOrUndefined, - ThreatMappingOrUndefined, - ThreatQueryOrUndefined, - ThreatsOrUndefined, - TypeOrUndefined, - LanguageOrUndefined, - SeverityOrUndefined, - SeverityMappingOrUndefined, - MaxSignalsOrUndefined, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import type { ListArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; -import type { VersionOrUndefined } from '@kbn/securitysolution-io-ts-types'; +import { isEmpty } from 'lodash/fp'; import { SavedObjectReference } from '@kbn/core/server'; import { RuleAction, RuleNotifyWhenType, SanitizedRule } from '@kbn/alerting-plugin/common'; import { RulesClient } from '@kbn/alerting-plugin/server'; -import { - DescriptionOrUndefined, - AnomalyThresholdOrUndefined, - QueryOrUndefined, - SavedIdOrUndefined, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - IndexOrUndefined, - NoteOrUndefined, - MetaOrUndefined, - FalsePositivesOrUndefined, - OutputIndexOrUndefined, - IntervalOrUndefined, - NameOrUndefined, - TagsOrUndefined, - ToOrUndefined, - ThresholdOrUndefined, - ReferencesOrUndefined, - AuthorOrUndefined, - BuildingBlockTypeOrUndefined, - LicenseOrUndefined, - RuleNameOverrideOrUndefined, - TimestampOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, - NamespaceOrUndefined, - DataViewIdOrUndefined, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../../../../common/detection_engine/schemas/common'; -import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -81,128 +26,6 @@ import { transformAlertToRuleAction } from '../../../../common/detection_engine/ import { legacyRuleActionsSavedObjectType } from '../rule_actions/legacy_saved_object_mappings'; import { LegacyMigrateParams } from './types'; -export const calculateInterval = ( - interval: string | undefined, - ruleInterval: string | undefined -): string => { - if (interval != null) { - return interval; - } else if (ruleInterval != null) { - return ruleInterval; - } else { - return '5m'; - } -}; - -export interface UpdateProperties { - author: AuthorOrUndefined; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: DescriptionOrUndefined; - timestampField: TimestampFieldOrUndefined; - eventCategoryOverride: EventCategoryOverrideOrUndefined; - tiebreakerField: TiebreakerFieldOrUndefined; - falsePositives: FalsePositivesOrUndefined; - from: FromOrUndefined; - query: QueryOrUndefined; - language: LanguageOrUndefined; - license: LicenseOrUndefined; - savedId: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId: MachineLearningJobIdOrUndefined; - filters: PartialFilter[] | undefined; - index: IndexOrUndefined; - dataViewId: DataViewIdOrUndefined; - interval: IntervalOrUndefined; - maxSignals: MaxSignalsOrUndefined; - relatedIntegrations: RelatedIntegrationArray | undefined; - requiredFields: RequiredFieldArray | undefined; - riskScore: RiskScoreOrUndefined; - riskScoreMapping: RiskScoreMappingOrUndefined; - ruleNameOverride: RuleNameOverrideOrUndefined; - outputIndex: OutputIndexOrUndefined; - name: NameOrUndefined; - setup: SetupGuide | undefined; - severity: SeverityOrUndefined; - severityMapping: SeverityMappingOrUndefined; - tags: TagsOrUndefined; - threat: ThreatsOrUndefined; - threshold: ThresholdOrUndefined; - threatFilters: ThreatFiltersOrUndefined; - threatIndex: ThreatIndexOrUndefined; - threatIndicatorPath: ThreatIndicatorPathOrUndefined; - threatQuery: ThreatQueryOrUndefined; - threatMapping: ThreatMappingOrUndefined; - threatLanguage: ThreatLanguageOrUndefined; - concurrentSearches: ConcurrentSearchesOrUndefined; - itemsPerSearch: ItemsPerSearchOrUndefined; - timestampOverride: TimestampOverrideOrUndefined; - to: ToOrUndefined; - type: TypeOrUndefined; - references: ReferencesOrUndefined; - note: NoteOrUndefined; - version: VersionOrUndefined; - exceptionsList: ListArrayOrUndefined; - anomalyThreshold: AnomalyThresholdOrUndefined; - namespace: NamespaceOrUndefined; -} - -export const calculateVersion = ( - immutable: boolean, - currentVersion: number, - updateProperties: UpdateProperties -): number => { - // early return if we are pre-packaged/immutable rule to be safe. We are never responsible - // for changing the version number of an immutable. Immutables are only responsible for changing - // their own version number. This would be really bad if an immutable version number is bumped by us - // due to a bug, hence the extra check and early bail if that is detected. - if (immutable === true) { - if (updateProperties.version != null) { - // we are an immutable rule but we are asking to update the version number so go ahead - // and update it to what is asked. - return updateProperties.version; - } else { - // we are immutable and not asking to update the version number so return the existing version - return currentVersion; - } - } - - // white list all properties but the enabled/disabled flag. We don't want to auto-increment - // the version number if only the enabled/disabled flag is being set. Likewise if we get other - // properties we are not expecting such as updatedAt we do not to cause a version number bump - // on that either. - const removedNullValues = removeUndefined(updateProperties); - if (isEmpty(removedNullValues)) { - return currentVersion; - } else { - return currentVersion + 1; - } -}; - -export const removeUndefined = (obj: object) => { - return pickBy((value: unknown) => value != null, obj); -}; - -export const calculateName = ({ - updatedName, - originalName, -}: { - updatedName: string | undefined; - originalName: string | undefined; -}): string => { - if (updatedName != null) { - return updatedName; - } else if (originalName != null) { - return originalName; - } else { - // You really should never get to this point. This is a fail safe way to send back - // the name of "untitled" just in case a rule name became null or undefined at - // some point since TypeScript allows it. - return 'untitled'; - } -}; - /** * Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen * on their saved object. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index 105e1e1c5764e..4e5aa1f444ed9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -6,13 +6,10 @@ */ import uuid from 'uuid'; -import * as t from 'io-ts'; -import { fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; -import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; +import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; import { @@ -43,24 +40,27 @@ import { RuleExecutionSummary } from '../../../../common/detection_engine/schema import { CreateRulesSchema, CreateTypeSpecific, - eqlFullPatchSchema, + eqlPatchParams, EqlPatchParams, FullResponseSchema, - machineLearningFullPatchSchema, + machineLearningPatchParams, MachineLearningPatchParams, - queryFullPatchSchema, + queryPatchParams, QueryPatchParams, ResponseTypeSpecific, - savedQueryFullPatchSchema, + savedQueryPatchParams, SavedQueryPatchParams, - threatMatchFullPatchSchema, + threatMatchPatchParams, ThreatMatchPatchParams, - thresholdFullPatchSchema, + thresholdPatchParams, ThresholdPatchParams, } from '../../../../common/detection_engine/schemas/request'; -import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; -import { AppClient } from '../../../types'; -import { DEFAULT_MAX_SIGNALS, SERVER_APP_ID } from '../../../../common/constants'; +import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; +import { + DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, + SERVER_APP_ID, +} from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { transformFromAlertThrottle, @@ -109,7 +109,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif threatMapping: params.threat_mapping, threatLanguage: params.threat_language, threatIndex: params.threat_index, - threatIndicatorPath: params.threat_indicator_path, + threatIndicatorPath: params.threat_indicator_path ?? DEFAULT_INDICATOR_SOURCE_PATH, concurrentSearches: params.concurrent_searches, itemsPerSearch: params.items_per_search, }; @@ -161,18 +161,6 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif } }; -const validateSchema = (input: unknown, schema: t.Type): A | BadRequestError => { - const decoded = schema.decode(input); - const checked = exactCheck(input, decoded); - const onLeft = (errors: t.Errors): BadRequestError | A => { - return new BadRequestError(formatErrors(errors).join()); - }; - const onRight = (value: A): BadRequestError | A => { - return value; - }; - return pipe(checked, fold(onLeft, onRight)); -}; - const patchEqlParams = ( params: EqlPatchParams, existingRule: EqlRuleParams @@ -274,56 +262,64 @@ const patchMachineLearningParams = ( }; }; +export const parseValidationError = (error: string | null): BadRequestError => { + if (error != null) { + return new BadRequestError(error); + } else { + return new BadRequestError('unknown validation error'); + } +}; + // TODO: unit test this function to make sure schemas pass validation as expected export const patchTypeSpecificSnakeToCamel = ( params: PatchRulesSchema, existingRule: RuleParams ): TypeSpecificRuleParams | BadRequestError => { - // Each rule type validates the full patch schema for the specific type of rule to ensure that - // params from other rule types are not being passed in. Otherwise, since the `type` is not required - // on patch requests, type specific params from one rule type could be passed in when patching a - // different rule type and they'd be ignored without a warning or error. - // This validation ensures that e.g. only valid EQL rule params are passed in when patching an EQL rule. + // Here we do the validation of patch params by rule type to ensure that the fields that are + // passed in to patch are of the correct type, e.g. `query` is a string. Since the combined patch schema + // is a union of types where everything is optional, it's hard to do the validation before we know the rule type - + // a patch request that defines `event_category_override` as a number would not be assignable to the EQL patch schema, + // but would be assignable to the other rule types since they don't specify `event_category_override`. switch (existingRule.type) { case 'eql': { - const validated = validateSchema(params, eqlFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, eqlPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchEqlParams(validated, existingRule); } case 'threat_match': { - const validated = validateSchema(params, threatMatchFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, threatMatchPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchThreatMatchParams(validated, existingRule); } case 'query': { - const validated = validateSchema(params, queryFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, queryPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchQueryParams(validated, existingRule); } case 'saved_query': { - const validated = validateSchema(params, savedQueryFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, savedQueryPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchSavedQueryParams(validated, existingRule); } case 'threshold': { - const validated = validateSchema(params, thresholdFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, thresholdPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchThresholdParams(validated, existingRule); } case 'machine_learning': { - const validated = validateSchema(params, machineLearningFullPatchSchema); - if (validated instanceof BadRequestError) { - return validated; + const [validated, error] = validateNonExact(params, machineLearningPatchParams); + if (validated == null) { + return parseValidationError(error); } return patchMachineLearningParams(validated, existingRule); } @@ -332,6 +328,7 @@ export const patchTypeSpecificSnakeToCamel = ( } } }; + const versionExcludedKeys = ['enabled', 'id', 'rule_id']; const shouldUpdateVersion = (params: PatchRulesSchema): boolean => { for (const key in params) { @@ -404,7 +401,6 @@ export const convertPatchAPIToInternalSchema = ( export const convertCreateAPIToInternalSchema = ( input: CreateRulesSchema, - siemClient: AppClient, immutable = false, defaultEnabled = true ): InternalRuleCreate => { @@ -424,7 +420,9 @@ export const convertCreateAPIToInternalSchema = ( from: input.from ?? 'now-6m', immutable, license: input.license, - outputIndex: input.output_index ?? siemClient.getSignalsIndex(), + // outputIndex is no longer used in the detection engine so there's no point in adding it + // to rules + outputIndex: '', timelineId: input.timeline_id, timelineTitle: input.timeline_title, meta: input.meta, 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 10f3b45ced94d..4b3f73a88d4af 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 @@ -5,106 +5,8 @@ * 2.0. */ -import { - From, - MachineLearningJobIdOrUndefined, - RiskScore, - RiskScoreMappingOrUndefined, - ThreatIndexOrUndefined, - ThreatQueryOrUndefined, - ThreatMappingOrUndefined, - ThreatLanguageOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatIndicatorPathOrUndefined, - ThreatsOrUndefined, - Type, - LanguageOrUndefined, - Severity, - SeverityMappingOrUndefined, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { Version } from '@kbn/securitysolution-io-ts-types'; - -import type { ListArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; import type { Filter } from '@kbn/es-query'; -import { RuleTypeParams as AlertingRuleTypeParams } from '@kbn/alerting-plugin/common'; -import { - AnomalyThresholdOrUndefined, - Description, - NoteOrUndefined, - ThresholdOrUndefined, - FalsePositives, - Immutable, - IndexOrUndefined, - OutputIndex, - QueryOrUndefined, - References, - SavedIdOrUndefined, - To, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - MetaOrUndefined, - RuleId, - AuthorOrUndefined, - BuildingBlockTypeOrUndefined, - LicenseOrUndefined, - RuleNameOverrideOrUndefined, - TimestampOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, -} from '../../../common/detection_engine/schemas/common/schemas'; export type PartialFilter = Partial; -export interface RuleTypeParams extends AlertingRuleTypeParams { - anomalyThreshold?: AnomalyThresholdOrUndefined; - author: AuthorOrUndefined; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: Description; - note: NoteOrUndefined; - timestampField?: TimestampFieldOrUndefined; - eventCategoryOverride?: EventCategoryOverrideOrUndefined; - tiebreakerField?: TiebreakerFieldOrUndefined; - falsePositives: FalsePositives; - from: From; - ruleId: RuleId; - immutable: Immutable; - index?: IndexOrUndefined; - language?: LanguageOrUndefined; - license: LicenseOrUndefined; - outputIndex: OutputIndex; - savedId?: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId?: MachineLearningJobIdOrUndefined; - query?: QueryOrUndefined; - filters?: unknown[]; - maxSignals: MaxSignals; - namespace?: string; - riskScore: RiskScore; - riskScoreMapping: RiskScoreMappingOrUndefined; - ruleNameOverride: RuleNameOverrideOrUndefined; - severity: Severity; - severityMapping: SeverityMappingOrUndefined; - threat: ThreatsOrUndefined; - threshold?: ThresholdOrUndefined; - threatFilters?: unknown[] | undefined; - threatIndex?: ThreatIndexOrUndefined; - threatIndicatorPath?: ThreatIndicatorPathOrUndefined; - threatQuery?: ThreatQueryOrUndefined; - threatMapping?: ThreatMappingOrUndefined; - threatLanguage?: ThreatLanguageOrUndefined; - timestampOverride: TimestampOverrideOrUndefined; - to: To; - type: Type; - references: References; - version: Version; - exceptionsList: ListArrayOrUndefined; - concurrentSearches?: ConcurrentSearchesOrUndefined; - itemsPerSearch?: ItemsPerSearchOrUndefined; -} - export type RefreshTypes = false | 'wait_for'; diff --git a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts index 41e079a381f50..8b54738d2d05b 100644 --- a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts @@ -38,6 +38,17 @@ export const buildRouteValidation = ) ); +export const buildRouteValidationNonExact = + >(schema: T): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + schema.decode(inputValue), + fold>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); + export const buildRouteValidationWithExcess = < T extends rt.InterfaceType | GenericIntersectionC | rt.PartialType, diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index 8c6504e478a08..a47beea25810f 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -9,7 +9,7 @@ import { Transform } from 'stream'; import { has, isString } from 'lodash/fp'; import { createMapStream, createFilterStream } from '@kbn/utils'; -import { ImportRulesSchemaDecoded } from '../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../common/detection_engine/schemas/request/import_rules_schema'; export interface RulesObjectsExportResultDetails { /** number of successfully exported objects */ @@ -29,13 +29,13 @@ export const parseNdjsonStrings = (): Transform => { }; export const filterExportedCounts = (): Transform => { - return createFilterStream( + return createFilterStream( (obj) => obj != null && !has('exported_count', obj) ); }; export const filterExceptions = (): Transform => { - return createFilterStream( + return createFilterStream( (obj) => obj != null && !has('list_id', obj) ); };