From 845f1529aba65b1ee3c35e201e7c3f564a82fa54 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Fri, 29 Jul 2022 08:55:52 -0700 Subject: [PATCH 01/11] Remove legacy rules schema --- .../schemas/request/rule_schemas.ts | 29 +- .../schemas/response/index.ts | 2 - .../response/rules_bulk_schema.test.ts | 22 +- .../schemas/response/rules_bulk_schema.ts | 4 +- .../schemas/response/rules_schema.mocks.ts | 40 +- .../schemas/response/rules_schema.test.ts | 691 +----------------- .../schemas/response/rules_schema.ts | 366 ---------- .../type_timeline_only_schema.test.ts | 66 -- .../response/type_timeline_only_schema.ts | 21 - .../detection_engine/rules/__mocks__/api.ts | 16 +- .../containers/detection_engine/rules/api.ts | 16 +- .../routes/__mocks__/utils.ts | 7 +- .../routes/rules/patch_rules_route.ts | 4 +- .../routes/rules/update_rules_route.ts | 4 +- .../detection_engine/routes/rules/utils.ts | 8 +- .../routes/rules/validate.test.ts | 12 +- .../detection_engine/routes/rules/validate.ts | 19 +- .../rules/get_export_by_object_ids.ts | 6 +- .../rules/get_export_details_ndjson.ts | 4 +- .../signals/__mocks__/es_results.ts | 2 + .../lib/detection_engine/signals/types.ts | 4 +- .../group1/perform_bulk_action.ts | 4 +- .../group1/update_rules_bulk.ts | 10 +- .../group3/create_exceptions.ts | 7 +- .../utils/get_complex_rule.ts | 6 +- .../utils/get_complex_rule_output.ts | 4 +- .../utils/get_rule.ts | 4 +- .../utils/get_simple_ml_rule_output.ts | 11 +- .../utils/get_simple_rule_output.ts | 26 +- ...simple_rule_output_with_web_hook_action.ts | 3 +- .../get_simple_rule_output_without_rule_id.ts | 3 +- .../remove_server_generated_properties.ts | 2 +- .../utils/resolve_simple_rule_output.ts | 10 +- 33 files changed, 175 insertions(+), 1258 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts delete mode 100644 x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts 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 e55fb9b6d763c..d2a66f14f0f43 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 @@ -270,7 +270,7 @@ const { patch: threatMatchPatchParams, response: threatMatchResponseParams, } = buildAPISchemas(threatMatchRuleParams); -export { threatMatchCreateParams }; +export { threatMatchCreateParams, threatMatchResponseParams }; const queryRuleParams = { required: { @@ -293,7 +293,7 @@ const { response: queryResponseParams, } = buildAPISchemas(queryRuleParams); -export { queryCreateParams }; +export { queryCreateParams, queryResponseParams }; const savedQueryRuleParams = { required: { @@ -318,7 +318,7 @@ const { response: savedQueryResponseParams, } = buildAPISchemas(savedQueryRuleParams); -export { savedQueryCreateParams }; +export { savedQueryCreateParams, savedQueryResponseParams }; const thresholdRuleParams = { required: { @@ -342,7 +342,7 @@ const { response: thresholdResponseParams, } = buildAPISchemas(thresholdRuleParams); -export { thresholdCreateParams }; +export { thresholdCreateParams, thresholdResponseParams }; const machineLearningRuleParams = { required: { @@ -359,7 +359,7 @@ const { response: machineLearningResponseParams, } = buildAPISchemas(machineLearningRuleParams); -export { machineLearningCreateParams }; +export { machineLearningCreateParams, machineLearningResponseParams }; const newTermsRuleParams = { required: { @@ -383,7 +383,7 @@ const { response: newTermsResponseParams, } = buildAPISchemas(newTermsRuleParams); -export { newTermsCreateParams }; +export { newTermsCreateParams, newTermsResponseParams }; // --------------------------------------- // END type specific parameter definitions @@ -489,14 +489,27 @@ const responseOptionalFields = { execution_summary: RuleExecutionSummary, }; -export const fullResponseSchema = t.intersection([ +const sharedResponseSchema = t.intersection([ baseResponseParams, - responseTypeSpecific, t.exact(t.type(responseRequiredFields)), t.exact(t.partial(responseOptionalFields)), ]); +type SharedResponseSchema = t.TypeOf; +export const fullResponseSchema = t.intersection([sharedResponseSchema, responseTypeSpecific]); export type FullResponseSchema = t.TypeOf; +// Convenience types for type specific responses +type ResponseSchema = SharedResponseSchema & T; +export type EqlResponseSchema = ResponseSchema>; +export type ThreatMatchResponseSchema = ResponseSchema>; +export type QueryResponseSchema = ResponseSchema>; +export type SavedQueryResponseSchema = ResponseSchema>; +export type ThresholdResponseSchema = ResponseSchema>; +export type MachineLearningResponseSchema = ResponseSchema< + t.TypeOf +>; +export type NewTermsResponseSchema = ResponseSchema>; + export interface RulePreviewLogs { errors: string[]; warnings: string[]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts index 1b688ce641a7a..ff622a6581a73 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts @@ -11,5 +11,3 @@ export * from './import_rules_schema'; export * from './prepackaged_rules_schema'; export * from './prepackaged_rules_status_schema'; export * from './rules_bulk_schema'; -export * from './rules_schema'; -export * from './type_timeline_only_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index 00800b9474716..ea8ffed5bab26 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -10,12 +10,12 @@ import { pipe } from 'fp-ts/lib/pipeable'; import type { RulesBulkSchema } from './rules_bulk_schema'; import { rulesBulkSchema } from './rules_bulk_schema'; -import type { RulesSchema } from './rules_schema'; import type { ErrorSchema } from './error_schema'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getRulesSchemaMock } from './rules_schema.mocks'; import { getErrorSchemaMock } from './error_schema.mocks'; +import type { FullResponseSchema } from '../request'; describe('prepackaged_rule_schema', () => { test('it should validate a regular message and and error together with a uuid', () => { @@ -64,24 +64,8 @@ describe('prepackaged_rule_schema', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate an invalid error message with a deleted value', () => { - const error = getErrorSchemaMock('fake id'); - // @ts-expect-error - delete error.error; - const payload: RulesBulkSchema = [error]; - const decoded = rulesBulkSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "error"', - ]); - expect(message.schema).toEqual({}); - }); - test('it should NOT validate a type of "query" when it has extra data', () => { - const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; const payload: RulesBulkSchema = [rule]; const decoded = rulesBulkSchema.decode(payload); @@ -93,7 +77,7 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data next to a valid error', () => { - const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; const payload: RulesBulkSchema = [getErrorSchemaMock(), rule]; const decoded = rulesBulkSchema.decode(payload); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts index 57d812645ed38..65c55f356c44b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts @@ -7,8 +7,8 @@ import * as t from 'io-ts'; -import { rulesSchema } from './rules_schema'; +import { fullResponseSchema } from '../request'; import { errorSchema } from './error_schema'; -export const rulesBulkSchema = t.array(t.union([rulesSchema, errorSchema])); +export const rulesBulkSchema = t.array(t.union([fullResponseSchema, errorSchema])); export type RulesBulkSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index c3fbec8a6d7b3..a312f4f1b6e10 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -6,13 +6,12 @@ */ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants'; +import type { FullResponseSchema } from '../request'; import { getListArrayMock } from '../types/lists.mock'; -import type { RulesSchema } from './rules_schema'; - export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; -export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => ({ +const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE) => ({ author: [], id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), @@ -24,45 +23,52 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchem from: 'now-6m', immutable: false, name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', references: ['test 1', 'test 2'], - severity: 'high', + severity: 'high' as const, severity_mapping: [], updated_by: 'elastic_kibana', tags: ['some fake tag 1', 'some fake tag 2'], to: 'now', - type: 'query', threat: [], version: 1, output_index: '.siem-signals-default', max_signals: 100, risk_score: 55, risk_score_mapping: [], - language: 'kuery', rule_id: 'query-rule-id', interval: '5m', exceptions_list: getListArrayMock(), related_integrations: [], required_fields: [], setup: '', + throttle: 'no_actions', + actions: [], }); -export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { - const basePayload = getRulesSchemaMock(anchorDate); - const { filters, index, query, language, ...rest } = basePayload; +export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => ({ + ...getResponseBaseParams(anchorDate), + query: 'user.name: root or user.name: admin', + type: 'query', + language: 'kuery', +}); +export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => { return { - ...rest, + ...getResponseBaseParams(anchorDate), type: 'machine_learning', anomaly_threshold: 59, machine_learning_job_id: 'some_machine_learning_job_id', }; }; -export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { +export const getThreatMatchingSchemaMock = ( + anchorDate: string = ANCHOR_DATE +): FullResponseSchema => { return { - ...getRulesSchemaMock(anchorDate), + ...getResponseBaseParams(anchorDate), type: 'threat_match', + query: 'user.name: root or user.name: admin', + language: 'kuery', threat_index: ['index-123'], threat_mapping: [{ entries: [{ field: 'host.name', type: 'mapping', value: 'host.name' }] }], threat_query: '*:*', @@ -91,7 +97,9 @@ export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): R * Useful for e2e backend tests where it doesn't have date time and other * server side properties attached to it. */ -export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial => { +export const getThreatMatchingSchemaPartialMock = ( + enabled = false +): Partial => { return { author: [], created_by: 'elastic', @@ -160,9 +168,9 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial { +export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => { return { - ...getRulesSchemaMock(anchorDate), + ...getResponseBaseParams(anchorDate), language: 'eql', type: 'eql', query: 'process where true', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index bac55c8510929..b784df4384152 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -7,35 +7,22 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import type * as t from 'io-ts'; -import type { RulesSchema } from './rules_schema'; -import { - rulesSchema, - checkTypeDependents, - getDependents, - addSavedId, - addQueryFields, - addTimelineTitle, - addMlFields, - addThreatMatchFields, - addEqlFields, -} from './rules_schema'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; import { getRulesSchemaMock, getRulesMlSchemaMock, getThreatMatchingSchemaMock, getRulesEqlSchemaMock, } from './rules_schema.mocks'; -import type { ListArray } from '@kbn/securitysolution-io-ts-list-types'; +import { fullResponseSchema } from '../request'; +import type { FullResponseSchema } from '../request'; describe('rules_schema', () => { test('it should validate a type of "query" without anything extra', () => { const payload = getRulesSchemaMock(); - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -45,10 +32,10 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -57,41 +44,39 @@ describe('rules_schema', () => { }); test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); + const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toHaveLength(1); expect(message.schema).toEqual({}); }); - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); + test('it should validate a type of "query" with a saved_id together', () => { + const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); + const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); payload.type = 'saved_query'; payload.saved_id = 'save id 123'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); + const expected: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); expected.type = 'saved_query'; expected.saved_id = 'save id 123'; @@ -101,11 +86,11 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); + const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); payload.type = 'saved_query'; delete payload.saved_id; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -116,12 +101,13 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: FullResponseSchema & { saved_id?: string; invalid_extra_data?: string } = + getRulesSchemaMock(); payload.type = 'saved_query'; payload.saved_id = 'save id 123'; payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -134,7 +120,7 @@ describe('rules_schema', () => { payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -146,12 +132,12 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -159,575 +145,11 @@ describe('rules_schema', () => { expect(message.schema).toEqual({}); }); - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - describe('checkTypeDependents', () => { - test('it should validate a type of "query" without anything extra', () => { - const payload = getRulesSchemaMock(); - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); - payload.type = 'invalid_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - }); - - describe('getDependents', () => { - test('it should validate a type of "query" without anything extra', () => { - const payload = getRulesSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should validate a namespace as string', () => { - const payload = { - ...getRulesSchemaMock(), - namespace: 'a namespace', - }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getRulesSchemaMock(); - payload.type = 'invalid_data'; - - const dependents = getDependents(payload as unknown as TypeAndTimelineOnly); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "invalid_data" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getRulesSchemaMock(); - payload.type = 'query'; - payload.saved_id = 'save id 123'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "saved_id"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.type = 'saved_query'; - delete payload.saved_id; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "saved_id"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); - expected.timeline_id = 'some timeline id'; - expected.timeline_title = 'some timeline title'; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - payload.timeline_title = 'some timeline title'; - payload.invalid_extra_data = 'invalid_extra_data'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_id = 'some timeline id'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getRulesSchemaMock(); - payload.timeline_title = 'some timeline title'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_title" but there is NOT a "timeline_id"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_title = 'some timeline title'; - - const decoded = checkTypeDependents(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "timeline_title"']); - expect(message.schema).toEqual({}); - }); - - test('it should NOT validate a type of "saved_query" with a "saved_id" dependent and a "timeline_id" but there is NOT a "timeline_title"', () => { - const payload = getRulesSchemaMock(); - payload.saved_id = 'some saved id'; - payload.type = 'saved_query'; - payload.timeline_id = 'some timeline id'; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "timeline_title"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it validates an ML rule response', () => { - const payload = getRulesMlSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesMlSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it rejects a response with both ML and query properties', () => { - const payload = { - ...getRulesSchemaMock(), - ...getRulesMlSchemaMock(), - }; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "query,language"']); - expect(message.schema).toEqual({}); - }); - - test('it validates a threat_match response', () => { - const payload = getThreatMatchingSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getThreatMatchingSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it rejects a response with threat_match properties but type of "query"', () => { - const payload: RulesSchema = { - ...getThreatMatchingSchemaMock(), - type: 'query', - }; - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'invalid keys "threat_index,["index-123"],threat_mapping,[{"entries":[{"field":"host.name","type":"mapping","value":"host.name"}]}],threat_query,threat_filters,[{"bool":{"must":[{"query_string":{"query":"host.name: linux","analyze_wildcard":true,"time_zone":"Zulu"}}],"filter":[],"should":[],"must_not":[]}}]"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it validates an eql rule response', () => { - const payload = getRulesEqlSchemaMock(); - - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - const expected = getRulesEqlSchemaMock(); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - }); - - describe('addSavedId', () => { - test('should return empty array if not given a type of "saved_query"', () => { - const emptyArray = addSavedId({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(emptyArray).toEqual(expected); - }); - - test('should array of size 2 given a "saved_query"', () => { - const array = addSavedId({ type: 'saved_query' }); - expect(array.length).toEqual(2); - }); - }); - - describe('addTimelineTitle', () => { - test('should return empty array if not given a timeline_id', () => { - const emptyArray = addTimelineTitle({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(emptyArray).toEqual(expected); - }); - - test('should array of size 2 given a "timeline_id" that is not null', () => { - const array = addTimelineTitle({ type: 'query', timeline_id: 'some id' }); - expect(array.length).toEqual(2); - }); - }); - - describe('addQueryFields', () => { - test('should return empty array if type is not "query"', () => { - const fields = addQueryFields({ type: 'machine_learning' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return two fields for a rule of type "query"', () => { - const fields = addQueryFields({ type: 'query' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "threshold"', () => { - const fields = addQueryFields({ type: 'threshold' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "saved_query"', () => { - const fields = addQueryFields({ type: 'saved_query' }); - expect(fields.length).toEqual(3); - }); - - test('should return two fields for a rule of type "threat_match"', () => { - const fields = addQueryFields({ type: 'threat_match' }); - expect(fields.length).toEqual(3); - }); - }); - - describe('addMlFields', () => { - test('should return empty array if type is not "machine_learning"', () => { - const fields = addMlFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return two fields for a rule of type "machine_learning"', () => { - const fields = addMlFields({ type: 'machine_learning' }); - expect(fields.length).toEqual(2); - }); - }); - describe('exceptions_list', () => { test('it should validate an empty array for "exceptions_list"', () => { const payload = getRulesSchemaMock(); payload.exceptions_list = []; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -737,11 +159,11 @@ describe('rules_schema', () => { }); test('it should NOT validate when "exceptions_list" is not expected type', () => { - const payload: Omit & { + const payload: Omit & { exceptions_list?: string; } = { ...getRulesSchemaMock(), exceptions_list: 'invalid_data' }; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -750,53 +172,13 @@ describe('rules_schema', () => { ]); expect(message.schema).toEqual({}); }); - - test('it should default to empty array if "exceptions_list" is undefined ', () => { - const payload: Omit & { - exceptions_list?: ListArray; - } = getRulesSchemaMock(); - payload.exceptions_list = undefined; - - const decoded = rulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ ...payload, exceptions_list: [] }); - }); - }); - - describe('addThreatMatchFields', () => { - test('should return empty array if type is not "threat_match"', () => { - const fields = addThreatMatchFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return nine (9) fields for a rule of type "threat_match"', () => { - const fields = addThreatMatchFields({ type: 'threat_match' }); - expect(fields.length).toEqual(10); - }); - }); - - describe('addEqlFields', () => { - test('should return empty array if type is not "eql"', () => { - const fields = addEqlFields({ type: 'query' }); - const expected: t.Mixed[] = []; - expect(fields).toEqual(expected); - }); - - test('should return 3 fields for a rule of type "eql"', () => { - const fields = addEqlFields({ type: 'eql' }); - expect(fields.length).toEqual(6); - }); }); describe('data_view_id', () => { test('it should validate a type of "query" with "data_view_id" defined', () => { const payload = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -806,15 +188,17 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with "data_view_id" defined', () => { - const payload = getRulesSchemaMock(); + const payload: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + getRulesSchemaMock(); payload.type = 'saved_query'; payload.saved_id = 'save id 123'; payload.data_view_id = 'logs-*'; - const decoded = rulesSchema.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getRulesSchemaMock(); + const expected: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + getRulesSchemaMock(); expected.type = 'saved_query'; expected.saved_id = 'save id 123'; @@ -827,8 +211,7 @@ describe('rules_schema', () => { test('it should validate a type of "eql" with "data_view_id" defined', () => { const payload = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; @@ -840,8 +223,7 @@ describe('rules_schema', () => { test('it should validate a type of "threat_match" with "data_view_id" defined', () => { const payload = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; @@ -853,8 +235,7 @@ describe('rules_schema', () => { test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { const payload = { ...getRulesMlSchemaMock(), data_view_id: 'logs-*' }; - const dependents = getDependents(payload); - const decoded = dependents.decode(payload); + const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts deleted file mode 100644 index 794ef71bf0536..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts +++ /dev/null @@ -1,366 +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 * as t from 'io-ts'; -import { isObject } from 'lodash/fp'; -import type { Either } from 'fp-ts/lib/Either'; -import { left, fold } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import { - actions, - from, - machine_learning_job_id, - risk_score, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - threats, - type, - language, - severity, - throttle, - max_signals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { DefaultStringArray, version } from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray } from '@kbn/securitysolution-io-ts-list-types'; - -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; -import { RuleExecutionSummary } from '../../rule_monitoring'; -import { - anomaly_threshold, - data_view_id, - description, - enabled, - timestamp_field, - event_category_override, - tiebreaker_field, - false_positives, - id, - immutable, - index, - interval, - rule_id, - name, - output_index, - query, - references, - updated_by, - tags, - to, - created_at, - created_by, - updated_at, - saved_id, - timeline_id, - timeline_title, - threshold, - filters, - meta, - outcome, - alias_target_id, - alias_purpose, - note, - building_block_type, - license, - rule_name_override, - timestamp_override, - namespace, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../common'; - -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { typeAndTimelineOnlySchema } from './type_timeline_only_schema'; - -/** - * This is the required fields for the rules schema response. Put all required properties on - * this base for schemas such as create_rules, update_rules, for the correct validation of the - * output schema. - */ -export const requiredRulesSchema = t.type({ - author: DefaultStringArray, - description, - enabled, - false_positives, - from, - id, - immutable, - interval, - rule_id, - output_index, - max_signals, - risk_score, - risk_score_mapping: DefaultRiskScoreMappingArray, - name, - references, - severity, - severity_mapping: DefaultSeverityMappingArray, - updated_by, - tags, - to, - type, - threat: threats, - created_at, - updated_at, - created_by, - version, - exceptions_list: DefaultListArray, - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, -}); - -export type RequiredRulesSchema = t.TypeOf; - -/** - * If you have type dependents or exclusive or situations add them here AND update the - * check_type_dependents file for whichever REST flow it is going through. - */ -export const dependentRulesSchema = t.partial({ - // All but ML - data_view_id, - - // query fields - language, - query, - - // eql only fields - timestamp_field, - event_category_override, - tiebreaker_field, - - // when type = saved_query, saved_id is required - saved_id, - - // These two are required together or not at all. - timeline_id, - timeline_title, - - // ML fields - anomaly_threshold, - machine_learning_job_id, - - // Threshold fields - threshold, - - // Threat Match fields - threat_filters, - threat_index, - threat_query, - concurrent_searches, - items_per_search, - threat_mapping, - threat_language, - threat_indicator_path, -}); - -/** - * This is the partial or optional fields for the rules schema. Put all optional - * properties on this. DO NOT PUT type dependents such as xor relationships here. - * Instead use dependentRulesSchema and check_type_dependents for how to do those. - */ -export const partialRulesSchema = t.partial({ - actions, - building_block_type, - license, - throttle, - rule_name_override, - timestamp_override, - filters, - meta, - outcome, - alias_target_id, - alias_purpose, - index, - namespace, - note, - uuid: id, // Move to 'required' post-migration - execution_summary: RuleExecutionSummary, -}); - -/** - * This is the rules schema WITHOUT typeDependents. You don't normally want to use this for a decode - */ -export const rulesWithoutTypeDependentsSchema = t.intersection([ - t.exact(dependentRulesSchema), - t.exact(partialRulesSchema), - t.exact(requiredRulesSchema), -]); -export type RulesWithoutTypeDependentsSchema = t.TypeOf; - -/** - * This is the rulesSchema you want to use for checking type dependents and all the properties - * through: rulesSchema.decode(someJSONObject) - */ -export const rulesSchema = new t.Type< - RulesWithoutTypeDependentsSchema, - RulesWithoutTypeDependentsSchema, - unknown ->( - 'RulesSchema', - (input: unknown): input is RulesWithoutTypeDependentsSchema => isObject(input), - (input): Either => { - return checkTypeDependents(input); - }, - t.identity -); - -/** - * This is the correct type you want to use for Rules that are outputted from the - * REST interface. This has all base and all optional properties merged together. - */ -export type RulesSchema = t.TypeOf; - -export const addSavedId = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'saved_query') { - return [ - t.exact(t.type({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addTimelineTitle = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.timeline_id != null) { - return [ - t.exact(t.type({ timeline_title: dependentRulesSchema.props.timeline_title })), - t.exact(t.type({ timeline_id: dependentRulesSchema.props.timeline_id })), - ]; - } else { - return []; - } -}; - -export const addQueryFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (['query', 'saved_query', 'threshold', 'threat_match'].includes(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addMlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isMlRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ anomaly_threshold: dependentRulesSchema.props.anomaly_threshold })), - t.exact( - t.type({ machine_learning_job_id: dependentRulesSchema.props.machine_learning_job_id }) - ), - ]; - } else { - return []; - } -}; - -export const addThresholdFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (isThresholdRule(typeAndTimelineOnly.type)) { - return [ - t.exact(t.type({ threshold: dependentRulesSchema.props.threshold })), - t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addEqlFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'eql') { - return [ - t.exact(t.partial({ timestamp_field: dependentRulesSchema.props.timestamp_field })), - t.exact( - t.partial({ event_category_override: dependentRulesSchema.props.event_category_override }) - ), - t.exact(t.partial({ tiebreaker_field: dependentRulesSchema.props.tiebreaker_field })), - t.exact(t.type({ query: dependentRulesSchema.props.query })), - t.exact(t.type({ language: dependentRulesSchema.props.language })), - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - ]; - } else { - return []; - } -}; - -export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed[] => { - if (typeAndTimelineOnly.type === 'threat_match') { - return [ - t.exact(t.partial({ data_view_id: dependentRulesSchema.props.data_view_id })), - t.exact(t.type({ threat_query: dependentRulesSchema.props.threat_query })), - t.exact(t.type({ threat_index: dependentRulesSchema.props.threat_index })), - t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })), - t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })), - t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })), - t.exact( - t.partial({ threat_indicator_path: dependentRulesSchema.props.threat_indicator_path }) - ), - t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })), - t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })), - t.exact( - t.partial({ - items_per_search: dependentRulesSchema.props.items_per_search, - }) - ), - ]; - } else { - return []; - } -}; - -export const getDependents = (typeAndTimelineOnly: TypeAndTimelineOnly): t.Mixed => { - const dependents: t.Mixed[] = [ - t.exact(requiredRulesSchema), - t.exact(partialRulesSchema), - ...addSavedId(typeAndTimelineOnly), - ...addTimelineTitle(typeAndTimelineOnly), - ...addQueryFields(typeAndTimelineOnly), - ...addMlFields(typeAndTimelineOnly), - ...addThresholdFields(typeAndTimelineOnly), - ...addEqlFields(typeAndTimelineOnly), - ...addThreatMatchFields(typeAndTimelineOnly), - ]; - - if (dependents.length > 1) { - // This unsafe cast is because t.intersection does not use an array but rather a set of - // tuples and really does not look like they expected us to ever dynamically build up - // intersections, but here we are doing that. Looking at their code, although they limit - // the array elements to 5, it looks like you have N number of intersections - const unsafeCast: [t.Mixed, t.Mixed] = dependents as [t.Mixed, t.Mixed]; - return t.intersection(unsafeCast); - } else { - // We are not allowed to call t.intersection with a single value so we return without - // it here normally. - return dependents[0]; - } -}; - -export const checkTypeDependents = (input: unknown): Either => { - const typeOnlyDecoded = typeAndTimelineOnlySchema.decode(input); - const onLeft = (errors: t.Errors): Either => left(errors); - const onRight = ( - typeAndTimelineOnly: TypeAndTimelineOnly - ): Either => { - const intersections = getDependents(typeAndTimelineOnly); - return intersections.decode(input); - }; - return pipe(typeOnlyDecoded, fold(onLeft, onRight)); -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts deleted file mode 100644 index 8026d99713214..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.test.ts +++ /dev/null @@ -1,66 +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 { left } from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; - -import type { TypeAndTimelineOnly } from './type_timeline_only_schema'; -import { typeAndTimelineOnlySchema } from './type_timeline_only_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; - -describe('prepackaged_rule_schema', () => { - test('it should validate a a type and timeline_id together', () => { - const payload: TypeAndTimelineOnly = { - type: 'query', - timeline_id: 'some id', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate just a type without a timeline_id of type query', () => { - const payload: TypeAndTimelineOnly = { - type: 'query', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should validate just a type of saved_query', () => { - const payload: TypeAndTimelineOnly = { - type: 'saved_query', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('it should NOT validate an invalid type', () => { - const payload: Omit & { type: string } = { - type: 'some other type', - }; - const decoded = typeAndTimelineOnlySchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "some other type" supplied to "type"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts deleted file mode 100644 index b164ab9b44e4f..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/type_timeline_only_schema.ts +++ /dev/null @@ -1,21 +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 * as t from 'io-ts'; - -import { type } from '@kbn/securitysolution-io-ts-alerting-types'; -import { timeline_id } from '../common/schemas'; - -/** - * Special schema type that is only the type and the timeline_id. - * This is used for dependent type checking only. - */ -export const typeAndTimelineOnlySchema = t.intersection([ - t.exact(t.type({ type })), - t.exact(t.partial({ timeline_id })), -]); -export type TypeAndTimelineOnly = t.TypeOf; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts index 69aa2a4502bc0..e445e5b935af2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts @@ -5,10 +5,8 @@ * 2.0. */ -import type { - GetInstalledIntegrationsResponse, - RulesSchema, -} from '../../../../../../common/detection_engine/schemas/response'; +import type { FullResponseSchema } from '../../../../../../common/detection_engine/schemas/request'; +import type { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/schemas/response'; import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; import { savedRuleMock, rulesMock } from '../mock'; @@ -25,14 +23,16 @@ import type { FetchRulesProps, } from '../types'; -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => Promise.resolve(getRulesSchemaMock()); -export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => +export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => Promise.resolve(getRulesSchemaMock()); -export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise => - Promise.resolve(getRulesSchemaMock()); +export const patchRule = async ({ + ruleProperties, + signal, +}: PatchRuleProps): Promise => Promise.resolve(getRulesSchemaMock()); export const getPrePackagedRulesStatus = async ({ signal, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts index dfee0f418d2d5..d67fddfa3c908 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts @@ -22,10 +22,7 @@ import type { FullResponseSchema, PreviewResponse, } from '../../../../../common/detection_engine/schemas/request'; -import type { - RulesSchema, - GetInstalledIntegrationsResponse, -} from '../../../../../common/detection_engine/schemas/response'; +import type { GetInstalledIntegrationsResponse } from '../../../../../common/detection_engine/schemas/response'; import type { UpdateRulesProps, @@ -70,8 +67,8 @@ export const createRule = async ({ rule, signal }: CreateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PUT', body: JSON.stringify(rule), signal, @@ -88,8 +85,11 @@ export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const patchRule = async ({ + ruleProperties, + signal, +}: PatchRuleProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PATCH', body: JSON.stringify(ruleProperties), signal, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index ccd0eb5c80fe6..488ad755afe41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -8,9 +8,9 @@ import { Readable } from 'stream'; import type { HapiReadableStream } from '../../rules/types'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; /** * Given a string, builds a hapi stream as our @@ -34,10 +34,7 @@ export const buildHapiStream = (string: string, filename = 'file.ndjson'): HapiR return stream; }; -export const getOutputRuleAlertForRest = (): Omit< - RulesSchema, - 'machine_learning_job_id' | 'anomaly_threshold' -> => ({ +export const getOutputRuleAlertForRest = (): FullResponseSchema => ({ author: ['Elastic'], actions: [], building_block_type: 'default', 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 f6f2ff03efa25..9538933055d70 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 @@ -18,7 +18,7 @@ import { patchRules } from '../../rules/patch_rules'; import { buildSiemResponse } from '../utils'; import { getIdError } from './utils'; -import { transformValidate } from './validate'; +import { newTransformValidate } from './validate'; import { readRules } from '../../rules/read_rules'; import { legacyMigrate } from '../../rules/utils'; @@ -83,7 +83,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP if (rule != null && rule.enabled != null && rule.name != null) { const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); - const [validated, errors] = transformValidate(rule, ruleExecutionSummary); + const [validated, errors] = newTransformValidate(rule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index 81d77e1e2eb71..e78027d542e56 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -16,7 +16,7 @@ import { throwAuthzError } from '../../../machine_learning/validation'; import { buildSiemResponse } from '../utils'; import { getIdError } from './utils'; -import { transformValidate } from './validate'; +import { newTransformValidate } from './validate'; import { updateRules } from '../../rules/update_rules'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { legacyMigrate } from '../../rules/utils'; @@ -73,7 +73,7 @@ export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: Setup if (rule != null) { const ruleExecutionLog = ctx.securitySolution.getRuleExecutionLog(); const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); - const [validated, errors] = transformValidate(rule, ruleExecutionSummary); + const [validated, errors] = newTransformValidate(rule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { 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 2df4cb712ddd2..9dfd5b1efed7c 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 @@ -14,7 +14,6 @@ import pMap from 'p-map'; import type { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import type { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import type { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import type { RuleAlertType } from '../../rules/types'; @@ -26,6 +25,7 @@ import type { RuleParams } from '../../schemas/rule_schemas'; // eslint-disable-next-line no-restricted-imports import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import type { RuleExecutionSummariesByRuleId } from '../../rule_monitoring'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; type PromiseFromStreams = ImportRulesSchema | Error; const MAX_CONCURRENT_SEARCHES = 10; @@ -92,7 +92,7 @@ export const getIdBulkError = ({ export const transformAlertsToRules = ( rules: RuleAlertType[], legacyRuleActions: Record -): Array> => { +): FullResponseSchema[] => { return rules.map((rule) => internalRuleToAPIResponse(rule, null, legacyRuleActions[rule.id])); }; @@ -104,7 +104,7 @@ export const transformFindAlerts = ( page: number; perPage: number; total: number; - data: Array>; + data: Array>; } | null => { return { page: ruleFindResults.page, @@ -121,7 +121,7 @@ export const transform = ( rule: PartialRule, ruleExecutionSummary?: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null -): Partial | null => { +): FullResponseSchema | null => { if (isAlertType(rule)) { return internalRuleToAPIResponse(rule, ruleExecutionSummary, legacyRuleActions); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 21db7e52e4f8d..f095f861a7aa1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { transformValidate, transformValidateBulkError } from './validate'; +import { newTransformValidate, transformValidateBulkError } from './validate'; import type { BulkError } from '../utils'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response'; import { getRuleMock } from '../__mocks__/request_responses'; import { ruleExecutionSummaryMock } from '../../../../../common/detection_engine/rule_monitoring/mocks'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -export const ruleOutput = (): RulesSchema => ({ +export const ruleOutput = (): FullResponseSchema => ({ actions: [], author: ['Elastic'], building_block_type: 'default', @@ -73,7 +73,7 @@ describe('validate', () => { describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); - const [validated, errors] = transformValidate(ruleAlert, null); + const [validated, errors] = newTransformValidate(ruleAlert, null); expect(validated).toEqual(ruleOutput()); expect(errors).toEqual(null); }); @@ -82,7 +82,7 @@ describe('validate', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); // @ts-expect-error delete ruleAlert.name; - const [validated, errors] = transformValidate(ruleAlert, null); + const [validated, errors] = newTransformValidate(ruleAlert, null); expect(validated).toEqual(null); expect(errors).toEqual('Invalid value "undefined" supplied to "name"'); }); @@ -114,7 +114,7 @@ describe('validate', () => { const rule = getRuleMock(getQueryRuleParams()); const ruleExecutionSumary = ruleExecutionSummaryMock.getSummarySucceeded(); const validatedOrError = transformValidateBulkError('rule-1', rule, ruleExecutionSumary); - const expected: RulesSchema = { + const expected: FullResponseSchema = { ...ruleOutput(), execution_summary: ruleExecutionSumary, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index 4183f217a61fe..bc01db7d4fe07 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -11,8 +11,6 @@ import type { PartialRule } from '@kbn/alerting-plugin/server'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; import { fullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -import type { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; -import { rulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { isAlertType } from '../../rules/types'; import type { BulkError } from '../utils'; import { createBulkErrorObject } from '../utils'; @@ -22,19 +20,6 @@ import type { RuleParams } from '../../schemas/rule_schemas'; import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; -export const transformValidate = ( - rule: PartialRule, - ruleExecutionSummary: RuleExecutionSummary | null, - legacyRuleActions?: LegacyRulesActionsSavedObject | null -): [RulesSchema | null, string | null] => { - const transformed = transform(rule, ruleExecutionSummary, legacyRuleActions); - if (transformed == null) { - return [null, 'Internal error transforming']; - } else { - return validateNonExact(transformed, rulesSchema); - } -}; - export const newTransformValidate = ( rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, @@ -52,10 +37,10 @@ export const transformValidateBulkError = ( ruleId: string, rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null -): RulesSchema | BulkError => { +): FullResponseSchema | BulkError => { if (isAlertType(rule)) { const transformed = internalRuleToAPIResponse(rule, ruleExecutionSummary); - const [validated, errors] = validateNonExact(transformed, rulesSchema); + const [validated, errors] = validateNonExact(transformed, fullResponseSchema); if (errors != null || validated == null) { return createBulkErrorObject({ ruleId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts index d5008c87f3b6d..e044c8fdfd1ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -11,7 +11,6 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import type { Logger } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; @@ -22,10 +21,11 @@ import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; // eslint-disable-next-line no-restricted-imports import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_bulk_rule_actions_saved_object'; import { internalRuleToAPIResponse } from '../schemas/rule_converters'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; interface ExportSuccessRule { statusCode: 200; - rule: Partial; + rule: FullResponseSchema; } interface ExportFailedRule { @@ -36,7 +36,7 @@ interface ExportFailedRule { export interface RulesErrors { exportedCount: number; missingRules: Array<{ rule_id: string }>; - rules: Array>; + rules: FullResponseSchema[]; } export const getExportByObjectIds = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts index 30be45f5eb163..204d78f5fe7d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts @@ -6,12 +6,12 @@ */ import type { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; import type { ExportRulesDetails } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; export const getExportDetailsNdjson = ( - rules: Array>, + rules: FullResponseSchema[], missingRules: Array<{ rule_id: string }> = [], exceptionDetails?: ExportExceptionDetails ): string => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index ebdf002986ffb..cd85789d41568 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -497,6 +497,8 @@ export const sampleSignalHit = (): SignalHit => ({ related_integrations: [], required_fields: [], setup: '', + throttle: 'no_actions', + actions: [], }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index db88284bc8881..5609eed4c0801 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -19,7 +19,6 @@ import type { ListClient } from '@kbn/lists-plugin/server'; import type { EcsFieldMap } from '@kbn/rule-registry-plugin/common/assets/field_maps/ecs_field_map'; import type { TypeOfFieldMap } from '@kbn/rule-registry-plugin/common/field_map'; import type { Status } from '../../../../common/detection_engine/schemas/common/schemas'; -import type { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import type { BaseHit, RuleAlertAction, @@ -42,6 +41,7 @@ import type { WrappedFieldsLatest, } from '../../../../common/detection_engine/schemas/alerts'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; +import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; export interface ThresholdResult { terms?: Array<{ @@ -192,7 +192,7 @@ export interface Signal { _meta?: { version: number; }; - rule: RulesSchema; + rule: FullResponseSchema; /** * @deprecated Use "parents" instead of "parent" */ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/perform_bulk_action.ts index 079e378533d5a..827e8ae723632 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/perform_bulk_action.ts @@ -15,7 +15,7 @@ import { BulkAction, BulkActionEditType, } from '@kbn/security-solution-plugin/common/detection_engine/schemas/common/schemas'; -import { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, @@ -346,7 +346,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); - rulesResponse.data.forEach((rule: RulesSchema) => { + rulesResponse.data.forEach((rule: FullResponseSchema) => { expect(rule.actions).to.eql([ { action_type_id: '.slack', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules_bulk.ts index 19447dec2b4a8..dc7209b9f1c98 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules_bulk.ts @@ -19,8 +19,6 @@ import { deleteSignalsIndex, getSimpleRuleOutput, removeServerGeneratedProperties, - getSimpleRuleOutputWithoutRuleId, - removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleUpdate, createRule, getSimpleRule, @@ -282,16 +280,16 @@ export default ({ getService }: FtrProviderContext) => { .send([updatedRule1, updatedRule2]) .expect(200); - const outputRule1 = getSimpleRuleOutputWithoutRuleId('rule-1'); + const outputRule1 = getSimpleRuleOutput('rule-1'); outputRule1.name = 'some other name'; outputRule1.version = 2; - const outputRule2 = getSimpleRuleOutputWithoutRuleId('rule-2'); + const outputRule2 = getSimpleRuleOutput('rule-2'); outputRule2.name = 'some other name'; outputRule2.version = 2; - const bodyToCompare1 = removeServerGeneratedPropertiesIncludingRuleId(body[0]); - const bodyToCompare2 = removeServerGeneratedPropertiesIncludingRuleId(body[1]); + const bodyToCompare1 = removeServerGeneratedProperties(body[0]); + const bodyToCompare2 = removeServerGeneratedProperties(body[1]); expect(bodyToCompare1).to.eql(outputRule1); expect(bodyToCompare2).to.eql(outputRule2); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts index f44e72f5cd50a..647c4dddb2bb1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts @@ -10,7 +10,7 @@ import expect from '@kbn/expect'; import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import { +import type { CreateRulesSchema, EqlCreateSchema, QueryCreateSchema, @@ -18,7 +18,6 @@ import { ThresholdCreateSchema, } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; -import { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -106,7 +105,7 @@ export default ({ getService }: FtrProviderContext) => { }; const rule = await createRule(supertest, log, ruleWithException); - const expected: Partial = { + const expected = { ...getSimpleRuleOutput(), exceptions_list: [ { @@ -147,7 +146,7 @@ export default ({ getService }: FtrProviderContext) => { await waitForRuleSuccessOrStatus(supertest, log, rule.id); const bodyToCompare = removeServerGeneratedProperties(rule); - const expected: Partial = { + const expected = { ...getSimpleRuleOutput(), enabled: true, exceptions_list: [ diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts index 1db5c784660fc..4f5cfdcd3ba56 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ +export const getComplexRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ actions: [], author: [], name: 'Complex Rule Query', @@ -92,4 +92,6 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial => ({ note: '# some investigation documentation', version: 1, query: 'user.name: root or user.name: admin', + throttle: 'no_actions', + exceptions_list: [], }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index cc33c2ebff447..0b39b751c8656 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -5,13 +5,11 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; - /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ +export const getComplexRuleOutput = (ruleId = 'rule-1') => ({ actions: [], author: [], created_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts index da28e867bc976..b1036e1f8b682 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -21,7 +21,7 @@ export const getRule = async ( supertest: SuperTest.SuperTest, log: ToolingLog, ruleId: string -): Promise => { +): Promise => { const response = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) .set('kbn-xsrf', 'true'); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts index c845c0d343261..51cd1f1f3576f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts @@ -5,15 +5,12 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; -import { getSimpleRuleOutput } from './get_simple_rule_output'; - -export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial => { - const rule = getSimpleRuleOutput(ruleId); - const { query, language, index, ...rest } = rule; +import type { MachineLearningResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { getBaseSimpleRuleOutput } from './get_simple_rule_output'; +export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { return { - ...rest, + ...getBaseSimpleRuleOutput(ruleId), name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', anomaly_threshold: 44, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 0d6cf9905d4a2..9c9e34214ed22 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -5,13 +5,14 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { removeServerGeneratedProperties } from './remove_server_generated_properties'; /** * This is the typical output of a simple rule that Kibana will output with all the defaults * except for the server generated properties. Useful for testing end to end tests. */ -export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial => ({ +export const getBaseSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ actions: [], author: [], created_by: 'elastic', @@ -20,10 +21,8 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial false_positives: [], from: 'now-6m', immutable: false, - index: ['auditbeat-*'], interval: '5m', rule_id: ruleId, - language: 'kuery', output_index: '', max_signals: 100, related_integrations: [], @@ -31,17 +30,30 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial risk_score: 1, risk_score_mapping: [], name: 'Simple Rule Query', - query: 'user.name: root or user.name: admin', references: [], setup: '', - severity: 'high', + severity: 'high' as const, severity_mapping: [], updated_by: 'elastic', tags: [], to: 'now', - type: 'query', threat: [], throttle: 'no_actions', exceptions_list: [], version: 1, + id: 'id', + updated_at: '2020-07-08T16:36:32.377Z', + created_at: '2020-07-08T16:36:32.377Z', }); + +const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSchema => ({ + ...getBaseSimpleRuleOutput(ruleId, enabled), + index: ['auditbeat-*'], + language: 'kuery', + query: 'user.name: root or user.name: admin', + type: 'query', +}); + +export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => { + return removeServerGeneratedProperties(getQueryRuleOutput(ruleId, enabled)); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts index 45dd0bfd5d477..f62767fdf0ad1 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts @@ -5,10 +5,9 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; import { getSimpleRuleOutput } from './get_simple_rule_output'; -export const getSimpleRuleOutputWithWebHookAction = (actionId: string): Partial => ({ +export const getSimpleRuleOutputWithWebHookAction = (actionId: string) => ({ ...getSimpleRuleOutput(), throttle: 'rule', actions: [ diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts index dbf94965278d6..418e91240c6bc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts @@ -5,14 +5,13 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; import { getSimpleRuleOutput } from './get_simple_rule_output'; /** * This is the typical output of a simple rule that Kibana will output with all the defaults except * for all the server generated properties such as created_by. Useful for testing end to end tests. */ -export const getSimpleRuleOutputWithoutRuleId = (ruleId = 'rule-1'): Partial => { +export const getSimpleRuleOutputWithoutRuleId = (ruleId = 'rule-1') => { const rule = getSimpleRuleOutput(ruleId); const { rule_id: rId, ...ruleWithoutRuleId } = rule; return ruleWithoutRuleId; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts index 5f863c0e62b9b..57a2745a12d02 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts @@ -13,7 +13,7 @@ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/de */ export const removeServerGeneratedProperties = ( rule: FullResponseSchema -): Partial => { +): Omit => { const { /* eslint-disable @typescript-eslint/naming-convention */ id, diff --git a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts index 468cbdfa23aa5..13fc65823007d 100644 --- a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts @@ -5,11 +5,9 @@ * 2.0. */ -import type { RulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema'; - import { getSimpleRuleOutput } from './get_simple_rule_output'; -export const resolveSimpleRuleOutput = ( - ruleId = 'rule-1', - enabled = false -): Partial => ({ outcome: 'exactMatch', ...getSimpleRuleOutput(ruleId, enabled) }); +export const resolveSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ + outcome: 'exactMatch', + ...getSimpleRuleOutput(ruleId, enabled), +}); From 38317c1f3d630ef53914b4614a13866585f0c8cc Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 1 Aug 2022 06:59:46 -0700 Subject: [PATCH 02/11] Remove RulesSchema from cypress test --- .../security_solution/cypress/objects/rule.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 7fd9e7b5f7625..25f6ae28d967e 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -5,12 +5,12 @@ * 2.0. */ -import type { RulesSchema } from '../../common/detection_engine/schemas/response'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { rawRules } from '../../server/lib/detection_engine/rules/prepackaged_rules'; import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques'; import type { CompleteTimeline } from './timeline'; import { getTimeline, getIndicatorMatchTimelineTemplate } from './timeline'; +import type { FullResponseSchema } from '../../common/detection_engine/schemas/request'; export const totalNumberOfPrebuiltRules = rawRules.length; @@ -484,7 +484,9 @@ export const getEditedRule = (): CustomRule => ({ tags: [...getExistingRule().tags, 'edited'], }); -export const expectedExportedRule = (ruleResponse: Cypress.Response): string => { +export const expectedExportedRule = ( + ruleResponse: Cypress.Response +): string => { const { id, updated_at: updatedAt, @@ -494,11 +496,14 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response name, risk_score: riskScore, severity, - query, } = ruleResponse.body; + let query: string | undefined; + if (ruleResponse.body.type === 'query') { + query = ruleResponse.body.query; + } // NOTE: Order of the properties in this object matters for the tests to work. - const rule: RulesSchema = { + const rule = { id, updated_at: updatedAt, updated_by: updatedBy, From 4c01f5b4ba221683f4583164a16f795b70bbfecd Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 1 Aug 2022 07:41:36 -0700 Subject: [PATCH 03/11] Fix ml rule API test mock --- .../utils/get_simple_ml_rule_output.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts index 51cd1f1f3576f..edff2ec3441f8 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts @@ -7,8 +7,9 @@ import type { MachineLearningResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { getBaseSimpleRuleOutput } from './get_simple_rule_output'; +import { removeServerGeneratedProperties } from './remove_server_generated_properties'; -export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { +const getBaseMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { return { ...getBaseSimpleRuleOutput(ruleId), name: 'Simple ML Rule', @@ -18,3 +19,7 @@ export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): MachineLearningRespons type: 'machine_learning', }; }; + +export const getSimpleMlRuleOutput = (ruleId = 'rule-1') => { + return removeServerGeneratedProperties(getBaseMlRuleOutput(ruleId)); +}; From 7a79e6e840c189f187d88dbf54840e2c530dced9 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 1 Aug 2022 08:39:14 -0700 Subject: [PATCH 04/11] Fix type error --- .../security_and_spaces/group1/update_rules.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules.ts index 583746db3936b..f57430018bf9d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_rules.ts @@ -81,6 +81,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); const outputRule = getSimpleMlRuleOutput(); + // @ts-expect-error type narrowing is lost due to Omit<> outputRule.machine_learning_job_id = ['legacy_job_id']; outputRule.version = 2; const bodyToCompare = removeServerGeneratedProperties(body); From 5a0a1414b2beab844e9c0ef46a8064ad2f02f65e Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 22 Aug 2022 11:01:04 -0700 Subject: [PATCH 05/11] Explicitly specify undefined fields in response mocks --- .../schemas/response/rules_schema.mocks.ts | 31 +++++++++++++++++++ .../routes/__mocks__/utils.ts | 7 +++++ .../routes/rules/validate.test.ts | 9 ++++++ .../rules/get_export_by_object_ids.test.ts | 7 +++++ .../signals/__mocks__/es_results.ts | 17 ++++++++++ 5 files changed, 71 insertions(+) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index a312f4f1b6e10..2f7587cffc88f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -43,6 +43,19 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE) => ({ setup: '', throttle: 'no_actions', actions: [], + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, }); export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => ({ @@ -50,6 +63,10 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): FullRespon query: 'user.name: root or user.name: admin', type: 'query', language: 'kuery', + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, }); export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => { @@ -90,6 +107,14 @@ export const getThreatMatchingSchemaMock = ( }, }, ], + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, + threat_indicator_path: undefined, + threat_language: undefined, + concurrent_searches: undefined, + items_per_search: undefined, }; }; @@ -174,5 +199,11 @@ export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullRes language: 'eql', type: 'eql', query: 'process where true', + index: undefined, + data_view_id: undefined, + filters: undefined, + timestamp_field: undefined, + event_category_override: undefined, + tiebreaker_field: undefined, }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index 488ad755afe41..f949e927ec095 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -90,4 +90,11 @@ export const getOutputRuleAlertForRest = (): FullResponseSchema => ({ related_integrations: [], required_fields: [], setup: '', + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index f095f861a7aa1..72d8aaeff2c82 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -67,6 +67,15 @@ export const ruleOutput = (): FullResponseSchema => ({ related_integrations: [], required_fields: [], setup: '', + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, + saved_id: undefined, }); describe('validate', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 31bdfb398c18a..4fea86a2e3395 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -213,6 +213,13 @@ describe('get_export_by_object_ids', () => { version: 1, exceptions_list: getListArrayMock(), execution_summary: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + data_view_id: undefined, }, ], }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts index dba3a190ab842..d3cdcd82b4989 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -499,6 +499,23 @@ export const sampleSignalHit = (): SignalHit => ({ setup: '', throttle: 'no_actions', actions: [], + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, + index: undefined, + data_view_id: undefined, + filters: undefined, + saved_id: undefined, }, depth: 1, }, From b64f9fa04f45a804cdd15af5b417df7384702bac Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 22 Aug 2022 15:09:56 -0700 Subject: [PATCH 06/11] Fix types --- .../utils/get_simple_rule_output.ts | 16 ++++++++++++++++ .../utils/resolve_simple_rule_output.ts | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 9c9e34214ed22..052a02f0fed71 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -44,6 +44,19 @@ export const getBaseSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ( id: 'id', updated_at: '2020-07-08T16:36:32.377Z', created_at: '2020-07-08T16:36:32.377Z', + building_block_type: undefined, + note: undefined, + license: undefined, + outcome: undefined, + alias_target_id: undefined, + alias_purpose: undefined, + timeline_id: undefined, + timeline_title: undefined, + meta: undefined, + rule_name_override: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + namespace: undefined, }); const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSchema => ({ @@ -52,6 +65,9 @@ const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSch language: 'kuery', query: 'user.name: root or user.name: admin', type: 'query', + data_view_id: undefined, + filters: undefined, + saved_id: undefined, }); export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => { diff --git a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts index 13fc65823007d..4f8b24e623ac3 100644 --- a/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/resolve_simple_rule_output.ts @@ -8,6 +8,6 @@ import { getSimpleRuleOutput } from './get_simple_rule_output'; export const resolveSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ - outcome: 'exactMatch', ...getSimpleRuleOutput(ruleId, enabled), + outcome: 'exactMatch', }); From 9b8ffc655840f6d725febd8a6f5b0d07c7422307 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Mon, 22 Aug 2022 15:56:51 -0700 Subject: [PATCH 07/11] Remove undefined properties so expect doesn't mess it up --- .../utils/remove_server_generated_properties.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts index 57a2745a12d02..d7e5f4534b478 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts @@ -6,6 +6,7 @@ */ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { pickBy } from 'lodash'; /** * This will remove server generated properties such as date times, etc... @@ -22,5 +23,9 @@ export const removeServerGeneratedProperties = ( execution_summary, ...removedProperties } = rule; - return removedProperties; + // We're only removing undefined values, so this cast correctly narrows the type + return pickBy(removedProperties, (value) => value !== undefined) as Omit< + FullResponseSchema, + 'id' | 'created_at' | 'updated_at' + >; }; From 9ca181f58558b0d9b29a8eadfbc3313a9c5a8909 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Wed, 24 Aug 2022 09:11:51 -0700 Subject: [PATCH 08/11] Use more specific types and rename newTransformValidate --- .../schemas/response/rules_schema.mocks.ts | 19 +++++++++++++------ .../routes/rules/create_rules_route.ts | 4 ++-- .../routes/rules/patch_rules_route.ts | 4 ++-- .../routes/rules/update_rules_route.ts | 4 ++-- .../routes/rules/validate.test.ts | 6 +++--- .../detection_engine/routes/rules/validate.ts | 2 +- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 2f7587cffc88f..ace99179bd658 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -6,7 +6,12 @@ */ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants'; -import type { FullResponseSchema } from '../request'; +import type { + EqlResponseSchema, + MachineLearningResponseSchema, + QueryResponseSchema, + ThreatMatchResponseSchema, +} from '../request'; import { getListArrayMock } from '../types/lists.mock'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; @@ -58,7 +63,7 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE) => ({ namespace: undefined, }); -export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => ({ +export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryResponseSchema => ({ ...getResponseBaseParams(anchorDate), query: 'user.name: root or user.name: admin', type: 'query', @@ -69,7 +74,9 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): FullRespon saved_id: undefined, }); -export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => { +export const getRulesMlSchemaMock = ( + anchorDate: string = ANCHOR_DATE +): MachineLearningResponseSchema => { return { ...getResponseBaseParams(anchorDate), type: 'machine_learning', @@ -80,7 +87,7 @@ export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResp export const getThreatMatchingSchemaMock = ( anchorDate: string = ANCHOR_DATE -): FullResponseSchema => { +): ThreatMatchResponseSchema => { return { ...getResponseBaseParams(anchorDate), type: 'threat_match', @@ -124,7 +131,7 @@ export const getThreatMatchingSchemaMock = ( */ export const getThreatMatchingSchemaPartialMock = ( enabled = false -): Partial => { +): Partial => { return { author: [], created_by: 'elastic', @@ -193,7 +200,7 @@ export const getThreatMatchingSchemaPartialMock = ( }; }; -export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): FullResponseSchema => { +export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): EqlResponseSchema => { return { ...getResponseBaseParams(anchorDate), language: 'eql', 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 7d4e88f3868d0..97545b73ee7b8 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 @@ -16,7 +16,7 @@ import { readRules } from '../../rules/read_rules'; import { buildSiemResponse } from '../utils'; import { createRulesSchema } from '../../../../../common/detection_engine/schemas/request'; -import { newTransformValidate } from './validate'; +import { transformValidate } from './validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; import { createRules } from '../../rules/create_rules'; @@ -85,7 +85,7 @@ export const createRulesRoute = ( const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(createdRule.id); - const [validated, errors] = newTransformValidate(createdRule, ruleExecutionSummary); + const [validated, errors] = transformValidate(createdRule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { 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 9538933055d70..f6f2ff03efa25 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 @@ -18,7 +18,7 @@ import { patchRules } from '../../rules/patch_rules'; import { buildSiemResponse } from '../utils'; import { getIdError } from './utils'; -import { newTransformValidate } from './validate'; +import { transformValidate } from './validate'; import { readRules } from '../../rules/read_rules'; import { legacyMigrate } from '../../rules/utils'; @@ -83,7 +83,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP if (rule != null && rule.enabled != null && rule.name != null) { const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); - const [validated, errors] = newTransformValidate(rule, ruleExecutionSummary); + const [validated, errors] = transformValidate(rule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts index e78027d542e56..81d77e1e2eb71 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -16,7 +16,7 @@ import { throwAuthzError } from '../../../machine_learning/validation'; import { buildSiemResponse } from '../utils'; import { getIdError } from './utils'; -import { newTransformValidate } from './validate'; +import { transformValidate } from './validate'; import { updateRules } from '../../rules/update_rules'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { legacyMigrate } from '../../rules/utils'; @@ -73,7 +73,7 @@ export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: Setup if (rule != null) { const ruleExecutionLog = ctx.securitySolution.getRuleExecutionLog(); const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); - const [validated, errors] = newTransformValidate(rule, ruleExecutionSummary); + const [validated, errors] = transformValidate(rule, ruleExecutionSummary); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts index 72d8aaeff2c82..84f693a529a68 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { newTransformValidate, transformValidateBulkError } from './validate'; +import { transformValidate, transformValidateBulkError } from './validate'; import type { BulkError } from '../utils'; import { getRuleMock } from '../__mocks__/request_responses'; import { ruleExecutionSummaryMock } from '../../../../../common/detection_engine/rule_monitoring/mocks'; @@ -82,7 +82,7 @@ describe('validate', () => { describe('transformValidate', () => { test('it should do a validation correctly of a partial alert', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); - const [validated, errors] = newTransformValidate(ruleAlert, null); + const [validated, errors] = transformValidate(ruleAlert, null); expect(validated).toEqual(ruleOutput()); expect(errors).toEqual(null); }); @@ -91,7 +91,7 @@ describe('validate', () => { const ruleAlert = getRuleMock(getQueryRuleParams()); // @ts-expect-error delete ruleAlert.name; - const [validated, errors] = newTransformValidate(ruleAlert, null); + const [validated, errors] = transformValidate(ruleAlert, null); expect(validated).toEqual(null); expect(errors).toEqual('Invalid value "undefined" supplied to "name"'); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts index bc01db7d4fe07..42e50db79294a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts @@ -20,7 +20,7 @@ import type { RuleParams } from '../../schemas/rule_schemas'; import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; -export const newTransformValidate = ( +export const transformValidate = ( rule: PartialRule, ruleExecutionSummary: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null From 17cddda704fa5047be2e634c69716c7152143f98 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Wed, 24 Aug 2022 11:43:20 -0700 Subject: [PATCH 09/11] Fix types in schema tests --- .../schemas/response/rules_schema.mocks.ts | 13 ++++++++++ .../schemas/response/rules_schema.test.ts | 26 ++++++------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index ace99179bd658..8e40256f7ee23 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -10,6 +10,7 @@ import type { EqlResponseSchema, MachineLearningResponseSchema, QueryResponseSchema, + SavedQueryResponseSchema, ThreatMatchResponseSchema, } from '../request'; import { getListArrayMock } from '../types/lists.mock'; @@ -73,6 +74,18 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRespo filters: undefined, saved_id: undefined, }); +export const getSavedQuerySchemaMock = ( + anchorDate: string = ANCHOR_DATE +): SavedQueryResponseSchema => ({ + ...getResponseBaseParams(anchorDate), + query: 'user.name: root or user.name: admin', + type: 'saved_query', + saved_id: 'save id 123', + language: 'kuery', + index: undefined, + data_view_id: undefined, + filters: undefined, +}); export const getRulesMlSchemaMock = ( anchorDate: string = ANCHOR_DATE diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts index b784df4384152..0a337eb28bc1c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts @@ -12,6 +12,7 @@ import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts import { getRulesSchemaMock, getRulesMlSchemaMock, + getSavedQuerySchemaMock, getThreatMatchingSchemaMock, getRulesEqlSchemaMock, } from './rules_schema.mocks'; @@ -69,25 +70,20 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + const payload = getSavedQuerySchemaMock(); const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); - - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; + const expected = getSavedQuerySchemaMock(); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); - payload.type = 'saved_query'; + const payload: FullResponseSchema & { saved_id?: string } = getSavedQuerySchemaMock(); + // @ts-expect-error delete payload.saved_id; const decoded = fullResponseSchema.decode(payload); @@ -102,9 +98,7 @@ describe('rules_schema', () => { test('it should NOT validate a type of "saved_query" when it has extra data', () => { const payload: FullResponseSchema & { saved_id?: string; invalid_extra_data?: string } = - getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + getSavedQuerySchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; const decoded = fullResponseSchema.decode(payload); @@ -189,19 +183,15 @@ describe('rules_schema', () => { test('it should validate a type of "saved_query" with "data_view_id" defined', () => { const payload: FullResponseSchema & { saved_id?: string; data_view_id?: string } = - getRulesSchemaMock(); - payload.type = 'saved_query'; - payload.saved_id = 'save id 123'; + getSavedQuerySchemaMock(); payload.data_view_id = 'logs-*'; const decoded = fullResponseSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected: FullResponseSchema & { saved_id?: string; data_view_id?: string } = - getRulesSchemaMock(); + getSavedQuerySchemaMock(); - expected.type = 'saved_query'; - expected.saved_id = 'save id 123'; expected.data_view_id = 'logs-*'; expect(getPaths(left(message.errors))).toEqual([]); From 5d3bd8a30a3f79434f540185e1a311b6349ac370 Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Wed, 7 Sep 2022 10:11:06 -0700 Subject: [PATCH 10/11] Review comments - explicit return types, better removeServerGeneratedProperties --- .../schemas/request/rule_schemas.ts | 2 +- .../schemas/response/rules_schema.mocks.ts | 3 +- .../security_solution/cypress/objects/rule.ts | 4 ++- .../utils/get_complex_rule_output.ts | 6 +++- .../utils/get_simple_ml_rule_output.ts | 4 +-- .../utils/get_simple_rule_output.ts | 20 ++++++++----- ...simple_rule_output_with_web_hook_action.ts | 5 +++- .../get_simple_rule_output_without_rule_id.ts | 5 +++- .../remove_server_generated_properties.ts | 30 ++++++++++--------- 9 files changed, 50 insertions(+), 29 deletions(-) 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 b49f4d2ab629a..08e609938938c 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 @@ -508,7 +508,7 @@ const sharedResponseSchema = t.intersection([ t.exact(t.type(responseRequiredFields)), t.exact(t.partial(responseOptionalFields)), ]); -type SharedResponseSchema = t.TypeOf; +export type SharedResponseSchema = t.TypeOf; export const fullResponseSchema = t.intersection([sharedResponseSchema, responseTypeSpecific]); export type FullResponseSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 8e40256f7ee23..bf6583a6855f0 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -11,13 +11,14 @@ import type { MachineLearningResponseSchema, QueryResponseSchema, SavedQueryResponseSchema, + SharedResponseSchema, ThreatMatchResponseSchema, } from '../request'; import { getListArrayMock } from '../types/lists.mock'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; -const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE) => ({ +const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponseSchema => ({ author: [], id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index e567ac9d62277..6b0051f12bc29 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -511,7 +511,9 @@ export const expectedExportedRule = ( } // NOTE: Order of the properties in this object matters for the tests to work. - const rule = { + // TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object + // without using Partial + const rule: Partial = { id, updated_at: updatedAt, updated_by: updatedBy, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index 0b39b751c8656..1491829b33999 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -5,11 +5,15 @@ * 2.0. */ +import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + +// TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object +// without using Partial /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRuleOutput = (ruleId = 'rule-1') => ({ +export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], author: [], created_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts index edff2ec3441f8..56afa355b0482 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts @@ -6,12 +6,12 @@ */ import type { MachineLearningResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; -import { getBaseSimpleRuleOutput } from './get_simple_rule_output'; +import { getMockSharedResponseSchema } from './get_simple_rule_output'; import { removeServerGeneratedProperties } from './remove_server_generated_properties'; const getBaseMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { return { - ...getBaseSimpleRuleOutput(ruleId), + ...getMockSharedResponseSchema(ruleId), name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', anomaly_threshold: 44, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 052a02f0fed71..1fe2f2adecc79 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -5,14 +5,16 @@ * 2.0. */ -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { + FullResponseSchema, + SharedResponseSchema, +} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { removeServerGeneratedProperties } from './remove_server_generated_properties'; -/** - * This is the typical output of a simple rule that Kibana will output with all the defaults - * except for the server generated properties. Useful for testing end to end tests. - */ -export const getBaseSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ({ +export const getMockSharedResponseSchema = ( + ruleId = 'rule-1', + enabled = false +): SharedResponseSchema => ({ actions: [], author: [], created_by: 'elastic', @@ -60,7 +62,7 @@ export const getBaseSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => ( }); const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSchema => ({ - ...getBaseSimpleRuleOutput(ruleId, enabled), + ...getMockSharedResponseSchema(ruleId, enabled), index: ['auditbeat-*'], language: 'kuery', query: 'user.name: root or user.name: admin', @@ -70,6 +72,10 @@ const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSch saved_id: undefined, }); +/** + * This is the typical output of a simple rule that Kibana will output with all the defaults + * except for the server generated properties. Useful for testing end to end tests. + */ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false) => { return removeServerGeneratedProperties(getQueryRuleOutput(ruleId, enabled)); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts index f62767fdf0ad1..c96537bfd0813 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_with_web_hook_action.ts @@ -6,8 +6,11 @@ */ import { getSimpleRuleOutput } from './get_simple_rule_output'; +import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; -export const getSimpleRuleOutputWithWebHookAction = (actionId: string) => ({ +export const getSimpleRuleOutputWithWebHookAction = ( + actionId: string +): RuleWithoutServerGeneratedProperties => ({ ...getSimpleRuleOutput(), throttle: 'rule', actions: [ diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts index 418e91240c6bc..56b5ab66773bb 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output_without_rule_id.ts @@ -6,12 +6,15 @@ */ import { getSimpleRuleOutput } from './get_simple_rule_output'; +import { RuleWithoutServerGeneratedProperties } from './remove_server_generated_properties'; /** * This is the typical output of a simple rule that Kibana will output with all the defaults except * for all the server generated properties such as created_by. Useful for testing end to end tests. */ -export const getSimpleRuleOutputWithoutRuleId = (ruleId = 'rule-1') => { +export const getSimpleRuleOutputWithoutRuleId = ( + ruleId = 'rule-1' +): Omit => { const rule = getSimpleRuleOutput(ruleId); const { rule_id: rId, ...ruleWithoutRuleId } = rule; return ruleWithoutRuleId; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts index d7e5f4534b478..8d8a34bba8b79 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts @@ -6,7 +6,15 @@ */ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; -import { pickBy } from 'lodash'; +import { omit, pickBy } from 'lodash'; + +const serverGeneratedProperties = ['id', 'created_at', 'updated_at', 'execution_summary'] as const; + +type ServerGeneratedProperties = typeof serverGeneratedProperties[number]; +export type RuleWithoutServerGeneratedProperties = Omit< + FullResponseSchema, + ServerGeneratedProperties +>; /** * This will remove server generated properties such as date times, etc... @@ -14,18 +22,12 @@ import { pickBy } from 'lodash'; */ export const removeServerGeneratedProperties = ( rule: FullResponseSchema -): Omit => { - const { - /* eslint-disable @typescript-eslint/naming-convention */ - id, - created_at, - updated_at, - execution_summary, - ...removedProperties - } = rule; +): RuleWithoutServerGeneratedProperties => { + const removedProperties = omit(rule, serverGeneratedProperties); + // We're only removing undefined values, so this cast correctly narrows the type - return pickBy(removedProperties, (value) => value !== undefined) as Omit< - FullResponseSchema, - 'id' | 'created_at' | 'updated_at' - >; + return pickBy( + removedProperties, + (value) => value !== undefined + ) as RuleWithoutServerGeneratedProperties; }; From 0ecb956646764bd87136d059e838556a8d28d25b Mon Sep 17 00:00:00 2001 From: Marshall Main Date: Wed, 7 Sep 2022 10:35:47 -0700 Subject: [PATCH 11/11] Re-add deleted test with modified assertion --- .../schemas/response/rules_bulk_schema.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts index ea8ffed5bab26..69e31522ef40a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts @@ -64,6 +64,21 @@ describe('prepackaged_rule_schema', () => { expect(message.schema).toEqual({}); }); + test('it should NOT validate an invalid error message with a deleted value', () => { + const error = getErrorSchemaMock('fake id'); + // @ts-expect-error + delete error.error; + const payload: RulesBulkSchema = [error]; + const decoded = rulesBulkSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "error"' + ); + expect(message.schema).toEqual({}); + }); + test('it should NOT validate a type of "query" when it has extra data', () => { const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data';