diff --git a/.eslintrc.js b/.eslintrc.js index 3cac46e7d2605..b5f8d106392d5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -589,8 +589,11 @@ module.exports = { * Security Solution overrides */ { - // front end typescript and javascript files only - files: ['x-pack/plugins/security_solution/public/**/*.{js,ts,tsx}'], + // front end and common typescript and javascript files only + files: [ + 'x-pack/plugins/security_solution/public/**/*.{js,ts,tsx}', + 'x-pack/plugins/security_solution/common/**/*.{js,ts,tsx}', + ], rules: { 'import/no-nodejs-modules': 'error', 'no-restricted-imports': [ @@ -766,6 +769,23 @@ module.exports = { /** * Lists overrides */ + { + // front end and common typescript and javascript files only + files: [ + 'x-pack/plugins/lists/public/**/*.{js,ts,tsx}', + 'x-pack/plugins/lists/common/**/*.{js,ts,tsx}', + ], + rules: { + 'import/no-nodejs-modules': 'error', + 'no-restricted-imports': [ + 'error', + { + // prevents UI code from importing server side code and then webpack including it when doing builds + patterns: ['**/server/*'], + }, + ], + }, + }, { // typescript and javascript for front and back end files: ['x-pack/plugins/lists/**/*.{js,ts,tsx}'], diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts index 0a5e861d84483..ee6a2aa0b339a 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts @@ -6,9 +6,6 @@ /* eslint-disable @typescript-eslint/camelcase */ -// TODO: You cannot import a stream from common into the front end code! CHANGE THIS -import { Readable } from 'stream'; - import * as t from 'io-ts'; import { file } from '../common/schemas'; @@ -20,17 +17,3 @@ export const importListItemSchema = t.exact( ); export type ImportListItemSchema = t.TypeOf; - -// TODO: You cannot import a stream from common into the front end code! CHANGE THIS -export interface HapiReadableStream extends Readable { - hapi: { - filename: string; - }; -} - -/** - * Special interface since we are streaming in a file through a reader - */ -export interface ImportListItemHapiFileSchema { - file: HapiReadableStream; -} diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index 36cf9bac373eb..c951c9b337131 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Readable } from 'stream'; + import { IRouter } from 'kibana/server'; import { LIST_ITEM_URL } from '../../common/constants'; @@ -13,15 +15,23 @@ import { transformError, validate, } from '../siem_server_deps'; -import { - ImportListItemHapiFileSchema, - importListItemQuerySchema, - importListItemSchema, - listSchema, -} from '../../common/schemas'; +import { importListItemQuerySchema, importListItemSchema, listSchema } from '../../common/schemas'; import { getListClient } from '.'; +export interface HapiReadableStream extends Readable { + hapi: { + filename: string; + }; +} + +/** + * Special interface since we are streaming in a file through a reader + */ +export interface ImportListItemHapiFileSchema { + file: HapiReadableStream; +} + export const importListItemRoute = (router: IRouter): void => { router.post( { diff --git a/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts b/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts index 9413bf9a3d84e..a283269271bd0 100644 --- a/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts +++ b/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TestReadable } from '../../../common/test_readable.mock'; - import { BufferLines } from './buffer_lines'; +import { TestReadable } from './test_readable.mock'; describe('buffer_lines', () => { test('it can read a single line', (done) => { diff --git a/x-pack/plugins/lists/common/test_readable.mock.ts b/x-pack/plugins/lists/server/services/items/test_readable.mock.ts similarity index 100% rename from x-pack/plugins/lists/common/test_readable.mock.ts rename to x-pack/plugins/lists/server/services/items/test_readable.mock.ts diff --git a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts index 3d9902e1d43dd..ccacdfbb5ff65 100644 --- a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts +++ b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TestReadable } from '../../../common/test_readable.mock'; import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { ImportListItemsToStreamOptions, WriteBufferToItemsOptions } from '../items'; import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from '../../../common/constants.mock'; +import { TestReadable } from './test_readable.mock'; + export const getImportListItemsToStreamOptionsMock = (): ImportListItemsToStreamOptions => ({ callCluster: getCallClusterMock(), listId: LIST_ID, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 7db8e57421d02..b0b9d70a76c32 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -143,6 +143,9 @@ export type Throttle = t.TypeOf; export const throttleOrNull = t.union([throttle, t.null]); export type ThrottleOrNull = t.TypeOf; +export const throttleOrNullOrUndefined = t.union([throttle, t.null, t.undefined]); +export type ThrottleOrUndefinedOrNull = t.TypeOf; + export const anomaly_threshold = PositiveInteger; export type AnomalyThreshold = t.TypeOf; @@ -156,11 +159,8 @@ export const machineLearningJobIdOrUndefined = t.union([machine_learning_job_id, export type MachineLearningJobIdOrUndefined = t.TypeOf; /** - * Note that this is a plain unknown object because we allow the UI - * to send us extra additional information as "meta" which can be anything. - * - * TODO: Strip away extra information and possibly even "freeze" this object - * so we have tighter control over 3rd party data structures. + * Note that this is a non-exact io-ts type as we allow extra meta information + * to be added to the meta object */ export const meta = t.object; export type Meta = t.TypeOf; @@ -192,8 +192,10 @@ export const severityOrUndefined = t.union([severity, t.undefined]); export type SeverityOrUndefined = t.TypeOf; export const status = t.keyof({ open: null, closed: null }); +export type Status = t.TypeOf; export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null }); +export type JobStatus = t.TypeOf; // TODO: Create a regular expression type or custom date math part type here export const to = t.string; @@ -307,10 +309,20 @@ export const versionOrUndefined = t.union([version, t.undefined]); export type VersionOrUndefined = t.TypeOf; export const last_success_at = IsoDateString; +export type LastSuccessAt = t.TypeOf; + export const last_success_message = t.string; +export type LastSuccessMessage = t.TypeOf; + export const last_failure_at = IsoDateString; +export type LastFailureAt = t.TypeOf; + export const last_failure_message = t.string; +export type LastFailureMessage = t.TypeOf; + export const status_date = IsoDateString; +export type StatusDate = t.TypeOf; + export const rules_installed = PositiveInteger; export const rules_updated = PositiveInteger; export const status_code = PositiveInteger; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts index a9ab6f8959e24..2847bd32df514 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema.mock.ts @@ -7,19 +7,30 @@ import { CreateRulesSchema, CreateRulesSchemaDecoded } from './create_rules_schema'; import { DEFAULT_MAX_SIGNALS } from '../../../constants'; -export const getCreateRulesSchemaMock = (): CreateRulesSchema => ({ - description: 'some description', +export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): CreateRulesSchema => ({ + description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', type: 'query', risk_score: 55, language: 'kuery', - rule_id: 'rule-1', + rule_id: ruleId, }); +export const getCreateMlRulesSchemaMock = (ruleId = 'rule-1') => { + const { query, language, index, ...mlParams } = getCreateRulesSchemaMock(ruleId); + + return { + ...mlParams, + type: 'machine_learning', + anomaly_threshold: 58, + machine_learning_job_id: 'typical-ml-job-id', + }; +}; + export const getCreateRulesSchemaDecodedMock = (): CreateRulesSchemaDecoded => ({ - description: 'some description', + description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', severity: 'high', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts index 92fab202c9ddc..aaeb90ffc5bcf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts @@ -7,7 +7,7 @@ import { ImportRulesSchema, ImportRulesSchemaDecoded } from './import_rules_schema'; import { DEFAULT_MAX_SIGNALS } from '../../../constants'; -export const getImportRulesSchemaMock = (): ImportRulesSchema => ({ +export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -15,7 +15,19 @@ export const getImportRulesSchemaMock = (): ImportRulesSchema => ({ type: 'query', risk_score: 55, language: 'kuery', - rule_id: 'rule-1', + rule_id: ruleId, +}); + +export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ + id: '6afb8ce1-ea94-4790-8653-fd0b021d2113', + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'query', + risk_score: 55, + language: 'kuery', + rule_id: ruleId, }); export const getImportRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ({ @@ -42,3 +54,22 @@ export const getImportRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ( rule_id: 'rule-1', immutable: false, }); + +/** + * Given an array of rules, builds an NDJSON string of rules + * as we might import/export + * @param rules Array of rule objects with which to generate rule JSON + */ +export const rulesToNdJsonString = (rules: ImportRulesSchema[]) => { + return rules.map((rule) => JSON.stringify(rule)).join('\r\n'); +}; + +/** + * Given an array of rule IDs, builds an NDJSON string of rules + * as we might import + * @param ruleIds Array of ruleIds with which to generate rule JSON + */ +export const ruleIdsToNdJsonString = (ruleIds: string[]) => { + const rules = ruleIds.map((ruleId) => getImportRulesSchemaMock(ruleId)); + return rulesToNdJsonString(rules); +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.mocks.ts new file mode 100644 index 0000000000000..52b2de316ddd5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.mocks.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ErrorSchema } from './error_schema'; + +export const getErrorSchemaMock = ( + id: string = '819eded6-e9c8-445b-a647-519aea39e063' +): ErrorSchema => ({ + id, + error: { + status_code: 404, + message: 'id: "819eded6-e9c8-445b-a647-519aea39e063" not found', + }, +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts index 2a4d75522d010..d9e16105351bd 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.test.ts @@ -7,15 +7,15 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; import { exactCheck } from '../../../exact_check'; import { foldLeftRight, getPaths } from '../../../test_utils'; +import { getErrorSchemaMock } from './error_schema.mocks'; describe('error_schema', () => { test('it should validate an error with a UUID given for id', () => { - const error = getErrorPayload(); - const decoded = errorSchema.decode(getErrorPayload()); + const error = getErrorSchemaMock(); + const decoded = errorSchema.decode(getErrorSchemaMock()); const checked = exactCheck(error, decoded); const message = pipe(checked, foldLeftRight); @@ -24,7 +24,7 @@ describe('error_schema', () => { }); test('it should validate an error with a plain string given for id since sometimes we echo the user id which might not be a UUID back out to them', () => { - const error = getErrorPayload('fake id'); + const error = getErrorSchemaMock('fake id'); const decoded = errorSchema.decode(error); const checked = exactCheck(error, decoded); const message = pipe(checked, foldLeftRight); @@ -35,7 +35,7 @@ describe('error_schema', () => { test('it should NOT validate an error when it has extra data next to a valid payload element', () => { type InvalidError = ErrorSchema & { invalid_extra_data?: string }; - const error: InvalidError = getErrorPayload(); + const error: InvalidError = getErrorSchemaMock(); error.invalid_extra_data = 'invalid_extra_data'; const decoded = errorSchema.decode(error); const checked = exactCheck(error, decoded); @@ -46,7 +46,7 @@ describe('error_schema', () => { }); test('it should NOT validate an error when it has required elements deleted from it', () => { - const error = getErrorPayload(); + const error = getErrorSchemaMock(); delete error.error; const decoded = errorSchema.decode(error); const checked = exactCheck(error, decoded); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts new file mode 100644 index 0000000000000..28c3eea761b40 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FindRulesSchema } from './find_rules_schema'; +import { getRulesSchemaMock } from './rules_schema.mocks'; + +export const getFindRulesSchemaMock = (): FindRulesSchema => ({ + page: 1, + perPage: 1, + total: 1, + data: [getRulesSchemaMock()], +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts index 51163c3d76ed6..fc1ab9b87795d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts @@ -6,31 +6,32 @@ import { findRulesSchema, FindRulesSchema } from './find_rules_schema'; import { pipe } from 'fp-ts/lib/pipeable'; -import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils'; import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; import { exactCheck } from '../../../exact_check'; import { foldLeftRight, getPaths } from '../../../test_utils'; +import { getRulesSchemaMock } from './rules_schema.mocks'; +import { getFindRulesSchemaMock } from './find_rules_schema.mocks'; describe('find_rules_schema', () => { test('it should validate a typical single find rules response', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getFindResponseSingle()); + expect(message.schema).toEqual(getFindRulesSchemaMock()); }); test('it should validate an empty find rules response', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); payload.data = []; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getFindResponseSingle(); + const expected = getFindRulesSchemaMock(); expected.data = []; expect(getPaths(left(message.errors))).toEqual([]); @@ -38,7 +39,7 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if it is has an extra property on it', () => { - const payload: FindRulesSchema & { invalid_data?: 'invalid' } = getFindResponseSingle(); + const payload: FindRulesSchema & { invalid_data?: 'invalid' } = getFindRulesSchemaMock(); payload.invalid_data = 'invalid'; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -49,8 +50,8 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if the rules are invalid within it', () => { - const payload = getFindResponseSingle(); - const invalidRule: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + const payload = getFindRulesSchemaMock(); + const invalidRule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); invalidRule.invalid_extra_data = 'invalid_data'; payload.data = [invalidRule]; const decoded = findRulesSchema.decode(payload); @@ -62,8 +63,8 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if the rule is missing a required field such as name', () => { - const payload = getFindResponseSingle(); - const invalidRule = getBaseResponsePayload(); + const payload = getFindRulesSchemaMock(); + const invalidRule = getRulesSchemaMock(); delete invalidRule.name; payload.data = [invalidRule]; const decoded = findRulesSchema.decode(payload); @@ -77,7 +78,7 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if it is missing perPage', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); delete payload.perPage; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -90,7 +91,7 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if it has a negative perPage number', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); payload.perPage = -1; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -101,7 +102,7 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if it has a negative page number', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); payload.page = -1; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); @@ -112,7 +113,7 @@ describe('find_rules_schema', () => { }); test('it should invalidate a typical single find rules response if it has a negative total', () => { - const payload = getFindResponseSingle(); + const payload = getFindRulesSchemaMock(); payload.total = -1; const decoded = findRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.mocks.ts new file mode 100644 index 0000000000000..b55809e6b27ea --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.mocks.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RulesBulkSchema } from './rules_bulk_schema'; +import { getRulesSchemaMock } from './rules_schema.mocks'; + +export const getRulesBulkSchemaMock = (): RulesBulkSchema => [getRulesSchemaMock()]; 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 04cf012f36dba..a169ac3deb9b9 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 @@ -7,46 +7,48 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { getBaseResponsePayload, getErrorPayload } from './__mocks__/utils'; import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; import { exactCheck } from '../../../exact_check'; import { foldLeftRight, getPaths } from '../../../test_utils'; +import { getRulesSchemaMock } from './rules_schema.mocks'; +import { getErrorSchemaMock } from './error_schema.mocks'; + describe('prepackaged_rule_schema', () => { test('it should validate a regular message and and error together with a uuid', () => { - const payload: RulesBulkSchema = [getBaseResponsePayload(), getErrorPayload()]; + const payload: RulesBulkSchema = [getRulesSchemaMock(), getErrorSchemaMock()]; const decoded = rulesBulkSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([getBaseResponsePayload(), getErrorPayload()]); + expect(message.schema).toEqual([getRulesSchemaMock(), getErrorSchemaMock()]); }); test('it should validate a regular message and and error together when the error has a non UUID', () => { - const payload: RulesBulkSchema = [getBaseResponsePayload(), getErrorPayload('fake id')]; + const payload: RulesBulkSchema = [getRulesSchemaMock(), getErrorSchemaMock('fake id')]; const decoded = rulesBulkSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([getBaseResponsePayload(), getErrorPayload('fake id')]); + expect(message.schema).toEqual([getRulesSchemaMock(), getErrorSchemaMock('fake id')]); }); test('it should validate an error', () => { - const payload: RulesBulkSchema = [getErrorPayload('fake id')]; + const payload: RulesBulkSchema = [getErrorSchemaMock('fake id')]; const decoded = rulesBulkSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual([getErrorPayload('fake id')]); + expect(message.schema).toEqual([getErrorSchemaMock('fake id')]); }); test('it should NOT validate a rule with a deleted value', () => { - const rule = getBaseResponsePayload(); + const rule = getRulesSchemaMock(); delete rule.name; const payload: RulesBulkSchema = [rule]; const decoded = rulesBulkSchema.decode(payload); @@ -61,7 +63,7 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate an invalid error message with a deleted value', () => { - const error = getErrorPayload('fake id'); + const error = getErrorSchemaMock('fake id'); delete error.error; const payload: RulesBulkSchema = [error]; const decoded = rulesBulkSchema.decode(payload); @@ -76,7 +78,7 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data', () => { - const rule: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; const payload: RulesBulkSchema = [rule]; const decoded = rulesBulkSchema.decode(payload); @@ -88,9 +90,9 @@ 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 } = getBaseResponsePayload(); + const rule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; - const payload: RulesBulkSchema = [getErrorPayload(), rule]; + const payload: RulesBulkSchema = [getErrorSchemaMock(), rule]; const decoded = rulesBulkSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -101,7 +103,7 @@ describe('prepackaged_rule_schema', () => { test('it should NOT validate an error when it has extra data', () => { type InvalidError = ErrorSchema & { invalid_extra_data?: string }; - const error: InvalidError = getErrorPayload(); + const error: InvalidError = getErrorSchemaMock(); error.invalid_extra_data = 'invalid'; const payload: RulesBulkSchema = [error]; const decoded = rulesBulkSchema.decode(payload); @@ -114,9 +116,9 @@ describe('prepackaged_rule_schema', () => { test('it should NOT validate an error when it has extra data next to a valid payload element', () => { type InvalidError = ErrorSchema & { invalid_extra_data?: string }; - const error: InvalidError = getErrorPayload(); + const error: InvalidError = getErrorSchemaMock(); error.invalid_extra_data = 'invalid'; - const payload: RulesBulkSchema = [getBaseResponsePayload(), error]; + const payload: RulesBulkSchema = [getRulesSchemaMock(), error]; const decoded = rulesBulkSchema.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/__mocks__/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts similarity index 65% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/__mocks__/utils.ts rename to x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index fef6bcf42e49f..ecbf0321cdc67 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -4,14 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RulesSchema } from '../rules_schema'; -import { RulesBulkSchema } from '../rules_bulk_schema'; -import { ErrorSchema } from '../error_schema'; -import { FindRulesSchema } from '../find_rules_schema'; +import { RulesSchema } from './rules_schema'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; -export const getBaseResponsePayload = (anchorDate: string = ANCHOR_DATE): RulesSchema => ({ +export const getPartialRulesSchemaMock = (): Partial => ({ + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: ['some fake tag 1', 'some fake tag 2'], + to: 'now', + type: 'query', + note: '', +}); + +export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => ({ id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), updated_at: new Date(anchorDate).toISOString(), @@ -75,10 +98,8 @@ export const getBaseResponsePayload = (anchorDate: string = ANCHOR_DATE): RulesS ], }); -export const getRulesBulkPayload = (): RulesBulkSchema => [getBaseResponsePayload()]; - -export const getMlRuleResponsePayload = (anchorDate: string = ANCHOR_DATE): RulesSchema => { - const basePayload = getBaseResponsePayload(anchorDate); +export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): RulesSchema => { + const basePayload = getRulesSchemaMock(anchorDate); const { filters, index, query, language, ...rest } = basePayload; return { @@ -88,20 +109,3 @@ export const getMlRuleResponsePayload = (anchorDate: string = ANCHOR_DATE): Rule machine_learning_job_id: 'some_machine_learning_job_id', }; }; - -export const getErrorPayload = ( - id: string = '819eded6-e9c8-445b-a647-519aea39e063' -): ErrorSchema => ({ - id, - error: { - status_code: 404, - message: 'id: "819eded6-e9c8-445b-a647-519aea39e063" not found', - }, -}); - -export const getFindResponseSingle = (): FindRulesSchema => ({ - page: 1, - perPage: 1, - total: 1, - data: [getBaseResponsePayload()], -}); 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 8ed9c30507f4f..90aef656db369 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 @@ -18,28 +18,28 @@ import { addTimelineTitle, addMlFields, } from './rules_schema'; -import { getBaseResponsePayload, getMlRuleResponsePayload } from './__mocks__/utils'; import { exactCheck } from '../../../exact_check'; import { foldLeftRight, getPaths } from '../../../test_utils'; import { TypeAndTimelineOnly } from './type_timeline_only_schema'; +import { getRulesSchemaMock, getRulesMlSchemaMock } from './rules_schema.mocks'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; describe('rules_schema', () => { test('it should validate a type of "query" without anything extra', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); const decoded = rulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); }); test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: RulesSchema & { invalid_extra_data?: string } = getBaseResponsePayload(); + const payload: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; const decoded = rulesSchema.decode(payload); @@ -51,7 +51,7 @@ describe('rules_schema', () => { }); test('it should NOT validate invalid_data for the type', () => { - const payload: Omit & { type: string } = getBaseResponsePayload(); + const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; const decoded = rulesSchema.decode(payload); @@ -65,7 +65,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; @@ -78,14 +78,14 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'saved_query'; payload.saved_id = 'save id 123'; const decoded = rulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.type = 'saved_query'; expected.saved_id = 'save id 123'; @@ -95,7 +95,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'saved_query'; delete payload.saved_id; @@ -110,7 +110,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -124,14 +124,14 @@ describe('rules_schema', () => { }); test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; const decoded = rulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.timeline_id = 'some timeline id'; expected.timeline_title = 'some timeline title'; @@ -140,7 +140,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -154,7 +154,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; const decoded = rulesSchema.decode(payload); @@ -168,7 +168,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_title = 'some timeline title'; const decoded = rulesSchema.decode(payload); @@ -180,7 +180,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_title = 'some timeline title'; @@ -194,7 +194,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_id = 'some timeline id'; @@ -211,19 +211,19 @@ describe('rules_schema', () => { describe('checkTypeDependents', () => { test('it should validate a type of "query" without anything extra', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); const decoded = checkTypeDependents(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + 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 } = getBaseResponsePayload(); + const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; const decoded = checkTypeDependents(payload); @@ -237,7 +237,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; @@ -250,14 +250,14 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + 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 = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.type = 'saved_query'; expected.saved_id = 'save id 123'; @@ -267,7 +267,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'saved_query'; delete payload.saved_id; @@ -282,7 +282,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -296,14 +296,14 @@ describe('rules_schema', () => { }); test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + 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 = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.timeline_id = 'some timeline id'; expected.timeline_title = 'some timeline title'; @@ -312,7 +312,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -326,7 +326,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; const decoded = checkTypeDependents(payload); @@ -340,7 +340,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_title = 'some timeline title'; const decoded = checkTypeDependents(payload); @@ -352,7 +352,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_title = 'some timeline title'; @@ -366,7 +366,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_id = 'some timeline id'; @@ -384,20 +384,20 @@ describe('rules_schema', () => { describe('getDependents', () => { test('it should validate a type of "query" without anything extra', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); const dependents = getDependents(payload); const decoded = dependents.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + 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 } = getBaseResponsePayload(); + const payload: Omit & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; const dependents = getDependents((payload as unknown) as TypeAndTimelineOnly); @@ -412,7 +412,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" with a saved_id together', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; @@ -426,7 +426,7 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'saved_query'; payload.saved_id = 'save id 123'; @@ -434,7 +434,7 @@ describe('rules_schema', () => { const decoded = dependents.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.type = 'saved_query'; expected.saved_id = 'save id 123'; @@ -444,7 +444,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.type = 'saved_query'; delete payload.saved_id; @@ -460,7 +460,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -475,7 +475,7 @@ describe('rules_schema', () => { }); test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; @@ -483,7 +483,7 @@ describe('rules_schema', () => { const decoded = dependents.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getBaseResponsePayload(); + const expected = getRulesSchemaMock(); expected.timeline_id = 'some timeline id'; expected.timeline_title = 'some timeline title'; @@ -492,7 +492,7 @@ 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 } = getBaseResponsePayload(); + 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'; @@ -507,7 +507,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is NOT a "timeline_title" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; const dependents = getDependents(payload); @@ -522,7 +522,7 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_title" if there is NOT a "timeline_id" dependent', () => { - const payload = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.timeline_title = 'some timeline title'; const dependents = getDependents(payload); @@ -535,7 +535,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_title = 'some timeline title'; @@ -549,7 +549,7 @@ describe('rules_schema', () => { }); 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 = getBaseResponsePayload(); + const payload = getRulesSchemaMock(); payload.saved_id = 'some saved id'; payload.type = 'saved_query'; payload.timeline_id = 'some timeline id'; @@ -566,13 +566,13 @@ describe('rules_schema', () => { }); test('it validates an ML rule response', () => { - const payload = getMlRuleResponsePayload(); + const payload = getRulesMlSchemaMock(); const dependents = getDependents(payload); const decoded = dependents.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getMlRuleResponsePayload(); + const expected = getRulesMlSchemaMock(); expect(getPaths(left(message.errors))).toEqual([]); expect(message.schema).toEqual(expected); @@ -580,8 +580,8 @@ describe('rules_schema', () => { test('it rejects a response with both ML and query properties', () => { const payload = { - ...getBaseResponsePayload(), - ...getMlRuleResponsePayload(), + ...getRulesSchemaMock(), + ...getRulesMlSchemaMock(), }; const dependents = getDependents(payload); diff --git a/x-pack/plugins/security_solution/common/exact_check.ts b/x-pack/plugins/security_solution/common/exact_check.ts index 48afc35b56ba1..041d0fb324df7 100644 --- a/x-pack/plugins/security_solution/common/exact_check.ts +++ b/x-pack/plugins/security_solution/common/exact_check.ts @@ -47,7 +47,7 @@ export const exactCheck = ( return pipe(decoded, fold(onLeft, onRight)); }; -export const findDifferencesRecursive = (original: T, decodedValue: T): string[] => { +export const findDifferencesRecursive = (original: unknown, decodedValue: T): string[] => { if (decodedValue === null && original === null) { // both the decodedValue and the original are null which indicates that they are equal // so do not report differences diff --git a/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson b/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson index c2e779feeca77..dcbfa9d0dd16e 100644 --- a/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson +++ b/x-pack/plugins/security_solution/cypress/test_files/expected_rules_export.ndjson @@ -1,2 +1,2 @@ -{"actions":[],"created_at":"2020-03-26T10:09:07.569Z","updated_at":"2020-03-26T10:09:08.021Z","created_by":"elastic","description":"Rule 1","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","id":"49db5bd1-bdd5-4821-be26-bb70a815dedb","immutable":false,"index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"0cea4194-03f2-4072-b281-d31b72221d9d","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":50,"name":"Rule 1","query":"host.name:*","references":[],"meta":{"from":"1m","throttle":"no_actions"},"severity":"low","updated_by":"elastic","tags":["rule1"],"to":"now","type":"query","threat":[],"throttle":"no_actions","version":1} +{"actions":[],"created_at":"2020-03-26T10:09:07.569Z","updated_at":"2020-03-26T10:09:08.021Z","created_by":"elastic","description":"Rule 1","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","id":"49db5bd1-bdd5-4821-be26-bb70a815dedb","immutable":false,"index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"0cea4194-03f2-4072-b281-d31b72221d9d","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":50,"name":"Rule 1","query":"host.name:*","references":[],"meta":{"from":"1m","throttle":"no_actions"},"severity":"low","updated_by":"elastic","tags":["rule1"],"to":"now","type":"query","threat":[],"throttle":"no_actions","version":1,"exceptions_list":[]} {"exported_count":1,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index 5a2a950f21bcf..cf5a6aa01f29e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -64,7 +64,8 @@ export const rulesNotificationAlertType = ({ from: fromInMs, to: toInMs, id: ruleAlertSavedObject.id, - kibanaSiemAppUrl: ruleAlertParams.meta?.kibana_siem_app_url, + kibanaSiemAppUrl: (ruleAlertParams.meta as { kibana_siem_app_url?: string }) + .kibana_siem_app_url, }); logger.info( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 23d5d7eb8d385..fe66496f70dcd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -22,34 +22,11 @@ import { IRuleSavedAttributesSavedObjectAttributes, HapiReadableStream, } from '../../rules/types'; -import { RuleAlertParamsRest } from '../../types'; import { requestMock } from './request'; import { RuleNotificationAlertType } from '../../notifications/types'; import { QuerySignalsSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_signals_index_schema'; import { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema'; - -export const typicalPayload = (): Partial => ({ - rule_id: 'rule-1', - description: 'Detecting root and admin users', - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - risk_score: 50, - type: 'query', - from: 'now-6m', - to: 'now', - severity: 'high', - query: 'user.name: root or user.name: admin', - language: 'kuery', - threat: [ - { - framework: 'fake', - tactic: { id: 'fakeId', name: 'fakeName', reference: 'fakeRef' }, - technique: [{ id: 'techniqueId', name: 'techniqueName', reference: 'techniqueRef' }], - }, - ], -}); +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({ signal_ids: ['somefakeid1', 'somefakeid2'], @@ -77,14 +54,14 @@ export const getUpdateRequest = () => requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_URL, - body: typicalPayload(), + body: getCreateRulesSchemaMock(), }); export const getPatchRequest = () => requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: typicalPayload(), + body: getCreateRulesSchemaMock(), }); export const getReadRequest = () => @@ -104,21 +81,21 @@ export const getReadBulkRequest = () => requestMock.create({ method: 'post', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, - body: [typicalPayload()], + body: [getCreateRulesSchemaMock()], }); export const getUpdateBulkRequest = () => requestMock.create({ method: 'put', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [typicalPayload()], + body: [getCreateRulesSchemaMock()], }); export const getPatchBulkRequest = () => requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [typicalPayload()], + body: [getCreateRulesSchemaMock()], }); export const getDeleteBulkRequest = () => @@ -254,11 +231,12 @@ export const getCreateRequest = () => requestMock.create({ method: 'post', path: DETECTION_ENGINE_RULES_URL, - body: typicalPayload(), + body: getCreateRulesSchemaMock(), }); +// TODO: Replace this with the mocks version from the mocks file export const typicalMlRulePayload = () => { - const { query, language, index, ...mlParams } = typicalPayload(); + const { query, language, index, ...mlParams } = getCreateRulesSchemaMock(); return { ...mlParams, @@ -284,8 +262,9 @@ export const createBulkMlRuleRequest = () => { }); }; +// TODO: Replace this with a mocks file version export const createRuleWithActionsRequest = () => { - const payload = typicalPayload(); + const payload = getCreateRulesSchemaMock(); return requestMock.create({ method: 'post', @@ -369,6 +348,7 @@ export const getResult = (): RuleAlertType => ({ falsePositives: [], from: 'now-6m', immutable: false, + savedId: undefined, query: 'user.name: root or user.name: admin', language: 'kuery', machineLearningJobId: undefined, @@ -560,9 +540,9 @@ export const getFindResultStatus = (): SavedObjectsFindResponse< alertId: '1ea5a820-4da1-4e82-92a1-2b43a7bece08', statusDate: '2020-02-18T15:26:49.783Z', status: 'succeeded', - lastFailureAt: null, + lastFailureAt: undefined, lastSuccessAt: '2020-02-18T15:26:49.783Z', - lastFailureMessage: null, + lastFailureMessage: undefined, lastSuccessMessage: 'succeeded', lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(), gap: '500.32', 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 41a28bfe8967d..063c9dffd66dd 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 @@ -6,70 +6,8 @@ import { Readable } from 'stream'; -import { OutputRuleAlertRest } from '../../types'; import { HapiReadableStream } from '../../rules/types'; - -/** - * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId - */ -export const getSimpleRule = (ruleId = 'rule-1'): Partial => ({ - name: 'Simple Rule Query', - description: 'Simple Rule Query', - risk_score: 1, - rule_id: ruleId, - severity: 'high', - type: 'query', - query: 'user.name: root or user.name: admin', -}); - -/** - * This is a typical ML rule for testing - * @param ruleId - */ -export const getSimpleMlRule = (ruleId = 'rule-1'): Partial => ({ - name: 'Simple Rule Query', - description: 'Simple Rule Query', - risk_score: 1, - rule_id: ruleId, - severity: 'high', - type: 'machine_learning', - anomaly_threshold: 44, - machine_learning_job_id: 'some_job_id', -}); - -/** - * This is a typical simple rule for testing that is easy for most basic testing - * @param ruleId - */ -export const getSimpleRuleWithId = (id = 'rule-1'): Partial => ({ - name: 'Simple Rule Query', - description: 'Simple Rule Query', - risk_score: 1, - id, - severity: 'high', - type: 'query', - query: 'user.name: root or user.name: admin', -}); - -/** - * Given an array of rules, builds an NDJSON string of rules - * as we might import/export - * @param rules Array of rule objects with which to generate rule JSON - */ -export const rulesToNdJsonString = (rules: Array>) => { - return rules.map((rule) => JSON.stringify(rule)).join('\r\n'); -}; - -/** - * Given an array of rule IDs, builds an NDJSON string of rules - * as we might import/export - * @param ruleIds Array of ruleIds with which to generate rule JSON - */ -export const ruleIdsToNdJsonString = (ruleIds: string[]) => { - const rules = ruleIds.map((ruleId) => getSimpleRule(ruleId)); - return rulesToNdJsonString(rules); -}; +import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; /** * Given a string, builds a hapi stream as our @@ -94,7 +32,7 @@ export const buildHapiStream = (string: string, filename = 'file.ndjson'): HapiR }; export const getOutputRuleAlertForRest = (): Omit< - OutputRuleAlertRest, + RulesSchema, 'machine_learning_job_id' | 'anomaly_threshold' > => ({ actions: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts index 1df6070bc33a6..11a4543e2fa12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts @@ -8,7 +8,6 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { - typicalPayload, getReadBulkRequest, getEmptyIndex, getNonEmptyIndex, @@ -20,6 +19,7 @@ import { import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { createRulesBulkRoute } from './create_rules_bulk_route'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -98,8 +98,7 @@ describe('create_rules_bulk', () => { expect(response.body).toEqual([ { error: { - message: - 'To create a rule, the index must exist first. Index .siem-signals does not exist', + message: 'To create a rule, the index must exist first. Index undefined does not exist', status_code: 400, }, rule_id: 'rule-1', @@ -143,7 +142,7 @@ describe('create_rules_bulk', () => { const request = requestMock.create({ method: 'post', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, - body: [typicalPayload(), typicalPayload()], + body: [getCreateRulesSchemaMock(), getCreateRulesSchemaMock()], }); const response = await server.inject(request, context); @@ -164,7 +163,7 @@ describe('create_rules_bulk', () => { const request = requestMock.create({ method: 'post', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, - body: [{ ...typicalPayload(), type: 'query' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -175,7 +174,7 @@ describe('create_rules_bulk', () => { const request = requestMock.create({ method: 'post', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, - body: [{ ...typicalPayload(), type: 'unexpected_type' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'unexpected_type' }], }); const result = server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 5abd7b1e76a76..37500572d1386 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -9,7 +9,6 @@ import { getEmptyFindResult, getResult, getCreateRequest, - typicalPayload, getFindResultStatus, getNonEmptyIndex, getEmptyIndex, @@ -22,6 +21,7 @@ import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { createRulesRoute } from './create_rules_route'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../rules/update_rules_notifications'); jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -114,7 +114,7 @@ describe('create_rules', () => { expect(response.status).toEqual(400); expect(response.body).toEqual({ - message: 'To create a rule, the index must exist first. Index .siem-signals does not exist', + message: 'To create a rule, the index must exist first. Index undefined does not exist', status_code: 400, }); }); @@ -149,7 +149,7 @@ describe('create_rules', () => { method: 'post', path: DETECTION_ENGINE_RULES_URL, body: { - ...typicalPayload(), + ...getCreateRulesSchemaMock(), type: 'query', }, }); @@ -163,7 +163,7 @@ describe('create_rules', () => { method: 'post', path: DETECTION_ENGINE_RULES_URL, body: { - ...typicalPayload(), + ...getCreateRulesSchemaMock(), type: 'unexpected_type', }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts index 0762fbc7dd6e3..84148231431a1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - buildHapiStream, - ruleIdsToNdJsonString, - rulesToNdJsonString, - getSimpleRuleWithId, -} from '../__mocks__/utils'; +import { buildHapiStream } from '../__mocks__/utils'; import { getImportRulesRequest, getImportRulesRequestOverwriteTrue, @@ -25,6 +20,11 @@ import { buildMlAuthz } from '../../../machine_learning/authz'; import { importRulesRoute } from './import_rules_route'; import * as createRulesStreamFromNdJson from '../../rules/create_rules_stream_from_ndjson'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { + getImportRulesWithIdSchemaMock, + ruleIdsToNdJsonString, + rulesToNdJsonString, +} from '../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -239,7 +239,11 @@ describe('import_rules_route', () => { }); test('returns 200 with errors if all rules are missing rule_ids and import fails on validation', async () => { - const rulesWithoutRuleIds = ['rule-1', 'rule-2'].map((ruleId) => getSimpleRuleWithId(ruleId)); + const rulesWithoutRuleIds = ['rule-1', 'rule-2'].map((ruleId) => + getImportRulesWithIdSchemaMock(ruleId) + ); + delete rulesWithoutRuleIds[0].rule_id; + delete rulesWithoutRuleIds[1].rule_id; const badPayload = buildHapiStream(rulesToNdJsonString(rulesWithoutRuleIds)); const badRequest = getImportRulesRequest(badPayload); @@ -251,7 +255,7 @@ describe('import_rules_route', () => { { error: { // TODO: Change the formatter to do better than output [object Object] - message: '[object Object],[object Object]', + message: '[object Object]', status_code: 400, }, rule_id: '(unknown id)', @@ -259,7 +263,7 @@ describe('import_rules_route', () => { { error: { // TODO: Change the formatter to do better than output [object Object] - message: '[object Object],[object Object]', + message: '[object Object]', status_code: 400, }, rule_id: '(unknown id)', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index d5145e1dceae0..24fd5e151e485 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -9,7 +9,6 @@ import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../mach import { buildMlAuthz } from '../../../machine_learning/authz'; import { getEmptyFindResult, - typicalPayload, getFindResultWithSingleHit, getPatchBulkRequest, getResult, @@ -18,6 +17,7 @@ import { import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { patchRulesBulkRoute } from './patch_rules_bulk_route'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -153,7 +153,7 @@ describe('patch_rules_bulk', () => { const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), rule_id: undefined }], + body: [{ ...getCreateRulesSchemaMock(), rule_id: undefined }], }); const response = await server.inject(request, context); @@ -173,7 +173,7 @@ describe('patch_rules_bulk', () => { const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), type: 'query' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -184,7 +184,7 @@ describe('patch_rules_bulk', () => { const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), type: 'unknown_type' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'unknown_type' }], }); const result = server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index 09230d45ee785..1b225f6dd93c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -12,7 +12,6 @@ import { getFindResultStatus, getResult, getPatchRequest, - typicalPayload, getFindResultWithSingleHit, nonRuleFindResult, typicalMlRulePayload, @@ -20,6 +19,7 @@ import { import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { patchRulesRoute } from './patch_rules_route'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -165,7 +165,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...typicalPayload(), rule_id: undefined }, + body: { ...getCreateRulesSchemaMock(), rule_id: undefined }, }); const response = await server.inject(request, context); expect(response.body).toEqual({ @@ -178,7 +178,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...typicalPayload(), type: 'query' }, + body: { ...getCreateRulesSchemaMock(), type: 'query' }, }); const result = server.validate(request); @@ -189,7 +189,7 @@ describe('patch_rules', () => { const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, - body: { ...typicalPayload(), type: 'unknown_type' }, + body: { ...getCreateRulesSchemaMock(), type: 'unknown_type' }, }); const result = server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts index 2f331938e3ca8..582bf1a9aa22b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts @@ -10,7 +10,6 @@ import { buildMlAuthz } from '../../../machine_learning/authz'; import { getEmptyFindResult, getResult, - typicalPayload, getFindResultWithSingleHit, getUpdateBulkRequest, getFindResultStatus, @@ -20,6 +19,7 @@ import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { updateRulesBulkRoute } from './update_rules_bulk_route'; import { BulkError } from '../utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -129,7 +129,7 @@ describe('update_rules_bulk', () => { const noIdRequest = requestMock.create({ method: 'put', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), rule_id: undefined }], + body: [{ ...getCreateRulesSchemaMock(), rule_id: undefined }], }); const response = await server.inject(noIdRequest, context); expect(response.body).toEqual([ @@ -144,7 +144,7 @@ describe('update_rules_bulk', () => { const request = requestMock.create({ method: 'put', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), type: 'query' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'query' }], }); const result = server.validate(request); @@ -155,7 +155,7 @@ describe('update_rules_bulk', () => { const request = requestMock.create({ method: 'put', path: `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, - body: [{ ...typicalPayload(), type: 'unknown_type' }], + body: [{ ...getCreateRulesSchemaMock(), type: 'unknown_type' }], }); const result = server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts index f8b7636080b1b..bbe65ff36a3e5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts @@ -10,7 +10,6 @@ import { getEmptyFindResult, getResult, getUpdateRequest, - typicalPayload, getFindResultWithSingleHit, getFindResultStatusEmpty, nonRuleFindResult, @@ -21,6 +20,7 @@ import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; import { updateRulesNotifications } from '../../rules/update_rules_notifications'; import { updateRulesRoute } from './update_rules_route'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); jest.mock('../../rules/update_rules_notifications'); @@ -139,7 +139,7 @@ describe('update_rules', () => { method: 'put', path: DETECTION_ENGINE_RULES_URL, body: { - ...typicalPayload(), + ...getCreateRulesSchemaMock(), rule_id: undefined, }, }); @@ -154,7 +154,7 @@ describe('update_rules', () => { const request = requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_URL, - body: { ...typicalPayload(), type: 'query' }, + body: { ...getCreateRulesSchemaMock(), type: 'query' }, }); const result = await server.validate(request); @@ -165,7 +165,7 @@ describe('update_rules', () => { const request = requestMock.create({ method: 'put', path: DETECTION_ENGINE_RULES_URL, - body: { ...typicalPayload(), type: 'unknown type' }, + body: { ...getCreateRulesSchemaMock(), type: 'unknown type' }, }); const result = await server.validate(request); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 3b2750bbbf664..3b514b92e1479 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -21,7 +21,7 @@ import { getResult } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleTypeParams } from '../../types'; import { BulkError, ImportSuccessError } from '../utils'; -import { getSimpleRule, getOutputRuleAlertForRest } from '../__mocks__/utils'; +import { getOutputRuleAlertForRest } from '../__mocks__/utils'; import { createPromiseFromStreams } from '../../../../../../../../src/legacy/utils/streams'; import { PartialAlert } from '../../../../../../alerts/server'; import { SanitizedAlert } from '../../../../../../alerts/server/types'; @@ -30,6 +30,7 @@ import { RuleAlertType } from '../../rules/types'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../feature_flags'; import { CreateRulesBulkSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock'; type PromiseFromStreams = ImportRulesSchemaDecoded | Error; @@ -62,14 +63,6 @@ describe('utils', () => { expect(rule).toEqual(expectedWithoutFromWithoutLanguage); }); - test('should omit query if query is null', () => { - const fullRule = getResult(); - fullRule.params.query = null; - const rule = transformAlertToRule(fullRule); - const { query, ...expectedWithoutQuery } = getOutputRuleAlertForRest(); - expect(rule).toEqual(expectedWithoutQuery); - }); - test('should omit query if query is undefined', () => { const fullRule = getResult(); fullRule.params.query = undefined; @@ -81,7 +74,7 @@ describe('utils', () => { test('should omit a mix of undefined, null, and missing fields', () => { const fullRule = getResult(); fullRule.params.query = undefined; - fullRule.params.language = null; + fullRule.params.language = undefined; const { from, ...omitParams } = fullRule.params; fullRule.params = omitParams as RuleTypeParams; const { enabled, ...omitEnabled } = fullRule; @@ -532,8 +525,8 @@ describe('utils', () => { }); test('returns tuple of duplicate conflict error and single rule when rules with matching rule-ids passed in and `overwrite` is false', async () => { - const rule = getSimpleRule('rule-1'); - const rule2 = getSimpleRule('rule-1'); + const rule = getCreateRulesSchemaMock('rule-1'); + const rule2 = getCreateRulesSchemaMock('rule-1'); const ndJsonStream = new Readable({ read() { this.push(`${JSON.stringify(rule)}\n`); @@ -561,9 +554,9 @@ describe('utils', () => { }); test('returns tuple of duplicate conflict error and single rule when rules with matching ids passed in and `overwrite` is false', async () => { - const rule = getSimpleRule('rule-1'); + const rule = getCreateRulesSchemaMock('rule-1'); delete rule.rule_id; - const rule2 = getSimpleRule('rule-1'); + const rule2 = getCreateRulesSchemaMock('rule-1'); delete rule2.rule_id; const ndJsonStream = new Readable({ read() { @@ -585,8 +578,8 @@ describe('utils', () => { }); test('returns tuple of empty duplicate errors array and single rule when rules with matching rule-ids passed in and `overwrite` is true', async () => { - const rule = getSimpleRule('rule-1'); - const rule2 = getSimpleRule('rule-1'); + const rule = getCreateRulesSchemaMock('rule-1'); + const rule2 = getCreateRulesSchemaMock('rule-1'); const ndJsonStream = new Readable({ read() { this.push(`${JSON.stringify(rule)}\n`); @@ -606,7 +599,7 @@ describe('utils', () => { }); test('returns tuple of empty duplicate errors array and single rule when rules without a rule-id is passed in', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getCreateRulesSchemaMock(); delete simpleRule.rule_id; const multipartPayload = `${JSON.stringify(simpleRule)}\n`; const ndJsonStream = new Readable({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 3ed45bd8367fc..9320eba26df0b 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 @@ -8,6 +8,7 @@ import { pickBy, countBy } from 'lodash/fp'; import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import uuid from 'uuid'; +import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { CreateRulesBulkSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { PartialAlert, FindResult } from '../../../../../../alerts/server'; @@ -21,7 +22,6 @@ import { isRuleStatusFindTypes, isRuleStatusSavedObjectType, } from '../../rules/types'; -import { OutputRuleAlertRest } from '../../types'; import { createBulkErrorObject, BulkError, @@ -30,7 +30,6 @@ import { createImportErrorObject, OutputError, } from '../utils'; -import { hasListsFeature } from '../../feature_flags'; import { RuleActions } from '../../rule_actions/types'; type PromiseFromStreams = ImportRulesSchemaDecoded | Error; @@ -104,12 +103,12 @@ export const transformAlertToRule = ( alert: RuleAlertType, ruleActions?: RuleActions | null, ruleStatus?: SavedObject -): Partial => { - return pickBy((value: unknown) => value != null, { +): Partial => { + return pickBy((value: unknown) => value != null, { actions: ruleActions?.actions ?? [], created_at: alert.createdAt.toISOString(), updated_at: alert.updatedAt.toISOString(), - created_by: alert.createdBy, + created_by: alert.createdBy ?? 'elastic', description: alert.params.description, enabled: alert.enabled, anomaly_threshold: alert.params.anomalyThreshold, @@ -134,28 +133,25 @@ export const transformAlertToRule = ( timeline_title: alert.params.timelineTitle, meta: alert.params.meta, severity: alert.params.severity, - updated_by: alert.updatedBy, + updated_by: alert.updatedBy ?? 'elastic', tags: transformTags(alert.tags), to: alert.params.to, type: alert.params.type, - threat: alert.params.threat, + threat: alert.params.threat ?? [], throttle: ruleActions?.ruleThrottle || 'no_actions', note: alert.params.note, version: alert.params.version, - status: ruleStatus?.attributes.status, + status: ruleStatus?.attributes.status ?? undefined, status_date: ruleStatus?.attributes.statusDate, - last_failure_at: ruleStatus?.attributes.lastFailureAt, - last_success_at: ruleStatus?.attributes.lastSuccessAt, - last_failure_message: ruleStatus?.attributes.lastFailureMessage, - last_success_message: ruleStatus?.attributes.lastSuccessMessage, - // TODO: (LIST-FEATURE) Remove hasListsFeature() check once we have lists available for a release - exceptions_list: hasListsFeature() ? alert.params.exceptionsList : null, + last_failure_at: ruleStatus?.attributes.lastFailureAt ?? undefined, + last_success_at: ruleStatus?.attributes.lastSuccessAt ?? undefined, + last_failure_message: ruleStatus?.attributes.lastFailureMessage ?? undefined, + last_success_message: ruleStatus?.attributes.lastSuccessMessage ?? undefined, + exceptions_list: alert.params.exceptionsList ?? [], }); }; -export const transformAlertsToRules = ( - alerts: RuleAlertType[] -): Array> => { +export const transformAlertsToRules = (alerts: RuleAlertType[]): Array> => { return alerts.map((alert) => transformAlertToRule(alert)); }; @@ -167,7 +163,7 @@ export const transformFindAlerts = ( page: number; perPage: number; total: number; - data: Array>; + data: Array>; } | null => { if (!ruleStatuses && isAlertTypes(findResults.data)) { return { @@ -194,7 +190,7 @@ export const transform = ( alert: PartialAlert, ruleActions?: RuleActions | null, ruleStatus?: SavedObject -): Partial | null => { +): Partial | null => { if (isAlertType(alert)) { return transformAlertToRule( alert, @@ -211,7 +207,7 @@ export const transformOrBulkError = ( alert: PartialAlert, ruleActions: RuleActions, ruleStatus?: unknown -): Partial | BulkError => { +): Partial | BulkError => { if (isAlertType(alert)) { if (isRuleStatusFindType(ruleStatus) && ruleStatus?.saved_objects.length > 0) { return transformAlertToRule(alert, ruleActions, ruleStatus?.saved_objects[0] ?? ruleStatus); 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 5fc239ed48263..7b0bf5997d12f 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 @@ -22,7 +22,6 @@ import { IRuleSavedAttributesSavedObjectAttributes, isRuleStatusFindType, } from '../../rules/types'; -import { OutputRuleAlertRest } from '../../types'; import { createBulkErrorObject, BulkError } from '../utils'; import { transformFindAlerts, transform, transformAlertToRule } from './utils'; import { RuleActions } from '../../rule_actions/types'; @@ -36,7 +35,7 @@ export const transformValidateFindAlerts = ( page: number; perPage: number; total: number; - data: Array>; + data: Array>; } | null, string | null ] => { 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 1a965842348ac..747cf82510460 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 @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { AlertsClient } from '../../../../../alerts/server'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; import { isAlertType } from '../rules/types'; import { readRules } from './read_rules'; import { transformAlertToRule } from '../routes/rules/utils'; -import { OutputRuleAlertRest } from '../types'; import { transformDataToNdjson } from '../../../utils/read_stream/create_stream_from_ndjson'; -interface ExportSuccesRule { +interface ExportSuccessRule { statusCode: 200; - rule: Partial; + rule: Partial; } interface ExportFailedRule { @@ -22,12 +22,12 @@ interface ExportFailedRule { missingRuleId: { rule_id: string }; } -type ExportRules = ExportSuccesRule | ExportFailedRule; +type ExportRules = ExportSuccessRule | ExportFailedRule; export interface RulesErrors { exportedCount: number; missingRules: Array<{ rule_id: string }>; - rules: Array>; + rules: Array>; } export const getExportByObjectIds = async ( @@ -80,7 +80,7 @@ export const getRulesFromObjects = async ( ) as ExportFailedRule[]; const exportedRules = alertsAndErrors.filter( (resp) => resp.statusCode === 200 - ) as ExportSuccesRule[]; + ) as ExportSuccessRule[]; return { exportedCount: exportedRules.length, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts index 431b3776fd9e2..848abc3febf35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts @@ -4,18 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sampleRule } from '../signals/__mocks__/es_results'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; +import { getRulesSchemaMock } from '../../../../common/detection_engine/schemas/response/rules_schema.mocks'; describe('getExportDetailsNdjson', () => { test('it ends with a new line character', () => { - const rule = sampleRule(); + const rule = getRulesSchemaMock(); const details = getExportDetailsNdjson([rule]); expect(details.endsWith('\n')).toEqual(true); }); test('it exports a correct count given a single rule and no missing rules', () => { - const rule = sampleRule(); + const rule = getRulesSchemaMock(); const details = getExportDetailsNdjson([rule]); const reParsed = JSON.parse(details); expect(reParsed).toEqual({ @@ -37,8 +37,8 @@ describe('getExportDetailsNdjson', () => { }); test('it exports a correct count given multiple rules and multiple missing rules', () => { - const rule1 = sampleRule(); - const rule2 = sampleRule(); + const rule1 = getRulesSchemaMock(); + const rule2 = getRulesSchemaMock(); rule2.rule_id = 'some other id'; rule2.id = 'some other id'; 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 a39541d044bc3..0a685cab9b450 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 @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { OutputRuleAlertRest } from '../types'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; export const getExportDetailsNdjson = ( - rules: Array>, + rules: Array>, missingRules: Array<{ rule_id: string }> = [] ): string => { const stringified = JSON.stringify({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 3796df353abb8..0c103b7176db4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -11,7 +11,6 @@ import { PatchRulesOptions } from './types'; import { addTags } from './add_tags'; import { calculateVersion, calculateName, calculateInterval } from './utils'; import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client'; -import { Meta } from '../types'; export const patchRules = async ({ alertsClient, @@ -60,7 +59,7 @@ export const patchRules = async ({ savedId, timelineId, timelineTitle, - meta: meta as Meta | undefined, // TODO: Remove this cast once we fix the types for calculate version and patch, + meta, filters, from, index, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts index 8bdb1287c6df2..4b84057f6d795 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts @@ -69,11 +69,17 @@ import { QueryFilterOrUndefined, FieldsOrUndefined, SortOrderOrUndefined, + JobStatus, + LastSuccessAt, + StatusDate, + LastSuccessMessage, + LastFailureAt, + LastFailureMessage, } from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertsClient, PartialAlert } from '../../../../../alerts/server'; import { Alert, SanitizedAlert } from '../../../../../alerts/common'; import { SIGNALS_ID } from '../../../../common/constants'; -import { RuleAlertParams, RuleTypeParams, PartialFilter } from '../types'; +import { RuleTypeParams, PartialFilter } from '../types'; export interface RuleAlertType extends Alert { params: RuleTypeParams; @@ -82,12 +88,12 @@ export interface RuleAlertType extends Alert { // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface IRuleStatusAttributes extends Record { alertId: string; // created alert id. - statusDate: string; - lastFailureAt: string | null | undefined; - lastFailureMessage: string | null | undefined; - lastSuccessAt: string | null | undefined; - lastSuccessMessage: string | null | undefined; - status: RuleStatusString | null | undefined; + statusDate: StatusDate; + lastFailureAt: LastFailureAt | null | undefined; + lastFailureMessage: LastFailureMessage | null | undefined; + lastSuccessAt: LastSuccessAt | null | undefined; + lastSuccessMessage: LastSuccessMessage | null | undefined; + status: JobStatus | null | undefined; lastLookBackDate: string | null | undefined; gap: string | null | undefined; bulkCreateTimeDurations: string[] | null | undefined; @@ -121,8 +127,6 @@ export interface IRuleStatusFindType { saved_objects: IRuleStatusSavedObject[]; } -export type RuleStatusString = 'succeeded' | 'failed' | 'going to run'; - export interface HapiReadableStream extends Readable { hapi: { filename: string; @@ -133,12 +137,6 @@ export interface Clients { alertsClient: AlertsClient; } -// TODO: Try and remove this patch -export type PatchRuleParams = Partial> & { - rule: SanitizedAlert | null; - savedObjectsClient: SavedObjectsClientContract; -} & Clients; - export const isAlertTypes = (partialAlert: PartialAlert[]): partialAlert is RuleAlertType[] => { return partialAlert.every((rule) => isAlertType(rule)); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts index 0236c357988d5..b3f327857dbb3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts @@ -12,7 +12,6 @@ import { addTags } from './add_tags'; import { calculateVersion } from './utils'; import { hasListsFeature } from '../feature_flags'; import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client'; -import { Meta } from '../types'; export const updateRules = async ({ alertsClient, @@ -63,7 +62,7 @@ export const updateRules = async ({ savedId, timelineId, timelineTitle, - meta: meta as Meta, // TODO: Remove this cast once we fix the types for calculate version and patch + meta, filters, from, index, @@ -81,6 +80,7 @@ export const updateRules = async ({ note, anomalyThreshold, machineLearningJobId, + exceptionsList, }); // TODO: Remove this and use regular exceptions_list once the feature is stable for a release diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index b7c36b20f44be..0f65b2a78ec4c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -5,7 +5,6 @@ */ import { calculateInterval, calculateVersion, calculateName } from './utils'; -import { PatchRuleParams } from './types'; describe('utils', () => { describe('#calculateInterval', () => { @@ -27,24 +26,104 @@ describe('utils', () => { describe('#calculateVersion', () => { test('returning the same version number if given an immutable but no updated version number', () => { - expect(calculateVersion(true, 1, { description: 'some description change' })).toEqual(1); + expect( + calculateVersion(true, 1, { + description: 'some description change', + falsePositives: undefined, + query: undefined, + language: undefined, + outputIndex: undefined, + savedId: undefined, + timelineId: undefined, + timelineTitle: undefined, + meta: undefined, + filters: [], + from: undefined, + index: undefined, + interval: undefined, + maxSignals: undefined, + riskScore: undefined, + name: undefined, + severity: undefined, + tags: undefined, + threat: undefined, + to: undefined, + type: undefined, + references: undefined, + version: undefined, + note: undefined, + anomalyThreshold: undefined, + machineLearningJobId: undefined, + exceptionsList: [], + }) + ).toEqual(1); }); test('returning an updated version number if given an immutable and an updated version number', () => { - expect(calculateVersion(true, 2, { description: 'some description change' })).toEqual(2); + expect( + calculateVersion(true, 2, { + description: 'some description change', + falsePositives: undefined, + query: undefined, + language: undefined, + outputIndex: undefined, + savedId: undefined, + timelineId: undefined, + timelineTitle: undefined, + meta: undefined, + filters: [], + from: undefined, + index: undefined, + interval: undefined, + maxSignals: undefined, + riskScore: undefined, + name: undefined, + severity: undefined, + tags: undefined, + threat: undefined, + to: undefined, + type: undefined, + references: undefined, + version: undefined, + note: undefined, + anomalyThreshold: undefined, + machineLearningJobId: undefined, + exceptionsList: [], + }) + ).toEqual(2); }); test('returning an updated version number if not given an immutable but but an updated description', () => { - expect(calculateVersion(false, 1, { description: 'some description change' })).toEqual(2); - }); - - test('returning the same version number but a undefined description', () => { - expect(calculateVersion(false, 1, { description: undefined })).toEqual(1); - }); - - test('returning an updated version number if not given an immutable but an updated falsy value', () => { expect( - calculateVersion(false, 1, ({ description: false } as unknown) as PatchRuleParams) + calculateVersion(false, 1, { + description: 'some description change', + falsePositives: undefined, + query: undefined, + language: undefined, + outputIndex: undefined, + savedId: undefined, + timelineId: undefined, + timelineTitle: undefined, + meta: undefined, + filters: [], + from: undefined, + index: undefined, + interval: undefined, + maxSignals: undefined, + riskScore: undefined, + name: undefined, + severity: undefined, + tags: undefined, + threat: undefined, + to: undefined, + type: undefined, + references: undefined, + version: undefined, + note: undefined, + anomalyThreshold: undefined, + machineLearningJobId: undefined, + exceptionsList: [], + }) ).toEqual(2); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts index 7d6091f6b97fa..d40cb5d96669b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts @@ -5,7 +5,35 @@ */ import { pickBy, isEmpty } from 'lodash/fp'; -import { PatchRuleParams } from './types'; +import { + DescriptionOrUndefined, + AnomalyThresholdOrUndefined, + QueryOrUndefined, + LanguageOrUndefined, + SavedIdOrUndefined, + TimelineIdOrUndefined, + TimelineTitleOrUndefined, + MachineLearningJobIdOrUndefined, + IndexOrUndefined, + NoteOrUndefined, + MetaOrUndefined, + VersionOrUndefined, + FalsePositivesOrUndefined, + FromOrUndefined, + OutputIndexOrUndefined, + IntervalOrUndefined, + MaxSignalsOrUndefined, + RiskScoreOrUndefined, + NameOrUndefined, + SeverityOrUndefined, + TagsOrUndefined, + ToOrUndefined, + ThreatOrUndefined, + TypeOrUndefined, + ReferencesOrUndefined, + ListAndOrUndefined, +} from '../../../../common/detection_engine/schemas/common/schemas'; +import { PartialFilter } from '../types'; export const calculateInterval = ( interval: string | undefined, @@ -20,10 +48,40 @@ export const calculateInterval = ( } }; +export interface UpdateProperties { + description: DescriptionOrUndefined; + falsePositives: FalsePositivesOrUndefined; + from: FromOrUndefined; + query: QueryOrUndefined; + language: LanguageOrUndefined; + savedId: SavedIdOrUndefined; + timelineId: TimelineIdOrUndefined; + timelineTitle: TimelineTitleOrUndefined; + meta: MetaOrUndefined; + machineLearningJobId: MachineLearningJobIdOrUndefined; + filters: PartialFilter[]; + index: IndexOrUndefined; + interval: IntervalOrUndefined; + maxSignals: MaxSignalsOrUndefined; + riskScore: RiskScoreOrUndefined; + outputIndex: OutputIndexOrUndefined; + name: NameOrUndefined; + severity: SeverityOrUndefined; + tags: TagsOrUndefined; + threat: ThreatOrUndefined; + to: ToOrUndefined; + type: TypeOrUndefined; + references: ReferencesOrUndefined; + note: NoteOrUndefined; + version: VersionOrUndefined; + exceptionsList: ListAndOrUndefined; + anomalyThreshold: AnomalyThresholdOrUndefined; +} + export const calculateVersion = ( immutable: boolean, currentVersion: number, - updateProperties: Partial> + updateProperties: UpdateProperties ): number => { // early return if we are pre-packaged/immutable rule to be safe. We are never responsible // for changing the version number of an immutable. Immutables are only responsible for changing @@ -44,7 +102,7 @@ export const calculateVersion = ( // the version number if only the enabled/disabled flag is being set. Likewise if we get other // properties we are not expecting such as updatedAt we do not to cause a version number bump // on that either. - const removedNullValues = pickBy( + const removedNullValues = pickBy( (value: unknown) => value != null, updateProperties ); 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 46a16e7dca153..6056e692854af 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 @@ -11,7 +11,7 @@ import { SavedObjectsFindResponse, } from '../../../../../../../../src/core/server'; import { loggingServiceMock } from '../../../../../../../../src/core/server/mocks'; -import { RuleTypeParams, OutputRuleAlertRest } from '../../types'; +import { RuleTypeParams } from '../../types'; import { IRuleStatusAttributes } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; @@ -364,34 +364,6 @@ export const sampleDocSearchResultsWithSortId = ( export const sampleRuleGuid = '04128c15-0d1b-4716-a4c5-46997ac7f3bd'; export const sampleIdGuid = 'e1e08ddc-5e37-49ff-a258-5393aa44435a'; -export const sampleRule = (): Partial => { - return { - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - false_positives: [], - from: 'now-6m', - id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', - immutable: false, - index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], - interval: '5m', - risk_score: 50, - rule_id: 'rule-1', - language: 'kuery', - max_signals: 100, - name: 'Detect Root/Admin Users', - output_index: '.siem-signals', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com', 'https://ww.example.com'], - severity: 'high', - updated_by: 'elastic', - tags: ['some fake tag 1', 'some fake tag 2'], - to: 'now', - type: 'query', - note: '', - }; -}; - export const exampleRuleStatus: () => SavedObject = () => ({ type: ruleStatusSavedObjectType, id: '042e6d90-7069-11ea-af8b-0f8ae4fa817e', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts index df9d282b71e5e..80c2441193a0c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -18,7 +18,7 @@ describe('buildBulkBody', () => { jest.clearAllMocks(); }); - test('if bulk body builds well-defined body', () => { + test('bulk body builds well-defined body', () => { const sampleParams = sampleRuleAlertParams(); const fakeSignalSourceHit = buildBulkBody({ doc: sampleDocNoSortId(), @@ -80,6 +80,7 @@ describe('buildBulkBody', () => { references: ['http://google.com'], severity: 'high', tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], throttle: 'no_actions', type: 'query', to: 'now', @@ -128,7 +129,7 @@ describe('buildBulkBody', () => { expect(fakeSignalSourceHit).toEqual(expected); }); - test('if bulk body builds original_event if it exists on the event to begin with', () => { + test('bulk body builds original_event if it exists on the event to begin with', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); doc._source.event = { @@ -216,6 +217,7 @@ describe('buildBulkBody', () => { created_at: fakeSignalSourceHit.signal.rule?.created_at, updated_at: fakeSignalSourceHit.signal.rule?.updated_at, throttle: 'no_actions', + threat: [], exceptions_list: [ { field: 'source.ip', @@ -254,7 +256,7 @@ describe('buildBulkBody', () => { expect(fakeSignalSourceHit).toEqual(expected); }); - test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { + test('bulk body builds original_event if it exists on the event to begin with but no kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); doc._source.event = { @@ -329,6 +331,7 @@ describe('buildBulkBody', () => { query: 'user.name: root or user.name: admin', references: ['http://google.com'], severity: 'high', + threat: [], tags: ['some fake tag 1', 'some fake tag 2'], type: 'query', to: 'now', @@ -378,7 +381,7 @@ describe('buildBulkBody', () => { expect(fakeSignalSourceHit).toEqual(expected); }); - test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { + test('bulk body builds original_event if it exists on the event to begin with with only kind information', () => { const sampleParams = sampleRuleAlertParams(); const doc = sampleDocNoSortId(); doc._source.event = { @@ -447,6 +450,7 @@ describe('buildBulkBody', () => { references: ['http://google.com'], severity: 'high', tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], type: 'query', to: 'now', note: '', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.test.ts index 772ebd932698b..07adfde71c1a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.test.ts @@ -1147,17 +1147,6 @@ describe('build_exceptions_query', () => { }); describe('buildQueryExceptions', () => { - test('it returns original query if no lists exist', () => { - const query = buildQueryExceptions({ - query: 'host.name: *', - language: 'kuery', - lists: undefined, - }); - const expectedQuery = 'host.name: *'; - - expect(query).toEqual([{ query: expectedQuery, language: 'kuery' }]); - }); - test('it returns original query if lists is empty array', () => { const query = buildQueryExceptions({ query: 'host.name: *', language: 'kuery', lists: [] }); const expectedQuery = 'host.name: *'; @@ -1165,24 +1154,6 @@ describe('build_exceptions_query', () => { expect(query).toEqual([{ query: expectedQuery, language: 'kuery' }]); }); - test('it returns original query if lists is null', () => { - const query = buildQueryExceptions({ query: 'host.name: *', language: 'kuery', lists: null }); - const expectedQuery = 'host.name: *'; - - expect(query).toEqual([{ query: expectedQuery, language: 'kuery' }]); - }); - - test('it returns original query if lists is undefined', () => { - const query = buildQueryExceptions({ - query: 'host.name: *', - language: 'kuery', - lists: undefined, - }); - const expectedQuery = 'host.name: *'; - - expect(query).toEqual([{ query: expectedQuery, language: 'kuery' }]); - }); - test('it returns expected query when lists exist and language is "kuery"', () => { // Equal to query && !((b || !c || d) && e) -> ((query AND NOT b AND c AND NOT d) OR (query AND NOT e) // https://www.dcode.fr/boolean-expressions-calculator diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.ts index d4efd9a2e8a1a..233b20792299b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_exceptions_query.ts @@ -3,13 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { + ListAndOrUndefined, + Language, + Query, +} from '../../../../common/detection_engine/schemas/common/schemas'; import { ListOperator, ListValues, List, } from '../../../../common/detection_engine/schemas/types/lists_default_array'; -import { Query } from '../../../../../../../src/plugins/data/server'; -import { RuleAlertParams, Language } from '../types'; +import { Query as DataQuery } from '../../../../../../../src/plugins/data/server'; type Operators = 'and' | 'or' | 'not'; type LuceneOperators = 'AND' | 'OR' | 'NOT'; @@ -187,10 +191,10 @@ export const buildQueryExceptions = ({ language, lists, }: { - query: string; + query: Query; language: Language; - lists: RuleAlertParams['exceptionsList']; -}): Query[] => { + lists: ListAndOrUndefined; +}): DataQuery[] => { if (lists && lists !== null) { const exceptions = buildExceptions({ lists, language, query }); const formattedQuery = formatQuery({ exceptions, language, query }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts index b3586c884d0c7..eb87976a6fbab 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.test.ts @@ -6,7 +6,7 @@ import { buildRule } from './build_rule'; import { sampleRuleAlertParams, sampleRuleGuid } from './__mocks__/es_results'; -import { OutputRuleAlertRest } from '../types'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; describe('buildRule', () => { beforeEach(() => { @@ -40,7 +40,7 @@ describe('buildRule', () => { tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); - const expected: Partial = { + const expected: Partial = { actions: [], created_by: 'elastic', description: 'Detecting root and admin users', @@ -61,6 +61,7 @@ describe('buildRule', () => { rule_id: 'rule-1', severity: 'high', tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], to: 'now', type: 'query', note: '', @@ -133,7 +134,7 @@ describe('buildRule', () => { tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); - const expected: Partial = { + const expected: Partial = { actions: [], created_by: 'elastic', description: 'Detecting root and admin users', @@ -154,6 +155,7 @@ describe('buildRule', () => { rule_id: 'rule-1', severity: 'high', tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], to: 'now', type: 'query', note: '', @@ -215,7 +217,7 @@ describe('buildRule', () => { tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); - const expected: Partial = { + const expected: Partial = { actions: [], created_by: 'elastic', description: 'Detecting root and admin users', @@ -237,6 +239,7 @@ describe('buildRule', () => { rule_id: 'rule-1', severity: 'high', tags: ['some fake tag 1', 'some fake tag 2'], + threat: [], to: 'now', type: 'query', updated_by: 'elastic', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts index de8de1bc513e3..bde9c970b0c8c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_rule.ts @@ -5,8 +5,9 @@ */ import { pickBy } from 'lodash/fp'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams, OutputRuleAlertRest } from '../types'; +import { RuleTypeParams } from '../types'; interface BuildRuleParams { ruleParams: RuleTypeParams; @@ -36,10 +37,10 @@ export const buildRule = ({ interval, tags, throttle, -}: BuildRuleParams): Partial => { - return pickBy((value: unknown) => value != null, { +}: BuildRuleParams): Partial => { + return pickBy((value: unknown) => value != null, { id, - rule_id: ruleParams.ruleId, + rule_id: ruleParams.ruleId ?? '(unknown rule_id)', actions, false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, @@ -67,12 +68,12 @@ export const buildRule = ({ filters: ruleParams.filters, created_by: createdBy, updated_by: updatedBy, - threat: ruleParams.threat, + threat: ruleParams.threat ?? [], throttle, version: ruleParams.version, created_at: createdAt, updated_at: updatedAt, - exceptions_list: ruleParams.exceptionsList, + exceptions_list: ruleParams.exceptionsList ?? [], machine_learning_job_id: ruleParams.machineLearningJobId, anomaly_threshold: ruleParams.anomalyThreshold, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts index f3f4ab60e4db6..6aebf8815659a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sampleDocNoSortId, sampleRule } from './__mocks__/es_results'; +import { sampleDocNoSortId } from './__mocks__/es_results'; import { buildSignal, buildAncestor, @@ -13,6 +13,7 @@ import { } from './build_signal'; import { Signal, Ancestor } from './types'; import { INTERNAL_RULE_ID_KEY, INTERNAL_IMMUTABLE_KEY } from '../../../../common/constants'; +import { getPartialRulesSchemaMock } from '../../../../common/detection_engine/schemas/response/rules_schema.mocks'; describe('buildSignal', () => { beforeEach(() => { @@ -22,7 +23,7 @@ describe('buildSignal', () => { test('it builds a signal as expected without original_event if event does not exist', () => { const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); delete doc._source.event; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { @@ -82,7 +83,7 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildSignal(doc, rule); const expected: Signal = { parent: { @@ -148,7 +149,7 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); rule.tags = [ 'some fake tag 1', 'some fake tag 2', @@ -220,7 +221,7 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildAncestor(doc, rule); const expected: Ancestor = { rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', @@ -258,7 +259,7 @@ describe('buildSignal', () => { }, ], }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildAncestor(doc, rule); const expected: Ancestor = { rule: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', @@ -296,7 +297,7 @@ describe('buildSignal', () => { }, ], }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); rule.tags = [ 'some fake tag 1', 'some fake tag 2', @@ -323,7 +324,7 @@ describe('buildSignal', () => { kind: 'event', module: 'system', }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildAncestorsSignal(doc, rule); const expected: Ancestor[] = [ { @@ -363,7 +364,7 @@ describe('buildSignal', () => { }, ], }; - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const signal = buildAncestorsSignal(doc, rule); const expected: Ancestor[] = [ { @@ -385,7 +386,7 @@ describe('buildSignal', () => { }); test('it removes internal tags from a typical rule', () => { - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); rule.tags = [ 'some fake tag 1', 'some fake tag 2', @@ -393,29 +394,29 @@ describe('buildSignal', () => { `${INTERNAL_IMMUTABLE_KEY}:true`, ]; const noInternals = removeInternalTagsFromRule(rule); - expect(noInternals).toEqual(sampleRule()); + expect(noInternals).toEqual(getPartialRulesSchemaMock()); }); test('it works with an empty array', () => { - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); rule.tags = []; const noInternals = removeInternalTagsFromRule(rule); - const expected = sampleRule(); + const expected = getPartialRulesSchemaMock(); expected.tags = []; expect(noInternals).toEqual(expected); }); test('it works if tags does not exist', () => { - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); delete rule.tags; const noInternals = removeInternalTagsFromRule(rule); - const expected = sampleRule(); + const expected = getPartialRulesSchemaMock(); delete expected.tags; expect(noInternals).toEqual(expected); }); test('it works if tags contains normal values and no internal values', () => { - const rule = sampleRule(); + const rule = getPartialRulesSchemaMock(); const noInternals = removeInternalTagsFromRule(rule); expect(noInternals).toEqual(rule); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts index a0551d75d2750..77a63c63ff97a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_signal.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { INTERNAL_IDENTIFIER } from '../../../../common/constants'; import { SignalSourceHit, Signal, Ancestor } from './types'; -import { OutputRuleAlertRest } from '../types'; -export const buildAncestor = ( - doc: SignalSourceHit, - rule: Partial -): Ancestor => { +export const buildAncestor = (doc: SignalSourceHit, rule: Partial): Ancestor => { const existingSignal = doc._source.signal?.parent; if (existingSignal != null) { return { @@ -34,7 +31,7 @@ export const buildAncestor = ( export const buildAncestorsSignal = ( doc: SignalSourceHit, - rule: Partial + rule: Partial ): Signal['ancestors'] => { const newAncestor = buildAncestor(doc, rule); const existingAncestors = doc._source.signal?.ancestors; @@ -45,7 +42,7 @@ export const buildAncestorsSignal = ( } }; -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { const ruleWithoutInternalTags = removeInternalTagsFromRule(rule); const parent = buildAncestor(doc, rule); const ancestors = buildAncestorsSignal(doc, rule); @@ -62,13 +59,11 @@ export const buildSignal = (doc: SignalSourceHit, rule: Partial -): Partial => { +export const removeInternalTagsFromRule = (rule: Partial): Partial => { if (rule.tags == null) { return rule; } else { - const ruleWithoutInternalTags: Partial = { + const ruleWithoutInternalTags: Partial = { ...rule, tags: rule.tags.filter((tag) => !tag.startsWith(INTERNAL_IDENTIFIER)), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.test.ts index d56e167f59e4c..4e9eb8587484f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.test.ts @@ -21,11 +21,11 @@ describe('filterEventsAgainstList', () => { listClient.getListItemByValues = jest.fn().mockResolvedValue([]); }); - it('should respond with eventSearchResult if exceptionList is empty', async () => { + it('should respond with eventSearchResult if exceptionList is empty array', async () => { const res = await filterEventsAgainstList({ logger: mockLogger, listClient, - exceptionsList: undefined, + exceptionsList: [], eventSearchResult: repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3), [ '1.1.1.1', '2.2.2.2', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.ts index 29b8b54d162df..48b120d1b5806 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_events_with_list.ts @@ -6,15 +6,15 @@ import { get } from 'lodash/fp'; import { Logger } from 'src/core/server'; +import { ListAndOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; import { List } from '../../../../common/detection_engine/schemas/types/lists_default_array'; import { type } from '../../../../../lists/common/schemas/common'; import { ListClient } from '../../../../../lists/server'; import { SignalSearchResponse, SearchTypes } from './types'; -import { RuleAlertParams } from '../types'; interface FilterEventsAgainstList { listClient: ListClient; - exceptionsList: RuleAlertParams['exceptionsList']; + exceptionsList: ListAndOrUndefined; logger: Logger; eventSearchResult: SignalSearchResponse; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts index 0930fbdb534f5..b1500e47db843 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.test.ts @@ -449,20 +449,6 @@ describe('get_filter', () => { }); }); - test('it should work when lists has value null', () => { - const esQuery = getQueryFilter('host.name: linux', 'kuery', [], ['auditbeat-*'], null); - expect(esQuery).toEqual({ - bool: { - filter: [ - { bool: { minimum_should_match: 1, should: [{ match: { 'host.name': 'linux' } }] } }, - ], - must: [], - must_not: [], - should: [], - }, - }); - }); - test('it should work when lists has value undefined', () => { const esQuery = getQueryFilter('host.name: linux', 'kuery', [], ['auditbeat-*'], undefined); expect(esQuery).toEqual({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts index c464238715afd..3e9f79c67d8ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts @@ -4,32 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + LanguageOrUndefined, + QueryOrUndefined, + Type, + SavedIdOrUndefined, + IndexOrUndefined, + ListAndOrUndefined, + Language, + Index, + Query, +} from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertServices } from '../../../../../alerts/server'; import { assertUnreachable } from '../../../utils/build_query'; import { Filter, - Query, + Query as DataQuery, esQuery, esFilters, IIndexPattern, } from '../../../../../../../src/plugins/data/server'; -import { PartialFilter, RuleAlertParams, Language } from '../types'; +import { PartialFilter } from '../types'; import { BadRequestError } from '../errors/bad_request_error'; import { buildQueryExceptions } from './build_exceptions_query'; export const getQueryFilter = ( - query: string, + query: Query, language: Language, filters: PartialFilter[], - index: string[], - lists: RuleAlertParams['exceptionsList'] + index: Index, + lists: ListAndOrUndefined ) => { const indexPattern = { fields: [], title: index.join(), } as IIndexPattern; - const queries: Query[] = buildQueryExceptions({ query, language, lists }); + const queries: DataQuery[] = buildQueryExceptions({ query, language, lists }); const config = { allowLeadingWildcards: true, @@ -46,14 +57,14 @@ export const getQueryFilter = ( }; interface GetFilterArgs { - type: RuleAlertParams['type']; - filters: PartialFilter[] | undefined | null; - language: Language | undefined | null; - query: string | undefined | null; - savedId: string | undefined | null; + type: Type; + filters: PartialFilter[] | undefined; + language: LanguageOrUndefined; + query: QueryOrUndefined; + savedId: SavedIdOrUndefined; services: AlertServices; - index: string[] | undefined | null; - lists: RuleAlertParams['exceptionsList']; + index: IndexOrUndefined; + lists: ListAndOrUndefined; } interface QueryAttributes { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts index 9430dfe7bfe23..0f4b8d1472b3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { JobStatus } from '../../../../common/detection_engine/schemas/common/schemas'; import { assertUnreachable } from '../../../utils/build_query'; -import { IRuleStatusAttributes, RuleStatusString } from '../rules/types'; +import { IRuleStatusAttributes } from '../rules/types'; import { getOrCreateRuleStatuses } from './get_or_create_rule_statuses'; import { RuleStatusSavedObjectsClient } from './rule_status_saved_objects_client'; @@ -26,7 +27,7 @@ export interface RuleStatusService { } export const buildRuleStatusAttributes: ( - status: RuleStatusString, + status: JobStatus, message?: string, attributes?: Attributes ) => Partial = (status, message, attributes = {}) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index b7bea906475db..42893a199a8f1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ListAndOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; import { AlertServices } from '../../../../../alerts/server'; import { ListClient } from '../../../../../lists/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams, RefreshTypes, RuleAlertParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { Logger } from '../../../../../../../src/core/server'; import { singleSearchAfter } from './single_search_after'; import { singleBulkCreate } from './single_bulk_create'; @@ -18,7 +19,7 @@ interface SearchAfterAndBulkCreateParams { ruleParams: RuleTypeParams; services: AlertServices; listClient: ListClient | undefined; // TODO: undefined is for temporary development, remove before merged - exceptionsList: RuleAlertParams['exceptionsList']; + exceptionsList: ListAndOrUndefined; logger: Logger; id: string; inputIndexPattern: string[]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts index d42ba8fe57005..461b2589babcc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_params_schema.ts @@ -4,40 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; +const signalSchema = schema.object({ + anomalyThreshold: schema.maybe(schema.number()), + description: schema.string(), + note: schema.nullable(schema.string()), + falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), + from: schema.string(), + ruleId: schema.string(), + immutable: schema.boolean({ defaultValue: false }), + index: schema.nullable(schema.arrayOf(schema.string())), + language: schema.nullable(schema.string()), + outputIndex: schema.nullable(schema.string()), + savedId: schema.nullable(schema.string()), + timelineId: schema.nullable(schema.string()), + timelineTitle: schema.nullable(schema.string()), + meta: schema.nullable(schema.object({}, { unknowns: 'allow' })), + machineLearningJobId: schema.maybe(schema.string()), + query: schema.nullable(schema.string()), + filters: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), + riskScore: schema.number(), + severity: schema.string(), + threat: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), + to: schema.string(), + type: schema.string(), + references: schema.arrayOf(schema.string(), { defaultValue: [] }), + version: schema.number({ defaultValue: 1 }), + exceptionsList: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), +}); + /** * This is the schema for the Alert Rule that represents the SIEM alert for signals * that index into the .siem-signals-${space-id} */ -export const signalParamsSchema = () => - schema.object({ - anomalyThreshold: schema.maybe(schema.number()), - description: schema.string(), - note: schema.nullable(schema.string()), - falsePositives: schema.arrayOf(schema.string(), { defaultValue: [] }), - from: schema.string(), - ruleId: schema.string(), - immutable: schema.boolean({ defaultValue: false }), - index: schema.nullable(schema.arrayOf(schema.string())), - language: schema.nullable(schema.string()), - outputIndex: schema.nullable(schema.string()), - savedId: schema.nullable(schema.string()), - timelineId: schema.nullable(schema.string()), - timelineTitle: schema.nullable(schema.string()), - meta: schema.nullable(schema.object({}, { unknowns: 'allow' })), - machineLearningJobId: schema.maybe(schema.string()), - query: schema.nullable(schema.string()), - filters: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), - maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }), - riskScore: schema.number(), - severity: schema.string(), - threat: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), - to: schema.string(), - type: schema.string(), - references: schema.arrayOf(schema.string(), { defaultValue: [] }), - version: schema.number({ defaultValue: 1 }), - exceptionsList: schema.maybe(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))), - }); +export const signalParamsSchema = () => signalSchema; + +export type SignalParamsSchema = TypeOf; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index f94eb7006829e..a2dc33ba1c2bf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -39,8 +39,6 @@ const getPayload = (ruleAlert: RuleAlertType, services: AlertServicesMock) => ({ name: ruleAlert.name, tags: ruleAlert.tags, throttle: ruleAlert.throttle, - scrollSize: 10, - scrollLock: '0', }, state: {}, spaceId: '', @@ -105,7 +103,7 @@ describe('rules_notification_alert_type', () => { attributes: ruleAlert, }); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; alert = signalRulesAlertType({ logger, @@ -196,7 +194,7 @@ describe('rules_notification_alert_type', () => { describe('ML rule', () => { it('should throw an error if ML plugin was not available', async () => { const ruleAlert = getMlResult(); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; alert = signalRulesAlertType({ logger, version, @@ -213,7 +211,7 @@ describe('rules_notification_alert_type', () => { it('should throw an error if machineLearningJobId or anomalyThreshold was not null', async () => { const ruleAlert = getMlResult(); ruleAlert.params.anomalyThreshold = undefined; - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; await alert.executor(payload); expect(logger.error).toHaveBeenCalled(); expect(logger.error.mock.calls[0][0]).toContain( @@ -223,7 +221,7 @@ describe('rules_notification_alert_type', () => { it('should throw an error if Machine learning job summary was null', async () => { const ruleAlert = getMlResult(); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; jobsSummaryMock.mockResolvedValue([]); await alert.executor(payload); expect(logger.warn).toHaveBeenCalled(); @@ -236,7 +234,7 @@ describe('rules_notification_alert_type', () => { it('should log an error if Machine learning job was not started', async () => { const ruleAlert = getMlResult(); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; jobsSummaryMock.mockResolvedValue([ { id: 'some_job_id', @@ -260,7 +258,7 @@ describe('rules_notification_alert_type', () => { it('should not call ruleStatusService.success if no anomalies were found', async () => { const ruleAlert = getMlResult(); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; jobsSummaryMock.mockResolvedValue([]); (findMlSignals as jest.Mock).mockResolvedValue({ hits: { @@ -278,7 +276,7 @@ describe('rules_notification_alert_type', () => { it('should call ruleStatusService.success if signals were created', async () => { const ruleAlert = getMlResult(); - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; jobsSummaryMock.mockResolvedValue([ { id: 'some_job_id', @@ -313,7 +311,7 @@ describe('rules_notification_alert_type', () => { id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', }, ]; - payload = getPayload(ruleAlert, alertServices); + payload = getPayload(ruleAlert, alertServices) as jest.Mocked; alertServices.savedObjectsClient.get.mockResolvedValue({ id: 'id', type: 'type', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 567274be6a9f8..091ee54e3174e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -275,7 +275,7 @@ export const signalRulesAlertType = ({ from: fromInMs, to: toInMs, id: savedObject.id, - kibanaSiemAppUrl: meta?.kibana_siem_app_url, + kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string }).kibana_siem_app_url, }); logger.info( 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 869c81f640561..082211df28320 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 @@ -4,21 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; +import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { AlertType, State, AlertExecutorOptions } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleAlertParams, OutputRuleAlertRest } from '../types'; +import { RuleTypeParams } from '../types'; import { SearchResponse } from '../../types'; export interface SignalsParams { signalIds: string[] | undefined | null; query: object | undefined | null; - status: 'open' | 'closed'; + status: Status; } export interface SignalsStatusParams { signalIds: string[] | undefined | null; query: object | undefined | null; - status: 'open' | 'closed'; + status: Status; } export type SearchTypes = @@ -91,10 +93,7 @@ export type SignalSearchResponse = SearchResponse; export type SignalSourceHit = SignalSearchResponse['hits']['hits'][number]; export type RuleExecutorOptions = Omit & { - params: RuleAlertParams & { - scrollSize: number; - scrollLock: string; - }; + params: RuleTypeParams; }; // This returns true because by default a RuleAlertTypeDefinition is an AlertType @@ -116,12 +115,12 @@ export interface Ancestor { } export interface Signal { - rule: Partial; + rule: Partial; parent: Ancestor; ancestors: Ancestor[]; original_time: string; original_event?: SearchTypes; - status: 'open' | 'closed'; + status: Status; } export interface SignalHit { @@ -145,12 +144,7 @@ export interface AlertAttributes { } export interface RuleAlertAttributes extends AlertAttributes { - params: Omit< - RuleAlertParams, - 'ruleId' | 'name' | 'enabled' | 'interval' | 'tags' | 'actions' | 'throttle' - > & { - ruleId: string; - }; + params: RuleTypeParams; } export type BulkResponseErrorAggregation = Record; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index 9062de49fa6ce..6e284908e3358 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -4,131 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ListsDefaultArraySchema } from '../../../common/detection_engine/schemas/types/lists_default_array'; +import { + AnomalyThresholdOrUndefined, + Description, + NoteOrUndefined, + ThreatOrUndefined, + FalsePositives, + From, + Immutable, + IndexOrUndefined, + LanguageOrUndefined, + MaxSignals, + MachineLearningJobIdOrUndefined, + RiskScore, + OutputIndex, + QueryOrUndefined, + References, + SavedIdOrUndefined, + Severity, + To, + TimelineIdOrUndefined, + TimelineTitleOrUndefined, + Version, + MetaOrUndefined, + RuleId, + ListAndOrUndefined, +} from '../../../common/detection_engine/schemas/common/schemas'; import { CallAPIOptions } from '../../../../../../src/core/server'; import { Filter } from '../../../../../../src/plugins/data/server'; -import { IRuleStatusAttributes } from './rules/types'; -import { RuleAlertAction, RuleType } from '../../../common/detection_engine/types'; +import { RuleType } from '../../../common/detection_engine/types'; export type PartialFilter = Partial; -export interface IMitreAttack { - id: string; - name: string; - reference: string; -} - -export interface ThreatParams { - framework: string; - tactic: IMitreAttack; - technique: IMitreAttack[]; -} - -// Notice below we are using lists: ListsAndArraySchema[]; which is coming directly from the response output section. -// TODO: Eventually this whole RuleAlertParams will be replaced with io-ts. For now we can slowly strangle it out and reduce duplicate types -// We don't have the input types defined through io-ts just yet but as we being introducing types from there we will more and more remove -// types and share them between input and output schema but have an input Rule Schema and an output Rule Schema. - -export interface Meta { - [key: string]: {} | string | undefined | null; - kibana_siem_app_url?: string | undefined; -} - -export type Language = 'kuery' | 'lucene'; - -export interface RuleAlertParams { - actions: RuleAlertAction[]; - anomalyThreshold: number | undefined; - description: string; - note: string | undefined | null; - enabled: boolean; - falsePositives: string[]; - filters: PartialFilter[] | undefined | null; - from: string; - immutable: boolean; - index: string[] | undefined | null; - interval: string; - ruleId: string | undefined | null; - language: Language | undefined | null; - maxSignals: number; - machineLearningJobId: string | undefined; - riskScore: number; - outputIndex: string; - name: string; - query: string | undefined | null; - references: string[]; - savedId?: string | undefined | null; - meta: Meta | undefined | null; - severity: string; - tags: string[]; - to: string; - timelineId: string | undefined | null; - timelineTitle: string | undefined | null; - threat: ThreatParams[] | undefined | null; +export interface RuleTypeParams { + anomalyThreshold: AnomalyThresholdOrUndefined; + description: Description; + note: NoteOrUndefined; + falsePositives: FalsePositives; + from: From; + ruleId: RuleId; + immutable: Immutable; + index: IndexOrUndefined; + language: LanguageOrUndefined; + outputIndex: OutputIndex; + savedId: SavedIdOrUndefined; + timelineId: TimelineIdOrUndefined; + timelineTitle: TimelineTitleOrUndefined; + meta: MetaOrUndefined; + machineLearningJobId: MachineLearningJobIdOrUndefined; + query: QueryOrUndefined; + filters: PartialFilter[] | undefined; + maxSignals: MaxSignals; + riskScore: RiskScore; + severity: Severity; + threat: ThreatOrUndefined; + to: To; type: RuleType; - version: number; - throttle: string | undefined | null; - exceptionsList: ListsDefaultArraySchema | null | undefined; + references: References; + version: Version; + exceptionsList: ListAndOrUndefined; } -export type RuleTypeParams = Omit< - RuleAlertParams, - 'name' | 'enabled' | 'interval' | 'tags' | 'actions' | 'throttle' ->; - -export type RuleAlertParamsRest = Omit< - RuleAlertParams, - | 'anomalyThreshold' - | 'ruleId' - | 'falsePositives' - | 'immutable' - | 'maxSignals' - | 'exceptionsList' - | 'machineLearningJobId' - | 'savedId' - | 'riskScore' - | 'timelineId' - | 'timelineTitle' - | 'outputIndex' -> & - Omit< - IRuleStatusAttributes, - | 'status' - | 'alertId' - | 'statusDate' - | 'lastFailureAt' - | 'lastSuccessAt' - | 'lastSuccessMessage' - | 'lastFailureMessage' - > & { - anomaly_threshold: RuleAlertParams['anomalyThreshold']; - exceptions_list: RuleAlertParams['exceptionsList']; - rule_id: RuleAlertParams['ruleId']; - false_positives: RuleAlertParams['falsePositives']; - saved_id?: RuleAlertParams['savedId']; - timeline_id: RuleAlertParams['timelineId']; - timeline_title: RuleAlertParams['timelineTitle']; - max_signals: RuleAlertParams['maxSignals']; - machine_learning_job_id: RuleAlertParams['machineLearningJobId']; - risk_score: RuleAlertParams['riskScore']; - output_index: RuleAlertParams['outputIndex']; - created_at: string; - updated_at: string; - status?: IRuleStatusAttributes['status'] | undefined; - status_date?: IRuleStatusAttributes['statusDate'] | undefined; - last_failure_at?: IRuleStatusAttributes['lastFailureAt'] | undefined; - last_success_at?: IRuleStatusAttributes['lastSuccessAt'] | undefined; - last_failure_message?: IRuleStatusAttributes['lastFailureMessage'] | undefined; - last_success_message?: IRuleStatusAttributes['lastSuccessMessage'] | undefined; - }; - -export type OutputRuleAlertRest = RuleAlertParamsRest & { - id: string; - created_by: string | undefined | null; - updated_by: string | undefined | null; - immutable: boolean; -}; - // eslint-disable-next-line @typescript-eslint/no-explicit-any export type CallWithRequest, V> = ( endpoint: string, diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts index ca2f881d6abd8..a1902202d983d 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { transformDataToNdjson } from './create_stream_from_ndjson'; -import { sampleRule } from '../../lib/detection_engine/signals/__mocks__/es_results'; import { ImportRulesSchemaDecoded } from '../../../common/detection_engine/schemas/request/import_rules_schema'; +import { getRulesSchemaMock } from '../../../common/detection_engine/schemas/response/rules_schema.mocks'; export const getOutputSample = (): Partial => ({ rule_id: 'rule-1', @@ -33,14 +33,14 @@ describe('create_rules_stream_from_ndjson', () => { }); test('single rule will transform with new line ending character for ndjson', () => { - const rule = sampleRule(); + const rule = getRulesSchemaMock(); const ruleNdjson = transformDataToNdjson([rule]); expect(ruleNdjson.endsWith('\n')).toBe(true); }); test('multiple rules will transform with two new line ending characters for ndjson', () => { - const result1 = sampleRule(); - const result2 = sampleRule(); + const result1 = getRulesSchemaMock(); + const result2 = getRulesSchemaMock(); result2.id = 'some other id'; result2.rule_id = 'some other id'; result2.name = 'Some other rule'; @@ -52,8 +52,8 @@ describe('create_rules_stream_from_ndjson', () => { }); test('you can parse two rules back out without errors', () => { - const result1 = sampleRule(); - const result2 = sampleRule(); + const result1 = getRulesSchemaMock(); + const result2 = getRulesSchemaMock(); result2.id = 'some other id'; result2.rule_id = 'some other id'; result2.name = 'Some other rule'; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts index 127f688dfbc28..9cda6271d427f 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules.ts @@ -12,12 +12,12 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, - getSimpleMlRule, + getSimpleRuleUpdate, + getSimpleMlRuleUpdate, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -41,11 +41,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.rule_id = 'rule-1'; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -68,11 +68,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's type to try to be a machine learning job type - const updatedRule = getSimpleMlRule('rule-1'); + const updatedRule = getSimpleMlRuleUpdate('rule-1'); updatedRule.rule_id = 'rule-1'; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -90,7 +90,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using an auto-generated rule_id', async () => { - const rule = getSimpleRule('rule-1'); + const rule = getSimpleRuleUpdate('rule-1'); delete rule.rule_id; // create a simple rule const { body: createRuleBody } = await supertest @@ -100,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.rule_id = createRuleBody.rule_id; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -123,11 +123,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; updatedRule.id = createdBody.id; delete updatedRule.rule_id; @@ -150,11 +150,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's enabled to false and another property - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.severity = 'low'; updatedRule.enabled = false; @@ -178,10 +178,10 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; ruleUpdate.timeline_id = 'some id'; @@ -192,7 +192,7 @@ export default ({ getService }: FtrProviderContext) => { .send(ruleUpdate) .expect(200); - const ruleUpdate2 = getSimpleRule('rule-1'); + const ruleUpdate2 = getSimpleRuleUpdate('rule-1'); ruleUpdate2.name = 'some other name'; // update a simple rule's name @@ -211,7 +211,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should give a 404 if it is given a fake id', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getSimpleRuleUpdate(); simpleRule.id = '5096dec6-b6b9-4d8d-8f93-6c2602079d9d'; delete simpleRule.rule_id; @@ -228,7 +228,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should give a 404 if it is given a fake rule_id', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getSimpleRuleUpdate(); simpleRule.rule_id = 'fake_id'; delete simpleRule.id; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts index 54f29939fb6b4..24eee8deaf3d4 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/update_rules_bulk.ts @@ -12,11 +12,11 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, + getSimpleRuleUpdate, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -40,10 +40,10 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; // update a simple rule's name @@ -65,20 +65,20 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // create a second simple rule await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-2')) + .send(getSimpleRuleUpdate('rule-2')) .expect(200); - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.name = 'some other name'; - const updatedRule2 = getSimpleRule('rule-2'); + const updatedRule2 = getSimpleRuleUpdate('rule-2'); updatedRule2.name = 'some other name'; // update both rule names @@ -107,11 +107,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createRuleBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createRuleBody.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; @@ -134,23 +134,23 @@ export default ({ getService }: FtrProviderContext) => { const { body: createRule1 } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // create a second simple rule const { body: createRule2 } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-2')) + .send(getSimpleRuleUpdate('rule-2')) .expect(200); // update both rule names - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createRule1.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; - const updatedRule2 = getSimpleRule('rule-1'); + const updatedRule2 = getSimpleRuleUpdate('rule-1'); updatedRule2.id = createRule2.id; updatedRule2.name = 'some other name'; delete updatedRule2.rule_id; @@ -180,11 +180,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createdBody.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; @@ -207,11 +207,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's enabled to false and another property - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.severity = 'low'; updatedRule1.enabled = false; @@ -235,11 +235,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's timeline_title - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; ruleUpdate.timeline_id = 'some id'; @@ -250,7 +250,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // update a simple rule's name - const ruleUpdate2 = getSimpleRule('rule-1'); + const ruleUpdate2 = getSimpleRuleUpdate('rule-1'); ruleUpdate2.name = 'some other name'; const { body } = await supertest @@ -268,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.id = '1fd52120-d3a9-4e7a-b23c-96c0e1a74ae5'; delete ruleUpdate.rule_id; @@ -290,7 +290,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a 200 but give a 404 in the message if it is given a fake rule_id', async () => { - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.rule_id = 'fake_id'; delete ruleUpdate.id; @@ -313,14 +313,14 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.name = 'some other name'; delete ruleUpdate.id; - const ruleUpdate2 = getSimpleRule('fake_id'); + const ruleUpdate2 = getSimpleRuleUpdate('fake_id'); ruleUpdate2.name = 'some other name'; delete ruleUpdate.id; @@ -353,16 +353,16 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update one rule name and give a fake id for the second - const rule1 = getSimpleRule(); + const rule1 = getSimpleRuleUpdate(); delete rule1.rule_id; rule1.id = createdBody.id; rule1.name = 'some other name'; - const rule2 = getSimpleRule(); + const rule2 = getSimpleRuleUpdate(); delete rule2.rule_id; rule2.id = 'b3aa019a-656c-4311-b13b-4d9852e24347'; rule2.name = 'some other name'; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts index 0b1b49e379d17..279158f09f273 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules.ts @@ -12,13 +12,14 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, removeServerGeneratedPropertiesIncludingRuleId, getSimpleRuleOutputWithoutRuleId, getSimpleMlRule, getSimpleMlRuleOutput, + getSimpleRuleUpdate, + getSimpleMlRuleUpdate, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -42,11 +43,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.rule_id = 'rule-1'; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -73,7 +74,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // update a simple rule's name - const updatedRule = getSimpleMlRule('rule-1'); + const updatedRule = getSimpleMlRuleUpdate('rule-1'); updatedRule.rule_id = 'rule-1'; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -92,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should update a single rule property of name using an auto-generated rule_id', async () => { - const rule = getSimpleRule('rule-1'); + const rule = getSimpleRuleUpdate('rule-1'); delete rule.rule_id; // create a simple rule const { body: createRuleBody } = await supertest @@ -102,7 +103,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.rule_id = createRuleBody.rule_id; updatedRule.name = 'some other name'; delete updatedRule.id; @@ -125,11 +126,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; updatedRule.id = createdBody.id; delete updatedRule.rule_id; @@ -152,11 +153,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's enabled to false and another property - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.severity = 'low'; updatedRule.enabled = false; @@ -180,10 +181,10 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; ruleUpdate.timeline_id = 'some id'; @@ -194,7 +195,7 @@ export default ({ getService }: FtrProviderContext) => { .send(ruleUpdate) .expect(200); - const ruleUpdate2 = getSimpleRule('rule-1'); + const ruleUpdate2 = getSimpleRuleUpdate('rule-1'); ruleUpdate2.name = 'some other name'; // update a simple rule's name @@ -213,7 +214,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should give a 404 if it is given a fake id', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getSimpleRuleUpdate(); simpleRule.id = '5096dec6-b6b9-4d8d-8f93-6c2602079d9d'; delete simpleRule.rule_id; @@ -230,7 +231,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should give a 404 if it is given a fake rule_id', async () => { - const simpleRule = getSimpleRule(); + const simpleRule = getSimpleRuleUpdate(); simpleRule.rule_id = 'fake_id'; delete simpleRule.id; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts index 54f29939fb6b4..24eee8deaf3d4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/update_rules_bulk.ts @@ -12,11 +12,11 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getSimpleRule, getSimpleRuleOutput, removeServerGeneratedProperties, getSimpleRuleOutputWithoutRuleId, removeServerGeneratedPropertiesIncludingRuleId, + getSimpleRuleUpdate, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -40,10 +40,10 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const updatedRule = getSimpleRule('rule-1'); + const updatedRule = getSimpleRuleUpdate('rule-1'); updatedRule.name = 'some other name'; // update a simple rule's name @@ -65,20 +65,20 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // create a second simple rule await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-2')) + .send(getSimpleRuleUpdate('rule-2')) .expect(200); - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.name = 'some other name'; - const updatedRule2 = getSimpleRule('rule-2'); + const updatedRule2 = getSimpleRuleUpdate('rule-2'); updatedRule2.name = 'some other name'; // update both rule names @@ -107,11 +107,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createRuleBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createRuleBody.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; @@ -134,23 +134,23 @@ export default ({ getService }: FtrProviderContext) => { const { body: createRule1 } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // create a second simple rule const { body: createRule2 } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-2')) + .send(getSimpleRuleUpdate('rule-2')) .expect(200); // update both rule names - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createRule1.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; - const updatedRule2 = getSimpleRule('rule-1'); + const updatedRule2 = getSimpleRuleUpdate('rule-1'); updatedRule2.id = createRule2.id; updatedRule2.name = 'some other name'; delete updatedRule2.rule_id; @@ -180,11 +180,11 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's name - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.id = createdBody.id; updatedRule1.name = 'some other name'; delete updatedRule1.rule_id; @@ -207,11 +207,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's enabled to false and another property - const updatedRule1 = getSimpleRule('rule-1'); + const updatedRule1 = getSimpleRuleUpdate('rule-1'); updatedRule1.severity = 'low'; updatedRule1.enabled = false; @@ -235,11 +235,11 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update a simple rule's timeline_title - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.timeline_title = 'some title'; ruleUpdate.timeline_id = 'some id'; @@ -250,7 +250,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // update a simple rule's name - const ruleUpdate2 = getSimpleRule('rule-1'); + const ruleUpdate2 = getSimpleRuleUpdate('rule-1'); ruleUpdate2.name = 'some other name'; const { body } = await supertest @@ -268,7 +268,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a 200 but give a 404 in the message if it is given a fake id', async () => { - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.id = '1fd52120-d3a9-4e7a-b23c-96c0e1a74ae5'; delete ruleUpdate.rule_id; @@ -290,7 +290,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should return a 200 but give a 404 in the message if it is given a fake rule_id', async () => { - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.rule_id = 'fake_id'; delete ruleUpdate.id; @@ -313,14 +313,14 @@ export default ({ getService }: FtrProviderContext) => { await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); - const ruleUpdate = getSimpleRule('rule-1'); + const ruleUpdate = getSimpleRuleUpdate('rule-1'); ruleUpdate.name = 'some other name'; delete ruleUpdate.id; - const ruleUpdate2 = getSimpleRule('fake_id'); + const ruleUpdate2 = getSimpleRuleUpdate('fake_id'); ruleUpdate2.name = 'some other name'; delete ruleUpdate.id; @@ -353,16 +353,16 @@ export default ({ getService }: FtrProviderContext) => { const { body: createdBody } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') - .send(getSimpleRule('rule-1')) + .send(getSimpleRuleUpdate('rule-1')) .expect(200); // update one rule name and give a fake id for the second - const rule1 = getSimpleRule(); + const rule1 = getSimpleRuleUpdate(); delete rule1.rule_id; rule1.id = createdBody.id; rule1.name = 'some other name'; - const rule2 = getSimpleRule(); + const rule2 = getSimpleRuleUpdate(); delete rule2.rule_id; rule2.id = 'b3aa019a-656c-4311-b13b-4d9852e24347'; rule2.name = 'some other name'; diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index b4330ce5d3912..5f0a8cd938408 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -7,7 +7,9 @@ import { Client } from '@elastic/elasticsearch'; import { SuperTest } from 'supertest'; import supertestAsPromised from 'supertest-as-promised'; -import { OutputRuleAlertRest } from '../../plugins/security_solution/server/lib/detection_engine/types'; +import { CreateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/create_rules_schema'; +import { UpdateRulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/request/update_rules_schema'; +import { RulesSchema } from '../../plugins/security_solution/common/detection_engine/schemas/response/rules_schema'; import { DETECTION_ENGINE_INDEX_URL } from '../../plugins/security_solution/common/constants'; /** @@ -15,8 +17,8 @@ import { DETECTION_ENGINE_INDEX_URL } from '../../plugins/security_solution/comm * @param rule Rule to pass in to remove typical server generated properties */ export const removeServerGeneratedProperties = ( - rule: Partial -): Partial => { + rule: Partial +): Partial => { const { created_at, updated_at, @@ -37,8 +39,8 @@ export const removeServerGeneratedProperties = ( * @param rule Rule to pass in to remove typical server generated properties */ export const removeServerGeneratedPropertiesIncludingRuleId = ( - rule: Partial -): Partial => { + rule: Partial +): Partial => { const ruleWithRemovedProperties = removeServerGeneratedProperties(rule); const { rule_id, ...additionalRuledIdRemoved } = ruleWithRemovedProperties; return additionalRuledIdRemoved; @@ -48,7 +50,22 @@ export const removeServerGeneratedPropertiesIncludingRuleId = ( * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId */ -export const getSimpleRule = (ruleId = 'rule-1'): Partial => ({ +export const getSimpleRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ + name: 'Simple Rule Query', + description: 'Simple Rule Query', + risk_score: 1, + rule_id: ruleId, + severity: 'high', + index: ['auditbeat-*'], + type: 'query', + query: 'user.name: root or user.name: admin', +}); + +/** + * This is a typical simple rule for testing that is easy for most basic testing + * @param ruleId + */ +export const getSimpleRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', risk_score: 1, @@ -63,7 +80,18 @@ export const getSimpleRule = (ruleId = 'rule-1'): Partial = * This is a representative ML rule payload as expected by the server * @param ruleId */ -export const getSimpleMlRule = (ruleId = 'rule-1'): Partial => ({ +export const getSimpleMlRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ + name: 'Simple ML Rule', + description: 'Simple Machine Learning Rule', + anomaly_threshold: 44, + risk_score: 1, + rule_id: ruleId, + severity: 'high', + machine_learning_job_id: 'some_job_id', + type: 'machine_learning', +}); + +export const getSimpleMlRuleUpdate = (ruleId = 'rule-1'): UpdateRulesSchema => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', anomaly_threshold: 44, @@ -107,7 +135,7 @@ export const getSignalStatusEmptyResponse = () => ({ /** * This is a typical simple rule for testing that is easy for most basic testing */ -export const getSimpleRuleWithoutRuleId = (): Partial => { +export const getSimpleRuleWithoutRuleId = (): CreateRulesSchema => { const simpleRule = getSimpleRule(); const { rule_id, ...ruleWithoutId } = simpleRule; return ruleWithoutId; @@ -130,9 +158,10 @@ export const binaryToString = (res: any, callback: any): void => { }; /** - * This is the typical output of a simple rule that Kibana will output with all the defaults. + * 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'): Partial => ({ +export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], created_by: 'elastic', description: 'Simple Rule Query', @@ -162,17 +191,16 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1'): Partial => { +export const getSimpleRuleOutputWithoutRuleId = (ruleId = 'rule-1'): Partial => { const rule = getSimpleRuleOutput(ruleId); const { rule_id, ...ruleWithoutRuleId } = rule; return ruleWithoutRuleId; }; -export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial => { +export const getSimpleMlRuleOutput = (ruleId = 'rule-1'): Partial => { const rule = getSimpleRuleOutput(ruleId); const { query, language, index, ...rest } = rule; @@ -252,7 +280,7 @@ export const getSimpleRuleAsNdjson = (ruleIds: string[]): Buffer => { * testing upload features. * @param rule The rule to convert to ndjson */ -export const ruleToNdjson = (rule: Partial): Buffer => { +export const ruleToNdjson = (rule: Partial): Buffer => { const stringified = JSON.stringify(rule); return Buffer.from(`${stringified}\n`); }; @@ -261,7 +289,7 @@ export const ruleToNdjson = (rule: Partial): Buffer => { * 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'): Partial => ({ actions: [], name: 'Complex Rule Query', description: 'Complex Rule Query', @@ -345,7 +373,7 @@ export const getComplexRule = (ruleId = 'rule-1'): 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'): Partial => ({ +export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial => ({ actions: [], created_by: 'elastic', name: 'Complex Rule Query',