From b0fdbe013a8d887055768e09f0911c8e78ed611a Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Mon, 9 Dec 2019 18:26:11 -0500 Subject: [PATCH 1/5] adds route for querying signals index, also updates signal status type names --- .../plugins/siem/server/kibana.index.ts | 2 + .../lib/detection_engine/alerts/types.ts | 277 ++ .../routes/__mocks__/request_responses.ts | 4 +- .../detection_engine/routes/schemas.test.ts | 2409 +++++++++++++++++ .../lib/detection_engine/routes/schemas.ts | 228 ++ .../signals/open_close_signals_route.ts | 6 +- .../routes/signals/query_signals_route.ts | 46 + .../scripts/signals/query_signals.sh | 20 + 8 files changed, 2987 insertions(+), 5 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts create mode 100755 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index f56e6b3c3f550..65b673e1c72a5 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -15,6 +15,7 @@ import { findRulesRoute } from './lib/detection_engine/routes/rules/find_rules_r import { deleteRulesRoute } from './lib/detection_engine/routes/rules/delete_rules_route'; import { updateRulesRoute } from './lib/detection_engine/routes/rules/update_rules_route'; import { setSignalsStatusRoute } from './lib/detection_engine/routes/signals/open_close_signals_route'; +import { querySignalsRoute } from './lib/detection_engine/routes/signals/query_signals_route'; import { ServerFacade } from './types'; import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_index_route'; import { isAlertExecutor } from './lib/detection_engine/signals/types'; @@ -44,6 +45,7 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy // POST /api/detection_engine/signals/status // Example usage can be found in siem/server/lib/detection_engine/scripts/signals setSignalsStatusRoute(__legacy); + querySignalsRoute(__legacy); // Detection Engine index routes that have the REST endpoints of /api/detection_engine/index // All REST index creation, policy management for spaces diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts new file mode 100644 index 0000000000000..e4fa376b3f111 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -0,0 +1,277 @@ +/* + * 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 { get } from 'lodash/fp'; + +import { SIGNALS_ID } from '../../../../common/constants'; +import { + Alert, + AlertType, + State, + AlertExecutorOptions, +} from '../../../../../alerting/server/types'; +import { AlertsClient } from '../../../../../alerting/server/alerts_client'; +import { ActionsClient } from '../../../../../actions/server/actions_client'; +import { RequestFacade } from '../../../types'; +import { SearchResponse } from '../../types'; +import { esFilters } from '../../../../../../../../src/plugins/data/server'; + +export type PartialFilter = Partial; + +export interface IMitreAttack { + id: string; + name: string; + reference: string; +} +export interface ThreatParams { + framework: string; + tactic: IMitreAttack; + techniques: IMitreAttack[]; +} +export interface RuleAlertParams { + description: string; + enabled: boolean; + falsePositives: string[]; + filter: Record | undefined | null; + filters: PartialFilter[] | undefined | null; + from: string; + immutable: boolean; + index: string[]; + interval: string; + ruleId: string | undefined | null; + language: string | undefined | null; + maxSignals: number; + riskScore: number; + outputIndex: string; + name: string; + query: string | undefined | null; + references: string[]; + savedId: string | undefined | null; + meta: Record | undefined | null; + severity: string; + tags: string[]; + to: string; + threats: ThreatParams[] | undefined | null; + type: 'filter' | 'query' | 'saved_query'; +} + +export type RuleAlertParamsRest = Omit< + RuleAlertParams, + 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' +> & { + rule_id: RuleAlertParams['ruleId']; + false_positives: RuleAlertParams['falsePositives']; + saved_id: RuleAlertParams['savedId']; + max_signals: RuleAlertParams['maxSignals']; + risk_score: RuleAlertParams['riskScore']; + output_index: RuleAlertParams['outputIndex']; +}; + +export interface SignalsStatusParams { + signalIds: string[] | undefined | null; + query: object | undefined | null; + status: 'open' | 'closed'; +} + +export interface SignalQueryParams { + searchQuery: object; +} + +export type SignalsStatusRestParams = Omit & { + signal_ids: SignalsStatusParams['signalIds']; +}; + +export type SignalsQueryRestParams = Omit & { + search_query: SignalQueryParams['searchQuery']; +}; + +export type OutputRuleAlertRest = RuleAlertParamsRest & { + id: string; + created_by: string | undefined | null; + updated_by: string | undefined | null; +}; + +export type UpdateRuleAlertParamsRest = Partial & { + id: string | undefined; + rule_id: RuleAlertParams['ruleId'] | undefined; +}; + +export interface FindParamsRest { + per_page: number; + page: number; + sort_field: string; + sort_order: 'asc' | 'desc'; + fields: string[]; + filter: string; +} + +export interface Clients { + alertsClient: AlertsClient; + actionsClient: ActionsClient; +} + +export type RuleParams = RuleAlertParams & Clients; + +export type UpdateRuleParams = Partial & { + id: string | undefined | null; +} & Clients; + +export type DeleteRuleParams = Clients & { + id: string | undefined; + ruleId: string | undefined | null; +}; + +export interface FindRulesRequest extends Omit { + query: { + per_page: number; + page: number; + search?: string; + sort_field?: string; + filter?: string; + fields?: string[]; + sort_order?: 'asc' | 'desc'; + }; +} + +export interface FindRuleParams { + alertsClient: AlertsClient; + perPage?: number; + page?: number; + sortField?: string; + filter?: string; + fields?: string[]; + sortOrder?: 'asc' | 'desc'; +} + +export interface ReadRuleParams { + alertsClient: AlertsClient; + id?: string | undefined | null; + ruleId?: string | undefined | null; +} + +export interface ReadRuleByRuleId { + alertsClient: AlertsClient; + ruleId: string; +} + +export type RuleTypeParams = Omit; + +export type RuleAlertType = Alert & { + id: string; + params: RuleTypeParams; +}; + +export interface RulesRequest extends RequestFacade { + payload: RuleAlertParamsRest; +} + +export interface SignalsStatusRequest extends RequestFacade { + payload: SignalsStatusRestParams; +} + +export interface SignalsQueryRequest extends RequestFacade { + payload: SignalsQueryRestParams; +} + +export interface UpdateRulesRequest extends RequestFacade { + payload: UpdateRuleAlertParamsRest; +} + +export type RuleExecutorOptions = Omit & { + params: RuleAlertParams & { + scrollSize: number; + scrollLock: string; + }; +}; + +export type SearchTypes = + | string + | string[] + | number + | number[] + | boolean + | boolean[] + | object + | object[]; + +export interface SignalSource { + [key: string]: SearchTypes; + '@timestamp': string; +} + +export interface BulkResponse { + took: number; + errors: boolean; + items: [ + { + create: { + _index: string; + _type?: string; + _id: string; + _version: number; + result?: string; + _shards?: { + total: number; + successful: number; + failed: number; + }; + _seq_no?: number; + _primary_term?: number; + status: number; + error?: { + type: string; + reason: string; + index_uuid?: string; + shard: string; + index: string; + }; + }; + } + ]; +} + +export interface MGetResponse { + docs: GetResponse[]; +} +export interface GetResponse { + _index: string; + _type: string; + _id: string; + _version: number; + _seq_no: number; + _primary_term: number; + found: boolean; + _source: SearchTypes; +} + +export type SignalSearchResponse = SearchResponse; +export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; + +export type QueryRequest = Omit & { + query: { id: string | undefined; rule_id: string | undefined }; +}; + +// This returns true because by default a RuleAlertTypeDefinition is an AlertType +// since we are only increasing the strictness of params. +export const isAlertExecutor = (obj: RuleAlertTypeDefinition): obj is AlertType => { + return true; +}; + +export type RuleAlertTypeDefinition = Omit & { + executor: ({ services, params, state }: RuleExecutorOptions) => Promise; +}; + +export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { + return obj.every(rule => isAlertType(rule)); +}; + +export const isAlertType = (obj: unknown): obj is RuleAlertType => { + return get('alertTypeId', obj) === SIGNALS_ID; +}; + +export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { + return objArray.length === 0 || isAlertType(objArray[0]); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 978434859ef95..08779a6b815e9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -45,12 +45,12 @@ export const typicalSetStatusSignalByIdsPayload = (): Partial status: 'closed', }); -export const typicalSetStatusSignalByQueryPayload = (): Partial => ({ +export const typicalSetStatusSignalByQueryPayload = (): Partial => ({ query: { range: { '@timestamp': { gte: 'now-2M', lte: 'now/M' } } }, status: 'closed', }); -export const setStatusSignalMissingIdsAndQueryPayload = (): Partial => ({ +export const setStatusSignalMissingIdsAndQueryPayload = (): Partial => ({ status: 'closed', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts new file mode 100644 index 0000000000000..3e679269ee012 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts @@ -0,0 +1,2409 @@ +/* + * 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 { + createRulesSchema, + updateRulesSchema, + findRulesSchema, + queryRulesSchema, + setSignalsStatusSchema, +} from './schemas'; +import { + RuleAlertParamsRest, + FindParamsRest, + UpdateRuleAlertParamsRest, + ThreatParams, + SignalsStatusRestParams, +} from '../alerts/types'; + +describe('schemas', () => { + describe('create rules schema', () => { + test('empty objects do not validate', () => { + expect(createRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + createRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[rule_id] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + interval: '5m', + index: ['index-1'], + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + risk_score: 50, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + }).error + ).toBeFalsy(); + }); + test('You can send in an empty array to threats', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).error + ).toBeFalsy(); + }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + threats: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeFalsy(); + }); + + test('If filter type is set then filter is required', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + }).error + ).toBeTruthy(); + }); + + test('If filter type is set then query is not allowed', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + query: 'some query value', + }).error + ).toBeTruthy(); + }); + + test('If filter type is set then language is not allowed', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('If filter type is set then filters are not allowed', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + filters: [], + }).error + ).toBeTruthy(); + }); + + test('allows references to be sent as valid', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('defaults references to an array', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual([]); + }); + + test('references cannot be numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { references: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { index: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('defaults interval to 5 min', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual('5m'); + }); + + test('defaults max signals to 100', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).value.max_signals + ).toEqual(100); + }); + + test('filter and filters cannot exist together', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + filter: {}, + filters: [], + }).error + ).toBeTruthy(); + }); + + test('saved_id is required when type is saved_query and will not validate without out', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + }).error + ).toBeTruthy(); + }); + + test('saved_id is required when type is saved_query and validates with it', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('filters cannot be a string', () => { + expect( + createRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('saved_query type cannot have filter with it', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filter: {}, + }).error + ).toBeTruthy(); + }); + + test('language validates with kuery', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can optionally send in an array of tags', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: ['tag_1', 'tag_2'], + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of tags that are numbers', () => { + expect( + createRulesSchema.validate> & { tags: number[] }>( + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: [0, 1, 2], + } + ).error + ).toBeTruthy(); + }); + + test('You cannot send in an array of threats that are missing "framework"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "tactic"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "techniques"', () => { + expect( + createRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + + test('You can optionally send in an array of false positives', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: ['false_1', 'false_2'], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of false positives that are numbers', () => { + expect( + createRulesSchema.validate< + Partial> & { false_positives: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: [5, 4], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can optionally set the immutable to be true', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot set the immutable to be a number', () => { + expect( + createRulesSchema.validate< + Partial> & { immutable: number } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: 5, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to 101', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 101, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to -1', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: -1, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can set the risk_score to 0', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 0, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set the risk_score to 100', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 100, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set meta to any object you want', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: { + somethingMadeUp: { somethingElse: true }, + }, + }).error + ).toBeFalsy(); + }); + + test('You cannot create meta as a string', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('You can have an empty query string when filters are present', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: '', + language: 'kuery', + filters: [], + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can omit the query string when filters are present', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('query string defaults to empty string when present with filters', () => { + expect( + createRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + }).value.query + ).toEqual(''); + }); + }); + + describe('update rules schema', () => { + test('empty objects do not validate as they require at least id or rule_id', () => { + expect(updateRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + updateRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[id] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[rule_id] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeFalsy(); + }); + + test('[id and rule_id] does not validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'id-1', + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, description] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + }).error + ).toBeFalsy(); + }); + + test('[id, risk_score] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + risk_score: 10, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, name, severity, type, interval] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { + expect( + updateRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + }).error + ).toBeFalsy(); + }); + + test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + }).error + ).toBeFalsy(); + }); + + test('If filter type is set then filter is still not required', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + }).error + ).toBeFalsy(); + }); + + test('If filter type is set then query is not allowed', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + query: 'some query value', + }).error + ).toBeTruthy(); + }); + + test('If filter type is set then language is not allowed', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('If filter type is set then filters are not allowed', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'filter', + filter: {}, + filters: [], + }).error + ).toBeTruthy(); + }); + + test('allows references to be sent as a valid value to update with', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('does not default references to an array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual(undefined); + }); + + test('does not default interval', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual(undefined); + }); + + test('does not default max signal', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).value.max_signals + ).toEqual(undefined); + }); + + test('references cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { references: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + updateRulesSchema.validate< + Partial> & { index: number[] } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('filter and filters cannot exist together', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + filter: {}, + filters: [], + }).error + ).toBeTruthy(); + }); + + test('saved_id is not required when type is saved_query and will validate without it', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + }).error + ).toBeFalsy(); + }); + + test('saved_id validates with saved_query', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('saved_query type cannot have filter with it', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filter: {}, + }).error + ).toBeTruthy(); + }); + + test('language validates with kuery', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('meta can be updated', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + meta: { whateverYouWant: 'anything_at_all' }, + }).error + ).toBeFalsy(); + }); + + test('You update meta as a string', () => { + expect( + updateRulesSchema.validate< + Partial & { meta: string }> + >({ + id: 'rule-1', + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('filters cannot be a string', () => { + expect( + updateRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + type: 'query', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('threats is not defaulted to empty array on update', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).value.threats + ).toBe(undefined); + }); + + test('threats is not defaulted to undefined on update with empty array', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).value.threats + ).toMatchObject([]); + }); + test('threats is valid when updated with all sub-objects', () => { + const expected: ThreatParams[] = [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ]; + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).value.threats + ).toMatchObject(expected); + }); + test('threats is invalid when updated with missing property framework', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing tactic sub-object', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('threats is invalid when updated with missing techniques', () => { + expect( + updateRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + }); + + describe('find rules schema', () => { + test('empty objects do validate', () => { + expect(findRulesSchema.validate>({}).error).toBeFalsy(); + }); + + test('all values validate', () => { + expect( + findRulesSchema.validate>({ + per_page: 5, + page: 1, + sort_field: 'some field', + fields: ['field 1', 'field 2'], + filter: 'some filter', + sort_order: 'asc', + }).error + ).toBeFalsy(); + }); + + test('made up parameters do not validate', () => { + expect( + findRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('per_page validates', () => { + expect( + findRulesSchema.validate>({ per_page: 5 }).error + ).toBeFalsy(); + }); + + test('page validates', () => { + expect( + findRulesSchema.validate>({ page: 5 }).error + ).toBeFalsy(); + }); + + test('sort_field validates', () => { + expect( + findRulesSchema.validate>({ sort_field: 'some value' }).error + ).toBeFalsy(); + }); + + test('fields validates with a string', () => { + expect( + findRulesSchema.validate>({ fields: ['some value'] }).error + ).toBeFalsy(); + }); + + test('fields validates with multiple strings', () => { + expect( + findRulesSchema.validate>({ + fields: ['some value 1', 'some value 2'], + }).error + ).toBeFalsy(); + }); + + test('fields does not validate with a number', () => { + expect( + findRulesSchema.validate> & { fields: number[] }>({ + fields: [5], + }).error + ).toBeTruthy(); + }); + + test('per page has a default of 20', () => { + expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); + }); + + test('page has a default of 1', () => { + expect(findRulesSchema.validate>({}).value.page).toEqual(1); + }); + + test('filter works with a string', () => { + expect( + findRulesSchema.validate>({ + filter: 'some value 1', + }).error + ).toBeFalsy(); + }); + + test('filter does not work with a number', () => { + expect( + findRulesSchema.validate> & { filter: number }>({ + filter: 5, + }).error + ).toBeTruthy(); + }); + + test('sort_order requires sort_field to work', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + }).error + ).toBeTruthy(); + }); + + test('sort_order and sort_field validate together', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'asc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order validates with desc and sort_field', () => { + expect( + findRulesSchema.validate>({ + sort_order: 'desc', + sort_field: 'some field', + }).error + ).toBeFalsy(); + }); + + test('sort_order does not validate with a string other than asc and desc', () => { + expect( + findRulesSchema.validate< + Partial> & { sort_order: string } + >({ + sort_order: 'some other string', + sort_field: 'some field', + }).error + ).toBeTruthy(); + }); + }); + + describe('queryRulesSchema', () => { + test('empty objects do not validate', () => { + expect(queryRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('both rule_id and id being supplied dot not validate', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1', id: '1' }) + .error + ).toBeTruthy(); + }); + + test('only id validates', () => { + expect( + queryRulesSchema.validate>({ id: '1' }).error + ).toBeFalsy(); + }); + + test('only rule_id validates', () => { + expect( + queryRulesSchema.validate>({ rule_id: '1' }).error + ).toBeFalsy(); + }); + }); + + describe('set signal status schema', () => { + test('signal_ids and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('query and status is valid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + status: 'open', + }).error + ).toBeFalsy(); + }); + + test('signal_ids and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + signal_ids: ['somefakeid'], + }).error + ).toBeTruthy(); + }); + + test('query and missing status is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + query: {}, + }).error + ).toBeTruthy(); + }); + + test('status is present but query or signal_ids is missing is invalid', () => { + expect( + setSignalsStatusSchema.validate>({ + status: 'closed', + }).error + ).toBeTruthy(); + }); + + test('signal_ids is present but status has wrong value', () => { + expect( + setSignalsStatusSchema.validate< + Partial< + Omit & { + status: string; + } + > + >({ + status: 'fakeVal', + }).error + ).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts new file mode 100644 index 0000000000000..2f6a186b94ed2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -0,0 +1,228 @@ +/* + * 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 Joi from 'joi'; +import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; + +/* eslint-disable @typescript-eslint/camelcase */ +const description = Joi.string(); +const enabled = Joi.boolean(); +const false_positives = Joi.array().items(Joi.string()); +const filter = Joi.object(); +const filters = Joi.array(); +const from = Joi.string(); +const immutable = Joi.boolean(); +const rule_id = Joi.string(); +const id = Joi.string(); +const index = Joi.array() + .items(Joi.string()) + .single(); +const interval = Joi.string(); +const query = Joi.string(); +const language = Joi.string().valid('kuery', 'lucene'); +const output_index = Joi.string(); +const saved_id = Joi.string(); +const meta = Joi.object(); +const max_signals = Joi.number().greater(0); +const name = Joi.string(); +const risk_score = Joi.number() + .greater(-1) + .less(101); +const severity = Joi.string(); +const status = Joi.string().valid('open', 'closed'); +const to = Joi.string(); +const type = Joi.string().valid('filter', 'query', 'saved_query'); +const queryFilter = Joi.string(); +const references = Joi.array() + .items(Joi.string()) + .single(); +const per_page = Joi.number() + .min(0) + .default(20); +const page = Joi.number() + .min(1) + .default(1); +const signal_ids = Joi.array().items(Joi.string()); +const signal_status_query = Joi.object(); +const sort_field = Joi.string(); +const sort_order = Joi.string().valid('asc', 'desc'); +const tags = Joi.array().items(Joi.string()); +const fields = Joi.array() + .items(Joi.string()) + .single(); +const threat_framework = Joi.string(); +const threat_tactic_id = Joi.string(); +const threat_tactic_name = Joi.string(); +const threat_tactic_reference = Joi.string(); +const threat_tactic = Joi.object({ + id: threat_tactic_id.required(), + name: threat_tactic_name.required(), + reference: threat_tactic_reference.required(), +}); +const threat_technique_id = Joi.string(); +const threat_technique_name = Joi.string(); +const threat_technique_reference = Joi.string(); +const threat_technique = Joi.object({ + id: threat_technique_id.required(), + name: threat_technique_name.required(), + reference: threat_technique_reference.required(), +}); +const threat_techniques = Joi.array().items(threat_technique.required()); + +const threats = Joi.array().items( + Joi.object({ + framework: threat_framework.required(), + tactic: threat_tactic.required(), + techniques: threat_techniques.required(), + }) +); +/* eslint-enable @typescript-eslint/camelcase */ + +export const createRulesSchema = Joi.object({ + description: description.required(), + enabled: enabled.default(true), + false_positives: false_positives.default([]), + filter: filter.when('type', { is: 'filter', then: Joi.required(), otherwise: Joi.forbidden() }), + filters: Joi.when('type', { + is: 'query', + then: filters.optional(), + otherwise: Joi.when('type', { + is: 'saved_query', + then: filters.optional(), + otherwise: Joi.forbidden(), + }), + }), + from: from.required(), + rule_id, + immutable: immutable.default(false), + index, + interval: interval.default('5m'), + query: Joi.when('type', { + is: 'query', + then: Joi.when('filters', { + is: Joi.exist(), + then: query + .optional() + .allow('') + .default(''), + otherwise: Joi.required(), + }), + otherwise: Joi.when('type', { + is: 'saved_query', + then: query.optional(), + otherwise: Joi.forbidden(), + }), + }), + language: Joi.when('type', { + is: 'query', + then: language.required(), + otherwise: Joi.when('type', { + is: 'saved_query', + then: language.optional(), + otherwise: Joi.forbidden(), + }), + }), + output_index, + saved_id: saved_id.when('type', { + is: 'saved_query', + then: Joi.required(), + otherwise: Joi.forbidden(), + }), + meta, + risk_score: risk_score.required(), + max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), + name: name.required(), + severity: severity.required(), + tags: tags.default([]), + to: to.required(), + type: type.required(), + threats: threats.default([]), + references: references.default([]), +}); + +export const updateRulesSchema = Joi.object({ + description, + enabled, + false_positives, + filter: filter.when('type', { is: 'filter', then: Joi.optional(), otherwise: Joi.forbidden() }), + filters: Joi.when('type', { + is: 'query', + then: filters.optional(), + otherwise: Joi.when('type', { + is: 'saved_query', + then: filters.optional(), + otherwise: Joi.forbidden(), + }), + }), + from, + rule_id, + id, + immutable, + index, + interval, + query: Joi.when('type', { + is: 'query', + then: query.optional(), + otherwise: Joi.when('type', { + is: 'saved_query', + then: query.optional(), + otherwise: Joi.forbidden(), + }), + }), + language: Joi.when('type', { + is: 'query', + then: language.optional(), + otherwise: Joi.when('type', { + is: 'saved_query', + then: language.optional(), + otherwise: Joi.forbidden(), + }), + }), + output_index, + saved_id: saved_id.when('type', { + is: 'saved_query', + then: Joi.optional(), + otherwise: Joi.forbidden(), + }), + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, +}).xor('id', 'rule_id'); + +export const queryRulesSchema = Joi.object({ + rule_id, + id, +}).xor('id', 'rule_id'); + +export const findRulesSchema = Joi.object({ + fields, + filter: queryFilter, + per_page, + page, + sort_field: Joi.when(Joi.ref('sort_order'), { + is: Joi.exist(), + then: sort_field.required(), + otherwise: sort_field.optional(), + }), + sort_order, +}); + +export const setSignalsStatusSchema = Joi.object({ + signal_ids, + query: signal_status_query, + status: status.required(), +}).xor('signal_ids', 'query'); + +export const querySignalsSchema = Joi.object({ + search_query: Joi.object(), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index b342cc5cd14ef..931ed8a133004 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; -import { SignalsRequest } from '../../signals/types'; -import { setSignalsStatusSchema } from '../schemas/set_signal_status_schema'; +import { SignalsStatusRequest } from '../../alerts/types'; +import { setSignalsStatusSchema } from '../schemas'; import { ServerFacade } from '../../../../types'; import { transformError, getIndex } from '../utils'; @@ -24,7 +24,7 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute payload: setSignalsStatusSchema, }, }, - async handler(request: SignalsRequest, headers) { + async handler(request: SignalsStatusRequest, _headers) { const { signal_ids: signalIds, query, status } = request.payload; const index = getIndex(request, server); const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts new file mode 100644 index 0000000000000..edd47703f8e91 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -0,0 +1,46 @@ +/* + * 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 Hapi from 'hapi'; +import { DETECTION_ENGINE_SIGNALS_URL } from '../../../../../common/constants'; +import { SignalsQueryRequest } from '../../alerts/types'; +import { querySignalsSchema } from '../schemas'; +import { ServerFacade } from '../../../../types'; +import { transformError, getIndex } from '../utils'; + +export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'GET', + path: DETECTION_ENGINE_SIGNALS_URL, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + query: querySignalsSchema, + }, + }, + async handler(request: SignalsQueryRequest, _headers) { + const { query: searchQuery } = request; + const index = getIndex(request, server); + const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); + try { + return callWithRequest(request, 'search', { + index, + body: searchQuery, + }); + } catch (exc) { + // error while getting or updating signal with id: id in signal index .siem-signals + return transformError(exc); + } + }, + }; +}; + +export const querySignalsRoute = (server: ServerFacade) => { + server.route(querySignalsRouteDef(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh new file mode 100755 index 0000000000000..259953db7f7cd --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./set_status_with_id.sh closed +# Example: ./set_status_with_id.sh open + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals \ + -d '{"search_query": { "query": { "match_all": {} } } }' \ + | jq . From e875fc6fb891af7543b68bb10d7fdd86af80f0f0 Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Tue, 10 Dec 2019 10:06:18 -0500 Subject: [PATCH 2/5] first pass at happy path tests --- .../legacy/plugins/siem/common/constants.ts | 1 + .../routes/__mocks__/request_responses.ts | 17 +++++ .../lib/detection_engine/routes/schemas.ts | 2 +- .../signals/query_signals_route.test.ts | 65 +++++++++++++++++++ .../routes/signals/query_signals_route.ts | 10 +-- .../scripts/signals/query_signals.sh | 8 +-- 6 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 0924b6c6eb5e6..3df9cb051e6cf 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -53,3 +53,4 @@ export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const SIGNALS_INDEX_KEY = 'signalsIndex'; export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals`; export const DETECTION_ENGINE_SIGNALS_STATUS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/status`; +export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_STATUS_URL}/search`; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 08779a6b815e9..16f9d5993c4eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -7,9 +7,16 @@ import { ServerInjectOptions } from 'hapi'; import { ActionResult } from '../../../../../../actions/server/types'; import { SignalsRestParams } from '../../signals/types'; +import { + RuleAlertParamsRest, + RuleAlertType, + SignalsStatusRestParams, + SignalsQueryRestParams, +} from '../../alerts/types'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_QUERY_SIGNALS_URL, } from '../../../../../common/constants'; import { RuleAlertType } from '../../rules/types'; import { RuleAlertParamsRest } from '../../types'; @@ -50,6 +57,10 @@ export const typicalSetStatusSignalByQueryPayload = (): Partial => ({ + search_query: { query: { match_all: {} } }, +}); + export const setStatusSignalMissingIdsAndQueryPayload = (): Partial => ({ status: 'closed', }); @@ -134,6 +145,12 @@ export const getSetSignalStatusByQueryRequest = (): ServerInjectOptions => ({ }, }); +export const getSignalsQueryRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: { ...typicalSignalsQuery() }, +}); + export const createActionResult = (): ActionResult => ({ id: 'result-1', actionTypeId: 'action-id-1', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts index 2f6a186b94ed2..cf0ee08b2c176 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts @@ -224,5 +224,5 @@ export const setSignalsStatusSchema = Joi.object({ }).xor('signal_ids', 'query'); export const querySignalsSchema = Joi.object({ - search_query: Joi.object(), + search_query: Joi.object().required(), }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts new file mode 100644 index 0000000000000..ebd3f0fd219cc --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { createMockServer } from '../__mocks__/_mock_server'; +import { querySignalsRoute } from './query_signals_route'; +import * as myUtils from '../utils'; +import { ServerInjectOptions } from 'hapi'; +import { getSignalsQueryRequest, typicalSignalsQuery } from '../__mocks__/request_responses'; +import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; + +describe('query for signal', () => { + let { server, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(myUtils, 'getIndex').mockReturnValue('fakeindex'); + ({ server, elasticsearch } = createMockServer()); + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn(() => true), + })); + querySignalsRoute(server); + }); + + describe('status on signal', () => { + test('returns 200 when setting a status on a signal by ids', async () => { + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn( + (_req, _type: string, queryBody: { index: string; body: object }) => { + expect(queryBody.body).toMatchObject({ ...typicalSignalsQuery }); + return true; + } + ), + })); + const { statusCode } = await server.inject(getSignalsQueryRequest()); + expect(statusCode).toBe(200); + expect(myUtils.getIndex).toHaveReturnedWith('fakeindex'); + }); + }); + + describe('validation', () => { + test('returns 200 if search_query present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: typicalSignalsQuery(), + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 400 if search_query is not present', async () => { + const { search_query: sq, ...otherThings } = typicalSignalsQuery(); + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: otherThings, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(400); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index edd47703f8e91..7552933b9acdc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -5,7 +5,7 @@ */ import Hapi from 'hapi'; -import { DETECTION_ENGINE_SIGNALS_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; import { SignalsQueryRequest } from '../../alerts/types'; import { querySignalsSchema } from '../schemas'; import { ServerFacade } from '../../../../types'; @@ -13,19 +13,19 @@ import { transformError, getIndex } from '../utils'; export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => { return { - method: 'GET', - path: DETECTION_ENGINE_SIGNALS_URL, + method: 'POST', + path: DETECTION_ENGINE_QUERY_SIGNALS_URL, options: { tags: ['access:siem'], validate: { options: { abortEarly: false, }, - query: querySignalsSchema, + payload: querySignalsSchema, }, }, async handler(request: SignalsQueryRequest, _headers) { - const { query: searchQuery } = request; + const { search_query: searchQuery } = request.payload; const index = getIndex(request, server); const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); try { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh index 259953db7f7cd..4508657afb411 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh @@ -11,10 +11,10 @@ set -e # Example: ./set_status_with_id.sh closed # Example: ./set_status_with_id.sh open - curl -s -k \ + curl -v -k \ -H 'Content-Type: application/json' \ -H 'kbn-xsrf: 123' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals \ - -d '{"search_query": { "query": { "match_all": {} } } }' \ - | jq . + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/search \ + -d '{"search_query": { "query": { "match_all": {} } } }' + #| jq . From bd53f0d92b89a12f853d30cab3493cf4cca2f3bb Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Tue, 10 Dec 2019 12:45:54 -0500 Subject: [PATCH 3/5] fixes stuff after rebase with master --- .../legacy/plugins/siem/common/constants.ts | 2 +- .../lib/detection_engine/alerts/types.ts | 277 -- .../routes/__mocks__/request_responses.ts | 10 +- .../detection_engine/routes/schemas.test.ts | 2409 ----------------- .../lib/detection_engine/routes/schemas.ts | 228 -- .../schemas/query_signals_index_schema.ts | 11 + .../schemas/set_signal_status_schema.test.ts | 14 +- .../signals/open_close_signals_route.ts | 4 +- .../routes/signals/query_signals_route.ts | 6 +- .../scripts/signals/query_signals.sh | 7 +- .../lib/detection_engine/signals/types.ts | 26 +- 11 files changed, 51 insertions(+), 2943 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 3df9cb051e6cf..c3494c0969900 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -53,4 +53,4 @@ export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; export const SIGNALS_INDEX_KEY = 'signalsIndex'; export const DETECTION_ENGINE_SIGNALS_URL = `${DETECTION_ENGINE_URL}/signals`; export const DETECTION_ENGINE_SIGNALS_STATUS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/status`; -export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_STATUS_URL}/search`; +export const DETECTION_ENGINE_QUERY_SIGNALS_URL = `${DETECTION_ENGINE_SIGNALS_URL}/search`; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts deleted file mode 100644 index e4fa376b3f111..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash/fp'; - -import { SIGNALS_ID } from '../../../../common/constants'; -import { - Alert, - AlertType, - State, - AlertExecutorOptions, -} from '../../../../../alerting/server/types'; -import { AlertsClient } from '../../../../../alerting/server/alerts_client'; -import { ActionsClient } from '../../../../../actions/server/actions_client'; -import { RequestFacade } from '../../../types'; -import { SearchResponse } from '../../types'; -import { esFilters } from '../../../../../../../../src/plugins/data/server'; - -export type PartialFilter = Partial; - -export interface IMitreAttack { - id: string; - name: string; - reference: string; -} -export interface ThreatParams { - framework: string; - tactic: IMitreAttack; - techniques: IMitreAttack[]; -} -export interface RuleAlertParams { - description: string; - enabled: boolean; - falsePositives: string[]; - filter: Record | undefined | null; - filters: PartialFilter[] | undefined | null; - from: string; - immutable: boolean; - index: string[]; - interval: string; - ruleId: string | undefined | null; - language: string | undefined | null; - maxSignals: number; - riskScore: number; - outputIndex: string; - name: string; - query: string | undefined | null; - references: string[]; - savedId: string | undefined | null; - meta: Record | undefined | null; - severity: string; - tags: string[]; - to: string; - threats: ThreatParams[] | undefined | null; - type: 'filter' | 'query' | 'saved_query'; -} - -export type RuleAlertParamsRest = Omit< - RuleAlertParams, - 'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex' -> & { - rule_id: RuleAlertParams['ruleId']; - false_positives: RuleAlertParams['falsePositives']; - saved_id: RuleAlertParams['savedId']; - max_signals: RuleAlertParams['maxSignals']; - risk_score: RuleAlertParams['riskScore']; - output_index: RuleAlertParams['outputIndex']; -}; - -export interface SignalsStatusParams { - signalIds: string[] | undefined | null; - query: object | undefined | null; - status: 'open' | 'closed'; -} - -export interface SignalQueryParams { - searchQuery: object; -} - -export type SignalsStatusRestParams = Omit & { - signal_ids: SignalsStatusParams['signalIds']; -}; - -export type SignalsQueryRestParams = Omit & { - search_query: SignalQueryParams['searchQuery']; -}; - -export type OutputRuleAlertRest = RuleAlertParamsRest & { - id: string; - created_by: string | undefined | null; - updated_by: string | undefined | null; -}; - -export type UpdateRuleAlertParamsRest = Partial & { - id: string | undefined; - rule_id: RuleAlertParams['ruleId'] | undefined; -}; - -export interface FindParamsRest { - per_page: number; - page: number; - sort_field: string; - sort_order: 'asc' | 'desc'; - fields: string[]; - filter: string; -} - -export interface Clients { - alertsClient: AlertsClient; - actionsClient: ActionsClient; -} - -export type RuleParams = RuleAlertParams & Clients; - -export type UpdateRuleParams = Partial & { - id: string | undefined | null; -} & Clients; - -export type DeleteRuleParams = Clients & { - id: string | undefined; - ruleId: string | undefined | null; -}; - -export interface FindRulesRequest extends Omit { - query: { - per_page: number; - page: number; - search?: string; - sort_field?: string; - filter?: string; - fields?: string[]; - sort_order?: 'asc' | 'desc'; - }; -} - -export interface FindRuleParams { - alertsClient: AlertsClient; - perPage?: number; - page?: number; - sortField?: string; - filter?: string; - fields?: string[]; - sortOrder?: 'asc' | 'desc'; -} - -export interface ReadRuleParams { - alertsClient: AlertsClient; - id?: string | undefined | null; - ruleId?: string | undefined | null; -} - -export interface ReadRuleByRuleId { - alertsClient: AlertsClient; - ruleId: string; -} - -export type RuleTypeParams = Omit; - -export type RuleAlertType = Alert & { - id: string; - params: RuleTypeParams; -}; - -export interface RulesRequest extends RequestFacade { - payload: RuleAlertParamsRest; -} - -export interface SignalsStatusRequest extends RequestFacade { - payload: SignalsStatusRestParams; -} - -export interface SignalsQueryRequest extends RequestFacade { - payload: SignalsQueryRestParams; -} - -export interface UpdateRulesRequest extends RequestFacade { - payload: UpdateRuleAlertParamsRest; -} - -export type RuleExecutorOptions = Omit & { - params: RuleAlertParams & { - scrollSize: number; - scrollLock: string; - }; -}; - -export type SearchTypes = - | string - | string[] - | number - | number[] - | boolean - | boolean[] - | object - | object[]; - -export interface SignalSource { - [key: string]: SearchTypes; - '@timestamp': string; -} - -export interface BulkResponse { - took: number; - errors: boolean; - items: [ - { - create: { - _index: string; - _type?: string; - _id: string; - _version: number; - result?: string; - _shards?: { - total: number; - successful: number; - failed: number; - }; - _seq_no?: number; - _primary_term?: number; - status: number; - error?: { - type: string; - reason: string; - index_uuid?: string; - shard: string; - index: string; - }; - }; - } - ]; -} - -export interface MGetResponse { - docs: GetResponse[]; -} -export interface GetResponse { - _index: string; - _type: string; - _id: string; - _version: number; - _seq_no: number; - _primary_term: number; - found: boolean; - _source: SearchTypes; -} - -export type SignalSearchResponse = SearchResponse; -export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; - -export type QueryRequest = Omit & { - query: { id: string | undefined; rule_id: string | undefined }; -}; - -// This returns true because by default a RuleAlertTypeDefinition is an AlertType -// since we are only increasing the strictness of params. -export const isAlertExecutor = (obj: RuleAlertTypeDefinition): obj is AlertType => { - return true; -}; - -export type RuleAlertTypeDefinition = Omit & { - executor: ({ services, params, state }: RuleExecutorOptions) => Promise; -}; - -export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => { - return obj.every(rule => isAlertType(rule)); -}; - -export const isAlertType = (obj: unknown): obj is RuleAlertType => { - return get('alertTypeId', obj) === SIGNALS_ID; -}; - -export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => { - return objArray.length === 0 || isAlertType(objArray[0]); -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 16f9d5993c4eb..86cc090b3ba83 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,13 +6,7 @@ import { ServerInjectOptions } from 'hapi'; import { ActionResult } from '../../../../../../actions/server/types'; -import { SignalsRestParams } from '../../signals/types'; -import { - RuleAlertParamsRest, - RuleAlertType, - SignalsStatusRestParams, - SignalsQueryRestParams, -} from '../../alerts/types'; +import { SignalsStatusRestParams, SignalsQueryRestParams } from '../../signals/types'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -47,7 +41,7 @@ export const typicalPayload = (): Partial> = ], }); -export const typicalSetStatusSignalByIdsPayload = (): Partial => ({ +export const typicalSetStatusSignalByIdsPayload = (): Partial => ({ signal_ids: ['somefakeid1', 'somefakeid2'], status: 'closed', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts deleted file mode 100644 index 3e679269ee012..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.test.ts +++ /dev/null @@ -1,2409 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - createRulesSchema, - updateRulesSchema, - findRulesSchema, - queryRulesSchema, - setSignalsStatusSchema, -} from './schemas'; -import { - RuleAlertParamsRest, - FindParamsRest, - UpdateRuleAlertParamsRest, - ThreatParams, - SignalsStatusRestParams, -} from '../alerts/types'; - -describe('schemas', () => { - describe('create rules schema', () => { - test('empty objects do not validate', () => { - expect(createRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - createRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[rule_id] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - interval: '5m', - index: ['index-1'], - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, name, severity, type, query, index, interval] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - risk_score: 50, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - test('You can send in an empty array to threats', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).error - ).toBeFalsy(); - }); - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - threats: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then filter is required', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then query is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - - test('allows references to be sent as valid', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('defaults references to an array', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual([]); - }); - - test('references cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { references: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { index: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('defaults interval to 5 min', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - type: 'query', - }).value.interval - ).toEqual('5m'); - }); - - test('defaults max signals to 100', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).value.max_signals - ).toEqual(100); - }); - - test('filter and filters cannot exist together', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - - test('saved_id is required when type is saved_query and will not validate without out', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - }).error - ).toBeTruthy(); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('filters cannot be a string', () => { - expect( - createRulesSchema.validate< - Partial & { filters: string }> - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('saved_query type cannot have filter with it', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - - test('language validates with kuery', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - risk_score: 50, - output_index: '.siem-signals', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can optionally send in an array of tags', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: ['tag_1', 'tag_2'], - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of tags that are numbers', () => { - expect( - createRulesSchema.validate> & { tags: number[] }>( - { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - tags: [0, 1, 2], - } - ).error - ).toBeTruthy(); - }); - - test('You cannot send in an array of threats that are missing "framework"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "tactic"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('You cannot send in an array of threats that are missing "techniques"', () => { - expect( - createRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - - test('You can optionally send in an array of false positives', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: ['false_1', 'false_2'], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot send in an array of false positives that are numbers', () => { - expect( - createRulesSchema.validate< - Partial> & { false_positives: number[] } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - false_positives: [5, 4], - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can optionally set the immutable to be true', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You cannot set the immutable to be a number', () => { - expect( - createRulesSchema.validate< - Partial> & { immutable: number } - >({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: 5, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to 101', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 101, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You cannot set the risk_score to -1', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: -1, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeTruthy(); - }); - - test('You can set the risk_score to 0', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 0, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set the risk_score to 100', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 100, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can set meta to any object you want', () => { - expect( - createRulesSchema.validate>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }).error - ).toBeFalsy(); - }); - - test('You cannot create meta as a string', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('You can have an empty query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: '', - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('You can omit the query string when filters are present', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('query string defaults to empty string when present with filters', () => { - expect( - createRulesSchema.validate & { meta: string }>>({ - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - immutable: true, - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - language: 'kuery', - filters: [], - max_signals: 1, - }).value.query - ).toEqual(''); - }); - }); - - describe('update rules schema', () => { - test('empty objects do not validate as they require at least id or rule_id', () => { - expect(updateRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('made up values do not validate', () => { - expect( - updateRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('[id] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[rule_id] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - }).error - ).toBeFalsy(); - }); - - test('[id and rule_id] does not validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'id-1', - rule_id: 'rule-1', - }).error - ).toBeTruthy(); - }); - - test('[rule_id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, description] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - }).error - ).toBeFalsy(); - }); - - test('[id, risk_score] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - risk_score: 10, - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, name, severity, type, interval] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('[rule_id, description, from, to, index, name, severity, type, filter] does validate', () => { - expect( - updateRulesSchema.validate>({ - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - - test('[id, description, from, to, index, name, severity, type, filter] does validate', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then filter is still not required', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - }).error - ).toBeFalsy(); - }); - - test('If filter type is set then query is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - query: 'some query value', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then language is not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('If filter type is set then filters are not allowed', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'filter', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - - test('allows references to be sent as a valid value to update with', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('does not default references to an array', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).value.references - ).toEqual(undefined); - }); - - test('does not default interval', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - type: 'query', - }).value.interval - ).toEqual(undefined); - }); - - test('does not default max signal', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - }).value.max_signals - ).toEqual(undefined); - }); - - test('references cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { references: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - references: [5], - }).error - ).toBeTruthy(); - }); - - test('indexes cannot be numbers', () => { - expect( - updateRulesSchema.validate< - Partial> & { index: number[] } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: [5], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - query: 'some-query', - language: 'kuery', - }).error - ).toBeTruthy(); - }); - - test('filter and filters cannot exist together', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - filter: {}, - filters: [], - }).error - ).toBeTruthy(); - }); - - test('saved_id is not required when type is saved_query and will validate without it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - }).error - ).toBeFalsy(); - }); - - test('saved_id validates with saved_query', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - }).error - ).toBeFalsy(); - }); - - test('saved_query type can have filters with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filters: [], - }).error - ).toBeFalsy(); - }); - - test('saved_query type cannot have filter with it', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'saved_query', - saved_id: 'some id', - filter: {}, - }).error - ).toBeTruthy(); - }); - - test('language validates with kuery', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - }).error - ).toBeFalsy(); - }); - - test('language validates with lucene', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'lucene', - }).error - ).toBeFalsy(); - }); - - test('language does not validate with something made up', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'something-made-up', - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be negative', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: -1, - }).error - ).toBeTruthy(); - }); - - test('max_signals cannot be zero', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 0, - }).error - ).toBeTruthy(); - }); - - test('max_signals can be 1', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).error - ).toBeFalsy(); - }); - - test('meta can be updated', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - meta: { whateverYouWant: 'anything_at_all' }, - }).error - ).toBeFalsy(); - }); - - test('You update meta as a string', () => { - expect( - updateRulesSchema.validate< - Partial & { meta: string }> - >({ - id: 'rule-1', - meta: 'should not work', - }).error - ).toBeTruthy(); - }); - - test('filters cannot be a string', () => { - expect( - updateRulesSchema.validate< - Partial & { filters: string }> - >({ - rule_id: 'rule-1', - type: 'query', - filters: 'some string', - }).error - ).toBeTruthy(); - }); - - test('threats is not defaulted to empty array on update', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - }).value.threats - ).toBe(undefined); - }); - - test('threats is not defaulted to undefined on update with empty array', () => { - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [], - }).value.threats - ).toMatchObject([]); - }); - test('threats is valid when updated with all sub-objects', () => { - const expected: ThreatParams[] = [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ]; - expect( - updateRulesSchema.validate>({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).value.threats - ).toMatchObject(expected); - }); - test('threats is invalid when updated with missing property framework', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing tactic sub-object', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - techniques: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - }).error - ).toBeTruthy(); - }); - test('threats is invalid when updated with missing techniques', () => { - expect( - updateRulesSchema.validate< - Partial> & { - threats: Array>>; - } - >({ - id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'severity', - interval: '5m', - type: 'query', - references: ['index-1'], - query: 'some query', - language: 'kuery', - max_signals: 1, - threats: [ - { - framework: 'fake', - tactic: { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - }, - ], - }).error - ).toBeTruthy(); - }); - }); - - describe('find rules schema', () => { - test('empty objects do validate', () => { - expect(findRulesSchema.validate>({}).error).toBeFalsy(); - }); - - test('all values validate', () => { - expect( - findRulesSchema.validate>({ - per_page: 5, - page: 1, - sort_field: 'some field', - fields: ['field 1', 'field 2'], - filter: 'some filter', - sort_order: 'asc', - }).error - ).toBeFalsy(); - }); - - test('made up parameters do not validate', () => { - expect( - findRulesSchema.validate>({ - madeUp: 'hi', - }).error - ).toBeTruthy(); - }); - - test('per_page validates', () => { - expect( - findRulesSchema.validate>({ per_page: 5 }).error - ).toBeFalsy(); - }); - - test('page validates', () => { - expect( - findRulesSchema.validate>({ page: 5 }).error - ).toBeFalsy(); - }); - - test('sort_field validates', () => { - expect( - findRulesSchema.validate>({ sort_field: 'some value' }).error - ).toBeFalsy(); - }); - - test('fields validates with a string', () => { - expect( - findRulesSchema.validate>({ fields: ['some value'] }).error - ).toBeFalsy(); - }); - - test('fields validates with multiple strings', () => { - expect( - findRulesSchema.validate>({ - fields: ['some value 1', 'some value 2'], - }).error - ).toBeFalsy(); - }); - - test('fields does not validate with a number', () => { - expect( - findRulesSchema.validate> & { fields: number[] }>({ - fields: [5], - }).error - ).toBeTruthy(); - }); - - test('per page has a default of 20', () => { - expect(findRulesSchema.validate>({}).value.per_page).toEqual(20); - }); - - test('page has a default of 1', () => { - expect(findRulesSchema.validate>({}).value.page).toEqual(1); - }); - - test('filter works with a string', () => { - expect( - findRulesSchema.validate>({ - filter: 'some value 1', - }).error - ).toBeFalsy(); - }); - - test('filter does not work with a number', () => { - expect( - findRulesSchema.validate> & { filter: number }>({ - filter: 5, - }).error - ).toBeTruthy(); - }); - - test('sort_order requires sort_field to work', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - }).error - ).toBeTruthy(); - }); - - test('sort_order and sort_field validate together', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'asc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order validates with desc and sort_field', () => { - expect( - findRulesSchema.validate>({ - sort_order: 'desc', - sort_field: 'some field', - }).error - ).toBeFalsy(); - }); - - test('sort_order does not validate with a string other than asc and desc', () => { - expect( - findRulesSchema.validate< - Partial> & { sort_order: string } - >({ - sort_order: 'some other string', - sort_field: 'some field', - }).error - ).toBeTruthy(); - }); - }); - - describe('queryRulesSchema', () => { - test('empty objects do not validate', () => { - expect(queryRulesSchema.validate>({}).error).toBeTruthy(); - }); - - test('both rule_id and id being supplied dot not validate', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1', id: '1' }) - .error - ).toBeTruthy(); - }); - - test('only id validates', () => { - expect( - queryRulesSchema.validate>({ id: '1' }).error - ).toBeFalsy(); - }); - - test('only rule_id validates', () => { - expect( - queryRulesSchema.validate>({ rule_id: '1' }).error - ).toBeFalsy(); - }); - }); - - describe('set signal status schema', () => { - test('signal_ids and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('query and status is valid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - status: 'open', - }).error - ).toBeFalsy(); - }); - - test('signal_ids and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - signal_ids: ['somefakeid'], - }).error - ).toBeTruthy(); - }); - - test('query and missing status is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - query: {}, - }).error - ).toBeTruthy(); - }); - - test('status is present but query or signal_ids is missing is invalid', () => { - expect( - setSignalsStatusSchema.validate>({ - status: 'closed', - }).error - ).toBeTruthy(); - }); - - test('signal_ids is present but status has wrong value', () => { - expect( - setSignalsStatusSchema.validate< - Partial< - Omit & { - status: string; - } - > - >({ - status: 'fakeVal', - }).error - ).toBeTruthy(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts deleted file mode 100644 index cf0ee08b2c176..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas.ts +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; - -/* eslint-disable @typescript-eslint/camelcase */ -const description = Joi.string(); -const enabled = Joi.boolean(); -const false_positives = Joi.array().items(Joi.string()); -const filter = Joi.object(); -const filters = Joi.array(); -const from = Joi.string(); -const immutable = Joi.boolean(); -const rule_id = Joi.string(); -const id = Joi.string(); -const index = Joi.array() - .items(Joi.string()) - .single(); -const interval = Joi.string(); -const query = Joi.string(); -const language = Joi.string().valid('kuery', 'lucene'); -const output_index = Joi.string(); -const saved_id = Joi.string(); -const meta = Joi.object(); -const max_signals = Joi.number().greater(0); -const name = Joi.string(); -const risk_score = Joi.number() - .greater(-1) - .less(101); -const severity = Joi.string(); -const status = Joi.string().valid('open', 'closed'); -const to = Joi.string(); -const type = Joi.string().valid('filter', 'query', 'saved_query'); -const queryFilter = Joi.string(); -const references = Joi.array() - .items(Joi.string()) - .single(); -const per_page = Joi.number() - .min(0) - .default(20); -const page = Joi.number() - .min(1) - .default(1); -const signal_ids = Joi.array().items(Joi.string()); -const signal_status_query = Joi.object(); -const sort_field = Joi.string(); -const sort_order = Joi.string().valid('asc', 'desc'); -const tags = Joi.array().items(Joi.string()); -const fields = Joi.array() - .items(Joi.string()) - .single(); -const threat_framework = Joi.string(); -const threat_tactic_id = Joi.string(); -const threat_tactic_name = Joi.string(); -const threat_tactic_reference = Joi.string(); -const threat_tactic = Joi.object({ - id: threat_tactic_id.required(), - name: threat_tactic_name.required(), - reference: threat_tactic_reference.required(), -}); -const threat_technique_id = Joi.string(); -const threat_technique_name = Joi.string(); -const threat_technique_reference = Joi.string(); -const threat_technique = Joi.object({ - id: threat_technique_id.required(), - name: threat_technique_name.required(), - reference: threat_technique_reference.required(), -}); -const threat_techniques = Joi.array().items(threat_technique.required()); - -const threats = Joi.array().items( - Joi.object({ - framework: threat_framework.required(), - tactic: threat_tactic.required(), - techniques: threat_techniques.required(), - }) -); -/* eslint-enable @typescript-eslint/camelcase */ - -export const createRulesSchema = Joi.object({ - description: description.required(), - enabled: enabled.default(true), - false_positives: false_positives.default([]), - filter: filter.when('type', { is: 'filter', then: Joi.required(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), - from: from.required(), - rule_id, - immutable: immutable.default(false), - index, - interval: interval.default('5m'), - query: Joi.when('type', { - is: 'query', - then: Joi.when('filters', { - is: Joi.exist(), - then: query - .optional() - .allow('') - .default(''), - otherwise: Joi.required(), - }), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.required(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), - output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.required(), - otherwise: Joi.forbidden(), - }), - meta, - risk_score: risk_score.required(), - max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), - name: name.required(), - severity: severity.required(), - tags: tags.default([]), - to: to.required(), - type: type.required(), - threats: threats.default([]), - references: references.default([]), -}); - -export const updateRulesSchema = Joi.object({ - description, - enabled, - false_positives, - filter: filter.when('type', { is: 'filter', then: Joi.optional(), otherwise: Joi.forbidden() }), - filters: Joi.when('type', { - is: 'query', - then: filters.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: filters.optional(), - otherwise: Joi.forbidden(), - }), - }), - from, - rule_id, - id, - immutable, - index, - interval, - query: Joi.when('type', { - is: 'query', - then: query.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: query.optional(), - otherwise: Joi.forbidden(), - }), - }), - language: Joi.when('type', { - is: 'query', - then: language.optional(), - otherwise: Joi.when('type', { - is: 'saved_query', - then: language.optional(), - otherwise: Joi.forbidden(), - }), - }), - output_index, - saved_id: saved_id.when('type', { - is: 'saved_query', - then: Joi.optional(), - otherwise: Joi.forbidden(), - }), - meta, - risk_score, - max_signals, - name, - severity, - tags, - to, - type, - threats, - references, -}).xor('id', 'rule_id'); - -export const queryRulesSchema = Joi.object({ - rule_id, - id, -}).xor('id', 'rule_id'); - -export const findRulesSchema = Joi.object({ - fields, - filter: queryFilter, - per_page, - page, - sort_field: Joi.when(Joi.ref('sort_order'), { - is: Joi.exist(), - then: sort_field.required(), - otherwise: sort_field.optional(), - }), - sort_order, -}); - -export const setSignalsStatusSchema = Joi.object({ - signal_ids, - query: signal_status_query, - status: status.required(), -}).xor('signal_ids', 'query'); - -export const querySignalsSchema = Joi.object({ - search_query: Joi.object().required(), -}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts new file mode 100644 index 0000000000000..4328e36e3c8ac --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts @@ -0,0 +1,11 @@ +/* + * 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 Joi from 'joi'; + +export const querySignalsSchema = Joi.object({ + search_query: Joi.object().required(), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts index b586b4666bfee..792c7afad05b1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/set_signal_status_schema.test.ts @@ -5,12 +5,12 @@ */ import { setSignalsStatusSchema } from './set_signal_status_schema'; -import { SignalsRestParams } from '../../signals/types'; +import { SignalsStatusRestParams } from '../../signals/types'; describe('set signal status schema', () => { test('signal_ids and status is valid', () => { expect( - setSignalsStatusSchema.validate>({ + setSignalsStatusSchema.validate>({ signal_ids: ['somefakeid'], status: 'open', }).error @@ -19,7 +19,7 @@ describe('set signal status schema', () => { test('query and status is valid', () => { expect( - setSignalsStatusSchema.validate>({ + setSignalsStatusSchema.validate>({ query: {}, status: 'open', }).error @@ -28,7 +28,7 @@ describe('set signal status schema', () => { test('signal_ids and missing status is invalid', () => { expect( - setSignalsStatusSchema.validate>({ + setSignalsStatusSchema.validate>({ signal_ids: ['somefakeid'], }).error ).toBeTruthy(); @@ -36,7 +36,7 @@ describe('set signal status schema', () => { test('query and missing status is invalid', () => { expect( - setSignalsStatusSchema.validate>({ + setSignalsStatusSchema.validate>({ query: {}, }).error ).toBeTruthy(); @@ -44,7 +44,7 @@ describe('set signal status schema', () => { test('status is present but query or signal_ids is missing is invalid', () => { expect( - setSignalsStatusSchema.validate>({ + setSignalsStatusSchema.validate>({ status: 'closed', }).error ).toBeTruthy(); @@ -54,7 +54,7 @@ describe('set signal status schema', () => { expect( setSignalsStatusSchema.validate< Partial< - Omit & { + Omit & { status: string; } > diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 931ed8a133004..009cb0bf50b59 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; -import { SignalsStatusRequest } from '../../alerts/types'; -import { setSignalsStatusSchema } from '../schemas'; +import { SignalsStatusRequest } from '../../signals/types'; +import { setSignalsStatusSchema } from '../schemas/set_signal_status_schema'; import { ServerFacade } from '../../../../types'; import { transformError, getIndex } from '../utils'; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index 7552933b9acdc..76d5f229dab02 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; -import { SignalsQueryRequest } from '../../alerts/types'; -import { querySignalsSchema } from '../schemas'; +import { SignalsQueryRequest } from '../../signals/types'; +import { querySignalsSchema } from '../schemas/query_signals_index_schema'; import { ServerFacade } from '../../../../types'; import { transformError, getIndex } from '../utils'; @@ -27,7 +27,7 @@ export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => async handler(request: SignalsQueryRequest, _headers) { const { search_query: searchQuery } = request.payload; const index = getIndex(request, server); - const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data'); + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); try { return callWithRequest(request, 'search', { index, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh index 4508657afb411..0b4163ef58998 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh @@ -9,12 +9,11 @@ set -e ./check_env_variables.sh -# Example: ./set_status_with_id.sh closed -# Example: ./set_status_with_id.sh open +# Example: ./signals/query_signals.sh curl -v -k \ -H 'Content-Type: application/json' \ -H 'kbn-xsrf: 123' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/search \ - -d '{"search_query": { "query": { "match_all": {} } } }' - #| jq . + -d '{"search_query": { "query": { "match_all": {} } } }' \ + | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index 213ceb29a6e25..29d5266e04106 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -15,12 +15,30 @@ export interface SignalsParams { status: 'open' | 'closed'; } -export type SignalsRestParams = Omit & { - signal_ids: SignalsParams['signalIds']; +export interface SignalsStatusParams { + signalIds: string[] | undefined | null; + query: object | undefined | null; + status: 'open' | 'closed'; +} + +export interface SignalQueryParams { + searchQuery: object; +} + +export type SignalsStatusRestParams = Omit & { + signal_ids: SignalsStatusParams['signalIds']; +}; + +export type SignalsQueryRestParams = Omit & { + search_query: SignalQueryParams['searchQuery']; }; -export interface SignalsRequest extends RequestFacade { - payload: SignalsRestParams; +export interface SignalsStatusRequest extends RequestFacade { + payload: SignalsStatusRestParams; +} + +export interface SignalsQueryRequest extends RequestFacade { + payload: SignalsQueryRestParams; } export type SearchTypes = From aa8a08b345bb590ac2e4101d20f6624e21fed31d Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Tue, 10 Dec 2019 18:07:39 -0500 Subject: [PATCH 4/5] utilizes removes search_query from payload and replaces it with just query, adds aggs to signals search api, updates route and validation tests --- .../routes/__mocks__/request_responses.ts | 12 ++- .../query_signals_index_schema.test.ts | 39 +++++++++ .../schemas/query_signals_index_schema.ts | 5 +- .../signals/query_signals_route.test.ts | 82 +++++++++++++++++-- .../routes/signals/query_signals_route.ts | 5 +- .../scripts/signals/aggs_signals.sh | 19 +++++ .../scripts/signals/query_signals.sh | 4 +- .../lib/detection_engine/signals/types.ts | 7 +- 8 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts create mode 100755 x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 86cc090b3ba83..86726187c4fbd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -52,7 +52,11 @@ export const typicalSetStatusSignalByQueryPayload = (): Partial => ({ - search_query: { query: { match_all: {} } }, + query: { match_all: {} }, +}); + +export const typicalSignalsQueryAggs = (): Partial => ({ + aggs: { statuses: { terms: { field: 'signal.status', size: 10 } } }, }); export const setStatusSignalMissingIdsAndQueryPayload = (): Partial => ({ @@ -145,6 +149,12 @@ export const getSignalsQueryRequest = (): ServerInjectOptions => ({ payload: { ...typicalSignalsQuery() }, }); +export const getSignalsAggsQueryRequest = (): ServerInjectOptions => ({ + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: { ...typicalSignalsQueryAggs() }, +}); + export const createActionResult = (): ActionResult => ({ id: 'result-1', actionTypeId: 'action-id-1', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts new file mode 100644 index 0000000000000..4f0dbf10f4559 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { querySignalsSchema } from './query_signals_index_schema'; +import { SignalsQueryRestParams } from '../../signals/types'; + +describe('query and aggs on signals index', () => { + test('query and aggs simultaneously', () => { + expect( + querySignalsSchema.validate>({ + query: {}, + aggs: {}, + }).error + ).toBeFalsy(); + }); + + test('query only', () => { + expect( + querySignalsSchema.validate>({ + query: {}, + }).error + ).toBeFalsy(); + }); + + test('aggs only', () => { + expect( + querySignalsSchema.validate>({ + aggs: {}, + }).error + ).toBeFalsy(); + }); + + test('missing query and aggs is invalid', () => { + expect(querySignalsSchema.validate>({}).error).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts index 4328e36e3c8ac..53ce50692e84a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/query_signals_index_schema.ts @@ -7,5 +7,6 @@ import Joi from 'joi'; export const querySignalsSchema = Joi.object({ - search_query: Joi.object().required(), -}); + query: Joi.object(), + aggs: Joi.object(), +}).min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts index ebd3f0fd219cc..1b990e8c1ff57 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.test.ts @@ -8,7 +8,12 @@ import { createMockServer } from '../__mocks__/_mock_server'; import { querySignalsRoute } from './query_signals_route'; import * as myUtils from '../utils'; import { ServerInjectOptions } from 'hapi'; -import { getSignalsQueryRequest, typicalSignalsQuery } from '../__mocks__/request_responses'; +import { + getSignalsQueryRequest, + getSignalsAggsQueryRequest, + typicalSignalsQuery, + typicalSignalsQueryAggs, +} from '../__mocks__/request_responses'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; describe('query for signal', () => { @@ -24,24 +29,64 @@ describe('query for signal', () => { querySignalsRoute(server); }); - describe('status on signal', () => { - test('returns 200 when setting a status on a signal by ids', async () => { + describe('query and agg on signals index', () => { + test('returns 200 when using single query', async () => { elasticsearch.getCluster = jest.fn(() => ({ callWithRequest: jest.fn( (_req, _type: string, queryBody: { index: string; body: object }) => { - expect(queryBody.body).toMatchObject({ ...typicalSignalsQuery }); + expect(queryBody.body).toMatchObject({ ...typicalSignalsQueryAggs() }); return true; } ), })); - const { statusCode } = await server.inject(getSignalsQueryRequest()); + const { statusCode } = await server.inject(getSignalsAggsQueryRequest()); expect(statusCode).toBe(200); expect(myUtils.getIndex).toHaveReturnedWith('fakeindex'); }); + + test('returns 200 when using single agg', async () => { + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn( + (_req, _type: string, queryBody: { index: string; body: object }) => { + expect(queryBody.body).toMatchObject({ ...typicalSignalsQueryAggs() }); + return true; + } + ), + })); + const { statusCode } = await server.inject(getSignalsAggsQueryRequest()); + expect(statusCode).toBe(200); + expect(myUtils.getIndex).toHaveReturnedWith('fakeindex'); + }); + + test('returns 200 when using aggs and query together', async () => { + const allTogether = getSignalsQueryRequest(); + allTogether.payload = { ...typicalSignalsQueryAggs(), ...typicalSignalsQuery() }; + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn( + (_req, _type: string, queryBody: { index: string; body: object }) => { + expect(queryBody.body).toMatchObject({ + ...typicalSignalsQueryAggs(), + ...typicalSignalsQuery(), + }); + return true; + } + ), + })); + const { statusCode } = await server.inject(allTogether); + expect(statusCode).toBe(200); + expect(myUtils.getIndex).toHaveReturnedWith('fakeindex'); + }); + + test('returns 400 when missing aggs and query', async () => { + const allTogether = getSignalsQueryRequest(); + allTogether.payload = {}; + const { statusCode } = await server.inject(allTogether); + expect(statusCode).toBe(400); + }); }); describe('validation', () => { - test('returns 200 if search_query present', async () => { + test('returns 200 if query present', async () => { const request: ServerInjectOptions = { method: 'POST', url: DETECTION_ENGINE_QUERY_SIGNALS_URL, @@ -51,12 +96,31 @@ describe('query for signal', () => { expect(statusCode).toBe(200); }); - test('returns 400 if search_query is not present', async () => { - const { search_query: sq, ...otherThings } = typicalSignalsQuery(); + test('returns 200 if aggs is present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: typicalSignalsQueryAggs(), + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 200 if aggs and query are present', async () => { + const request: ServerInjectOptions = { + method: 'POST', + url: DETECTION_ENGINE_QUERY_SIGNALS_URL, + payload: { ...typicalSignalsQueryAggs(), ...typicalSignalsQuery() }, + }; + const { statusCode } = await server.inject(request); + expect(statusCode).toBe(200); + }); + + test('returns 400 if aggs and query are NOT present', async () => { const request: ServerInjectOptions = { method: 'POST', url: DETECTION_ENGINE_QUERY_SIGNALS_URL, - payload: otherThings, + payload: {}, }; const { statusCode } = await server.inject(request); expect(statusCode).toBe(400); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index 76d5f229dab02..fac57434b9e90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -25,13 +25,14 @@ export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => }, }, async handler(request: SignalsQueryRequest, _headers) { - const { search_query: searchQuery } = request.payload; + const { query, aggs } = request.payload; + const body = { query, aggs }; const index = getIndex(request, server); const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); try { return callWithRequest(request, 'search', { index, - body: searchQuery, + body, }); } catch (exc) { // error while getting or updating signal with id: id in signal index .siem-signals diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh new file mode 100755 index 0000000000000..c8501321184b4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./signals/query_signals.sh + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/search \ + -d '{"aggs": {"statuses": {"terms": {"field": "signal.status", "size": 10 }}}}' \ + | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh index 0b4163ef58998..2fc76406ec0f6 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/query_signals.sh @@ -10,10 +10,10 @@ set -e ./check_env_variables.sh # Example: ./signals/query_signals.sh - curl -v -k \ + curl -s -k \ -H 'Content-Type: application/json' \ -H 'kbn-xsrf: 123' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/signals/search \ - -d '{"search_query": { "query": { "match_all": {} } } }' \ + -d '{ "query": { "match_all": {} } } ' \ | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index 29d5266e04106..a30182c537884 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -22,16 +22,15 @@ export interface SignalsStatusParams { } export interface SignalQueryParams { - searchQuery: object; + query: object | undefined | null; + aggs: object | undefined | null; } export type SignalsStatusRestParams = Omit & { signal_ids: SignalsStatusParams['signalIds']; }; -export type SignalsQueryRestParams = Omit & { - search_query: SignalQueryParams['searchQuery']; -}; +export type SignalsQueryRestParams = SignalQueryParams; export interface SignalsStatusRequest extends RequestFacade { payload: SignalsStatusRestParams; From 7ec68f9267529fdedb0e54557a3d73f104b16bc8 Mon Sep 17 00:00:00 2001 From: Devin Hurley Date: Tue, 10 Dec 2019 18:55:48 -0500 Subject: [PATCH 5/5] removes _headers parameter from route handler and updates comment for aggs script --- .../detection_engine/routes/signals/open_close_signals_route.ts | 2 +- .../lib/detection_engine/routes/signals/query_signals_route.ts | 2 +- .../server/lib/detection_engine/scripts/signals/aggs_signals.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 009cb0bf50b59..7c49a1942ee91 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -24,7 +24,7 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute payload: setSignalsStatusSchema, }, }, - async handler(request: SignalsStatusRequest, _headers) { + async handler(request: SignalsStatusRequest) { const { signal_ids: signalIds, query, status } = request.payload; const index = getIndex(request, server); const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index fac57434b9e90..89ffed259cf77 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -24,7 +24,7 @@ export const querySignalsRouteDef = (server: ServerFacade): Hapi.ServerRoute => payload: querySignalsSchema, }, }, - async handler(request: SignalsQueryRequest, _headers) { + async handler(request: SignalsQueryRequest) { const { query, aggs } = request.payload; const body = { query, aggs }; const index = getIndex(request, server); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh index c8501321184b4..27186a14af902 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/signals/aggs_signals.sh @@ -9,7 +9,7 @@ set -e ./check_env_variables.sh -# Example: ./signals/query_signals.sh +# Example: ./signals/aggs_signal.sh curl -s -k \ -H 'Content-Type: application/json' \ -H 'kbn-xsrf: 123' \