[RAC][Security Solution] Add base Security Rule Type (#105096)

* injects bulkCreate and wrapHits to individual rule executors

* WIP create_security_rule_type_factory based on Marshall's work in #d3076ca54526ea0e61a9a99e1c1bce854806977e

* removes ruleStatusService from old rule executors, fixes executor unit tests

* fixes rebase

* Rename reference_rules to rule_types

* Fix type errors

* Fix type errors in base security rule factory

* Additional improvements to types and interfaces

* More type alignment

* Fix remaining type errors in query rule

* Add validation / inject lists plugin

* Formatting

* Improvements to typing

* Static typing on executors

* cleanup

* Hook up params for query/threshold rules... includes exceptionsList and daterange tuple

* Scaffolding for wrapHits and bulkCreate

* Add error handling / status reporting

* Fixup alert type state

* Begin threshold

* Begin work on threshold state

* Organize rule types

* Export base security rule types

* Fixup lifecycle static typing

* WrapHits / bulk changes

* Field mappings (partial)

* whoops

* Remove redundant params

* More flexibile implementation of bulkCreateFactory

* Add mappings

* Finish query rule

* Revert "Remove redundant params"

This reverts commit 87aff9c810.

* Revert "whoops"

This reverts commit a7771bd392.

* Fixup return types

* Use alertWithPersistence

* Fix import

* End-to-end rule mostly working

* Fix bulkCreate

* Bug fixes

* Bug fixes and mapping changes

* Fix indexing

* cleanup

* Fix type errors

* Test fixes

* Fix query tests

* cleanup / rename kibana.rac to kibana

* Remove eql/threshold (for now)

* Move technical fields to package

* Add indexAlias and buildRuleMessageFactory

* imports

* type errors

* Change 'kibana.rac.*' to 'kibana.*'

* Fix lifecycle tests

* Single alert instance

* fix import

* Fix type error

* Fix more type errors

* Fix query rule type test

* revert to previous ts-expect-error

* type errors again

* types / linting

* General readability improvements

* Add invariant function from Dmitrii's branch

* Use invariant and constants

* Improvements to field mappings

* More test failure fixes

* Add refresh param for bulk create

* Update more field refs

* Actually use refresh param

* cleanup

* test fixes

* changes to rule creation script

* Fix created signals count

* Use ruleId

* Updates to bulk indexing

* Mapping updates

* Cannot use 'strict' for dynamic setting

Co-authored-by: Marshall Main <marshall.main@elastic.co>
Co-authored-by: Ece Ozalp <ozale272@newschool.edu>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Madison Caldwell 2021-08-03 12:48:07 -04:00 committed by GitHub
parent bcb16c1b86
commit 8f9086b4c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 2547 additions and 1346 deletions

View file

@ -2122,7 +2122,7 @@
"signature": [
"(options: { fields: OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }) => { reason: string; link: string; }"
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }) => { reason: string; link: string; }"
],
"path": "x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts",
"deprecated": false,
@ -2138,7 +2138,7 @@
"signature": [
"{ fields: OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }"
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }"
],
"path": "x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts",
"deprecated": false
@ -2979,7 +2979,7 @@
"signature": [
"(options: { fields: OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }) => { reason: string; link: string; }"
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">> & Record<string, any>; formatters: { asDuration: (value: number | null | undefined, { defaultValue, extended }?: FormatterOptions) => string; asPercent: (numerator: number | null | undefined, denominator: number | undefined, fallbackResult?: string) => string; }; }) => { reason: string; link: string; }"
],
"path": "x-pack/plugins/observability/public/rules/create_observability_rule_type_registry.ts",
"deprecated": false,

View file

@ -525,7 +525,7 @@
"section": "def-server.AlertExecutorOptions",
"text": "AlertExecutorOptions"
},
"<any, any, any, any, any>) => { \"rule.id\": string; \"rule.uuid\": string; \"rule.category\": string; \"rule.name\": string; tags: string[]; \"kibana.rac.alert.producer\": string; }"
"<any, any, any, any, any>) => { \"rule.id\": string; \"rule.uuid\": string; \"rule.category\": string; \"rule.name\": string; tags: string[]; \"kibana.alert.producer\": string; }"
],
"path": "x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts",
"deprecated": false,
@ -740,7 +740,7 @@
"signature": [
"(alert: { id: string; fields: Record<string, unknown> & Partial<Pick<OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">>, \"tags\" | \"@timestamp\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">>; }) => Pick<",
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">>, \"tags\" | \"@timestamp\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">>; }) => Pick<",
"AlertInstance",
"<InstanceState, InstanceContext, ActionGroupIds>, \"getState\" | \"replaceState\" | \"scheduleActions\" | \"scheduleActionsWithSubGroup\">"
],
@ -758,7 +758,7 @@
"signature": [
"{ id: string; fields: Record<string, unknown> & Partial<Pick<OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">>, \"tags\" | \"@timestamp\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">>; }"
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">>, \"tags\" | \"@timestamp\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">>; }"
],
"path": "x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts",
"deprecated": false
@ -850,10 +850,10 @@
},
{
"parentPluginId": "ruleRegistry",
"id": "def-server.RuleExecutorData.PRODUCER",
"id": "def-server.RuleExecutorData.ALERT_PRODUCER",
"type": "string",
"tags": [],
"label": "[PRODUCER]",
"label": "[ALERT_PRODUCER]",
"description": [],
"path": "x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts",
"deprecated": false
@ -1147,7 +1147,7 @@
"signature": [
"(input: unknown) => OutputOf<",
"Optional",
"<{ readonly \"kibana.rac.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.rac.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.rac.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.rac.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.rac.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.rac.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.rac.alert.producer\" | \"kibana.rac.alert.owner\" | \"kibana.rac.alert.id\" | \"kibana.rac.alert.uuid\" | \"kibana.rac.alert.start\" | \"kibana.rac.alert.end\" | \"kibana.rac.alert.duration.us\" | \"kibana.rac.alert.severity.level\" | \"kibana.rac.alert.severity.value\" | \"kibana.rac.alert.status\" | \"kibana.rac.alert.evaluation.threshold\" | \"kibana.rac.alert.evaluation.value\" | \"kibana.rac.alert.reason\" | \"kibana.space_ids\">>"
"<{ readonly \"kibana.alert.owner\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.producer\": { readonly type: \"keyword\"; }; readonly \"kibana.space_ids\": { readonly type: \"keyword\"; readonly array: true; }; readonly \"kibana.alert.uuid\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.id\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.start\": { readonly type: \"date\"; }; readonly \"kibana.alert.end\": { readonly type: \"date\"; }; readonly \"kibana.alert.duration.us\": { readonly type: \"long\"; }; readonly \"kibana.alert.severity.level\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.severity.value\": { readonly type: \"long\"; }; readonly \"kibana.alert.status\": { readonly type: \"keyword\"; }; readonly \"kibana.alert.evaluation.threshold\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.evaluation.value\": { readonly type: \"scaled_float\"; readonly scaling_factor: 100; }; readonly \"kibana.alert.reason\": { readonly type: \"keyword\"; }; readonly tags: { readonly type: \"keyword\"; readonly array: true; readonly required: false; }; readonly '@timestamp': { readonly type: \"date\"; readonly array: false; readonly required: true; }; readonly 'event.kind': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'event.action': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.uuid': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.id': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.name': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; readonly 'rule.category': { readonly type: \"keyword\"; readonly array: false; readonly required: false; }; }, \"tags\" | \"event.kind\" | \"event.action\" | \"rule.uuid\" | \"rule.id\" | \"rule.name\" | \"rule.category\" | \"kibana.alert.producer\" | \"kibana.alert.owner\" | \"kibana.alert.id\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.severity.level\" | \"kibana.alert.severity.value\" | \"kibana.alert.status\" | \"kibana.alert.evaluation.threshold\" | \"kibana.alert.evaluation.value\" | \"kibana.alert.reason\" | \"kibana.space_ids\">>"
],
"path": "x-pack/plugins/rule_registry/common/parse_technical_fields.ts",
"deprecated": false,

View file

@ -8,79 +8,192 @@
import { ValuesType } from 'utility-types';
const ALERT_NAMESPACE = 'kibana.rac.alert' as const;
const KIBANA_NAMESPACE = 'kibana' as const;
const TIMESTAMP = '@timestamp' as const;
const EVENT_KIND = 'event.kind' as const;
const ALERT_NAMESPACE = `${KIBANA_NAMESPACE}.alert` as const;
const ALERT_RULE_NAMESPACE = `${ALERT_NAMESPACE}.rule` as const;
const CONSUMERS = `${KIBANA_NAMESPACE}.consumers` as const;
const ECS_VERSION = 'ecs.version' as const;
const EVENT_ACTION = 'event.action' as const;
const RULE_UUID = 'rule.uuid' as const;
const EVENT_KIND = 'event.kind' as const;
const RULE_CATEGORY = 'rule.category' as const;
const RULE_CONSUMERS = 'rule.consumers' as const;
const RULE_ID = 'rule.id' as const;
const RULE_NAME = 'rule.name' as const;
const RULE_CATEGORY = 'rule.category' as const;
const RULE_UUID = 'rule.uuid' as const;
const SPACE_IDS = `${KIBANA_NAMESPACE}.space_ids` as const;
const TAGS = 'tags' as const;
const PRODUCER = `${ALERT_NAMESPACE}.producer` as const;
const OWNER = `${ALERT_NAMESPACE}.owner` as const;
const ALERT_ID = `${ALERT_NAMESPACE}.id` as const;
const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const;
const ALERT_START = `${ALERT_NAMESPACE}.start` as const;
const ALERT_END = `${ALERT_NAMESPACE}.end` as const;
const TIMESTAMP = '@timestamp' as const;
const VERSION = `${KIBANA_NAMESPACE}.version` as const;
const ALERT_ACTION_GROUP = `${ALERT_NAMESPACE}.action_group` as const;
const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const;
const ALERT_SEVERITY_LEVEL = `${ALERT_NAMESPACE}.severity.level` as const;
const ALERT_SEVERITY_VALUE = `${ALERT_NAMESPACE}.severity.value` as const;
const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const;
const SPACE_IDS = 'kibana.space_ids' as const;
const ALERT_END = `${ALERT_NAMESPACE}.end` as const;
const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const;
const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const;
const ALERT_ID = `${ALERT_NAMESPACE}.id` as const;
const ALERT_OWNER = `${ALERT_NAMESPACE}.owner` as const;
const ALERT_PRODUCER = `${ALERT_NAMESPACE}.producer` as const;
const ALERT_REASON = `${ALERT_NAMESPACE}.reason` as const;
const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const;
const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const;
const ALERT_SEVERITY_LEVEL = `${ALERT_NAMESPACE}.severity.level` as const;
const ALERT_SEVERITY_VALUE = `${ALERT_NAMESPACE}.severity.value` as const;
const ALERT_START = `${ALERT_NAMESPACE}.start` as const;
const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const;
const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const;
const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const;
const ALERT_WORKFLOW_REASON = `${ALERT_NAMESPACE}.workflow_reason` as const;
const ALERT_WORKFLOW_STATUS = `${ALERT_NAMESPACE}.workflow_status` as const;
const ALERT_WORKFLOW_USER = `${ALERT_NAMESPACE}.workflow_user` as const;
const ALERT_RULE_AUTHOR = `${ALERT_RULE_NAMESPACE}.author` as const;
const ALERT_RULE_CONSUMERS = `${ALERT_RULE_NAMESPACE}.consumers` as const;
const ALERT_RULE_CREATED_AT = `${ALERT_RULE_NAMESPACE}.created_at` as const;
const ALERT_RULE_CREATED_BY = `${ALERT_RULE_NAMESPACE}.created_by` as const;
const ALERT_RULE_DESCRIPTION = `${ALERT_RULE_NAMESPACE}.description` as const;
const ALERT_RULE_ENABLED = `${ALERT_RULE_NAMESPACE}.enabled` as const;
const ALERT_RULE_FROM = `${ALERT_RULE_NAMESPACE}.from` as const;
const ALERT_RULE_ID = `${ALERT_RULE_NAMESPACE}.id` as const;
const ALERT_RULE_INTERVAL = `${ALERT_RULE_NAMESPACE}.interval` as const;
const ALERT_RULE_LICENSE = `${ALERT_RULE_NAMESPACE}.license` as const;
const ALERT_RULE_NAME = `${ALERT_RULE_NAMESPACE}.name` as const;
const ALERT_RULE_NOTE = `${ALERT_RULE_NAMESPACE}.note` as const;
const ALERT_RULE_REFERENCES = `${ALERT_RULE_NAMESPACE}.references` as const;
const ALERT_RULE_RISK_SCORE = `${ALERT_RULE_NAMESPACE}.risk_score` as const;
const ALERT_RULE_RISK_SCORE_MAPPING = `${ALERT_RULE_NAMESPACE}.risk_score_mapping` as const;
const ALERT_RULE_RULE_ID = `${ALERT_RULE_NAMESPACE}.rule_id` as const;
const ALERT_RULE_RULE_NAME_OVERRIDE = `${ALERT_RULE_NAMESPACE}.rule_name_override` as const;
const ALERT_RULE_SEVERITY = `${ALERT_RULE_NAMESPACE}.severity` as const;
const ALERT_RULE_SEVERITY_MAPPING = `${ALERT_RULE_NAMESPACE}.severity_mapping` as const;
const ALERT_RULE_TAGS = `${ALERT_RULE_NAMESPACE}.tags` as const;
const ALERT_RULE_TO = `${ALERT_RULE_NAMESPACE}.to` as const;
const ALERT_RULE_TYPE = `${ALERT_RULE_NAMESPACE}.type` as const;
const ALERT_RULE_UPDATED_AT = `${ALERT_RULE_NAMESPACE}.updated_at` as const;
const ALERT_RULE_UPDATED_BY = `${ALERT_RULE_NAMESPACE}.updated_by` as const;
const ALERT_RULE_VERSION = `${ALERT_RULE_NAMESPACE}.version` as const;
const fields = {
TIMESTAMP,
CONSUMERS,
ECS_VERSION,
EVENT_KIND,
EVENT_ACTION,
RULE_UUID,
RULE_CATEGORY,
RULE_CONSUMERS,
RULE_ID,
RULE_NAME,
RULE_CATEGORY,
RULE_UUID,
TAGS,
PRODUCER,
OWNER,
ALERT_ID,
ALERT_UUID,
ALERT_START,
ALERT_END,
TIMESTAMP,
ALERT_ACTION_GROUP,
ALERT_DURATION,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_OWNER,
ALERT_PRODUCER,
ALERT_REASON,
ALERT_RISK_SCORE,
ALERT_RULE_AUTHOR,
ALERT_RULE_CONSUMERS,
ALERT_RULE_CREATED_AT,
ALERT_RULE_CREATED_BY,
ALERT_RULE_DESCRIPTION,
ALERT_RULE_ENABLED,
ALERT_RULE_FROM,
ALERT_RULE_ID,
ALERT_RULE_INTERVAL,
ALERT_RULE_LICENSE,
ALERT_RULE_NAME,
ALERT_RULE_NOTE,
ALERT_RULE_REFERENCES,
ALERT_RULE_RISK_SCORE,
ALERT_RULE_RISK_SCORE_MAPPING,
ALERT_RULE_RULE_ID,
ALERT_RULE_RULE_NAME_OVERRIDE,
ALERT_RULE_SEVERITY,
ALERT_RULE_SEVERITY_MAPPING,
ALERT_RULE_TAGS,
ALERT_RULE_TO,
ALERT_RULE_TYPE,
ALERT_RULE_UPDATED_AT,
ALERT_RULE_UPDATED_BY,
ALERT_RULE_VERSION,
ALERT_START,
ALERT_SEVERITY,
ALERT_SEVERITY_LEVEL,
ALERT_SEVERITY_VALUE,
ALERT_STATUS,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_REASON,
ALERT_SYSTEM_STATUS,
ALERT_UUID,
ALERT_WORKFLOW_REASON,
ALERT_WORKFLOW_STATUS,
ALERT_WORKFLOW_USER,
SPACE_IDS,
VERSION,
};
export {
TIMESTAMP,
EVENT_KIND,
EVENT_ACTION,
RULE_UUID,
RULE_ID,
RULE_NAME,
RULE_CATEGORY,
TAGS,
PRODUCER,
OWNER,
ALERT_ID,
ALERT_UUID,
ALERT_START,
ALERT_END,
ALERT_ACTION_GROUP,
ALERT_DURATION,
ALERT_SEVERITY_LEVEL,
ALERT_SEVERITY_VALUE,
ALERT_STATUS,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_OWNER,
ALERT_PRODUCER,
ALERT_REASON,
ALERT_RISK_SCORE,
ALERT_STATUS,
ALERT_WORKFLOW_REASON,
ALERT_WORKFLOW_STATUS,
ALERT_WORKFLOW_USER,
ALERT_RULE_AUTHOR,
ALERT_RULE_CONSUMERS,
ALERT_RULE_CREATED_AT,
ALERT_RULE_CREATED_BY,
ALERT_RULE_DESCRIPTION,
ALERT_RULE_ENABLED,
ALERT_RULE_FROM,
ALERT_RULE_ID,
ALERT_RULE_INTERVAL,
ALERT_RULE_LICENSE,
ALERT_RULE_NAME,
ALERT_RULE_NOTE,
ALERT_RULE_REFERENCES,
ALERT_RULE_RISK_SCORE,
ALERT_RULE_RISK_SCORE_MAPPING,
ALERT_RULE_RULE_ID,
ALERT_RULE_RULE_NAME_OVERRIDE,
ALERT_RULE_SEVERITY_MAPPING,
ALERT_RULE_TAGS,
ALERT_RULE_TO,
ALERT_RULE_TYPE,
ALERT_RULE_UPDATED_AT,
ALERT_RULE_UPDATED_BY,
ALERT_RULE_VERSION,
ALERT_RULE_SEVERITY,
ALERT_SEVERITY,
ALERT_SEVERITY_LEVEL,
ALERT_SEVERITY_VALUE,
ALERT_START,
ALERT_SYSTEM_STATUS,
ALERT_UUID,
CONSUMERS,
ECS_VERSION,
EVENT_ACTION,
EVENT_KIND,
RULE_CATEGORY,
RULE_CONSUMERS,
RULE_ID,
RULE_NAME,
RULE_UUID,
TAGS,
TIMESTAMP,
SPACE_IDS,
VERSION,
};
export type TechnicalRuleDataFieldName = ValuesType<typeof fields>;

View file

@ -5,7 +5,17 @@
* 2.0.
*/
import { ALERT_SEVERITY_LEVEL } from '@kbn/rule-data-utils/target/technical_field_names';
import {
ALERT_DURATION,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_PRODUCER,
ALERT_SEVERITY_LEVEL,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { ValuesType } from 'utility-types';
import { EuiTheme } from '../../../../../../../../src/plugins/kibana_react/common';
import { ObservabilityRuleTypeRegistry } from '../../../../../../observability/public';
@ -23,28 +33,26 @@ const theme = ({
} as unknown) as EuiTheme;
const alert: Alert = {
'rule.id': ['apm.transaction_duration'],
'kibana.rac.alert.evaluation.value': [2057657.39],
[ALERT_EVALUATION_VALUE]: [2057657.39],
'service.name': ['frontend-rum'],
'rule.name': ['Latency threshold | frontend-rum'],
'kibana.rac.alert.duration.us': [62879000],
'kibana.rac.alert.status': ['open'],
[ALERT_DURATION]: [62879000],
[ALERT_STATUS]: ['open'],
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
'kibana.rac.alert.producer': ['apm'],
'kibana.rac.alert.uuid': ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
[ALERT_PRODUCER]: ['apm'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
'rule.uuid': ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T16:16:05.183Z'],
'kibana.rac.alert.id': ['apm.transaction_duration_All'],
[ALERT_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
'kibana.rac.alert.evaluation.threshold': [500000],
'kibana.rac.alert.start': ['2021-06-01T16:15:02.304Z'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-01T16:15:02.304Z'],
'event.kind': ['state'],
'rule.category': ['Latency threshold'],
};
const chartStartTime = new Date(
alert['kibana.rac.alert.start']![0] as string
).getTime();
const chartStartTime = new Date(alert[ALERT_START]![0] as string).getTime();
const getFormatter: ObservabilityRuleTypeRegistry['getFormatter'] = () => () => ({
link: '/',
reason: 'a good reason',

View file

@ -5,6 +5,16 @@
* 2.0.
*/
import {
ALERT_DURATION,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_SEVERITY_LEVEL,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { StoryContext } from '@storybook/react';
import React, { ComponentType } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
@ -111,66 +121,66 @@ Example.args = {
alerts: [
{
'rule.id': ['apm.transaction_duration'],
'kibana.rac.alert.evaluation.value': [2001708.19],
[ALERT_EVALUATION_VALUE]: [2001708.19],
'service.name': ['frontend-rum'],
'rule.name': ['Latency threshold | frontend-rum'],
'kibana.rac.alert.duration.us': [10000000000],
'kibana.rac.alert.status': ['open'],
[ALERT_DURATION]: [10000000000],
[ALERT_STATUS]: ['open'],
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
'kibana.rac.alert.producer': ['apm'],
'kibana.rac.alert.uuid': ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
'kibana.alert.producer': ['apm'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
'rule.uuid': ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
'kibana.rac.alert.id': ['apm.transaction_duration_All'],
[ALERT_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
'kibana.rac.alert.evaluation.threshold': [500000],
'kibana.rac.alert.start': ['2021-06-02T04:00:00.000Z'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T04:00:00.000Z'],
'event.kind': ['state'],
'rule.category': ['Latency threshold'],
},
{
'rule.id': ['apm.transaction_duration'],
'kibana.rac.alert.evaluation.value': [2001708.19],
[ALERT_EVALUATION_VALUE]: [2001708.19],
'service.name': ['frontend-rum'],
'rule.name': ['Latency threshold | frontend-rum'],
'kibana.rac.alert.duration.us': [10000000000],
'kibana.rac.alert.status': ['open'],
[ALERT_DURATION]: [10000000000],
[ALERT_STATUS]: ['open'],
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
'kibana.rac.alert.producer': ['apm'],
'kibana.rac.alert.severity.level': ['warning'],
'kibana.rac.alert.uuid': ['af2ae371-df79-4fca-b0eb-a2dbd9478181'],
'kibana.alert.producer': ['apm'],
[ALERT_SEVERITY_LEVEL]: ['warning'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478181'],
'rule.uuid': ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
'kibana.rac.alert.id': ['apm.transaction_duration_All'],
[ALERT_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
'kibana.rac.alert.evaluation.threshold': [500000],
'kibana.rac.alert.start': ['2021-06-02T10:45:00.000Z'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T10:45:00.000Z'],
'event.kind': ['state'],
'rule.category': ['Latency threshold'],
},
{
'rule.id': ['apm.transaction_duration'],
'kibana.rac.alert.evaluation.value': [2001708.19],
[ALERT_EVALUATION_VALUE]: [2001708.19],
'service.name': ['frontend-rum'],
'rule.name': ['Latency threshold | frontend-rum'],
'kibana.rac.alert.duration.us': [1000000000],
'kibana.rac.alert.status': ['open'],
[ALERT_DURATION]: [1000000000],
[ALERT_STATUS]: ['open'],
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
'kibana.rac.alert.producer': ['apm'],
'kibana.rac.alert.severity.level': ['critical'],
'kibana.rac.alert.uuid': ['af2ae371-df79-4fca-b0eb-a2dbd9478182'],
'kibana.alert.producer': ['apm'],
[ALERT_SEVERITY_LEVEL]: ['critical'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478182'],
'rule.uuid': ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
'@timestamp': ['2021-06-01T20:27:48.833Z'],
'kibana.rac.alert.id': ['apm.transaction_duration_All'],
[ALERT_ID]: ['apm.transaction_duration_All'],
'processor.event': ['transaction'],
'kibana.rac.alert.evaluation.threshold': [500000],
'kibana.rac.alert.start': ['2021-06-02T16:50:00.000Z'],
[ALERT_EVALUATION_THRESHOLD]: [500000],
[ALERT_START]: ['2021-06-02T16:50:00.000Z'],
'event.kind': ['state'],
'rule.category': ['Latency threshold'],
},

View file

@ -133,20 +133,23 @@ export class APMPlugin
settings: {
number_of_shards: 1,
},
mappings: mappingFromFieldMap({
[SERVICE_NAME]: {
type: 'keyword',
mappings: mappingFromFieldMap(
{
[SERVICE_NAME]: {
type: 'keyword',
},
[SERVICE_ENVIRONMENT]: {
type: 'keyword',
},
[TRANSACTION_TYPE]: {
type: 'keyword',
},
[PROCESSOR_EVENT]: {
type: 'keyword',
},
},
[SERVICE_ENVIRONMENT]: {
type: 'keyword',
},
[TRANSACTION_TYPE]: {
type: 'keyword',
},
[PROCESSOR_EVENT]: {
type: 'keyword',
},
}),
'strict'
),
},
},
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ALERT_UUID } from '@kbn/rule-data-utils/target/technical_field_names';
import { ALERT_UUID } from '@kbn/rule-data-utils';
import React, { ComponentType } from 'react';
import type { TopAlertResponse } from '../';
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
@ -57,7 +57,7 @@ export default {
};
export function Example({ alerts }: Args) {
const selectedAlertId = apmAlertResponseExample[0][ALERT_UUID][0];
const selectedAlertId = apmAlertResponseExample[0]![ALERT_UUID]![0];
const observabilityRuleTypeRegistry = createObservabilityRuleTypeRegistryMock();
return (
<AlertsFlyout

View file

@ -5,22 +5,33 @@
* 2.0.
*/
import {
ALERT_DURATION,
ALERT_END,
ALERT_ID,
ALERT_SEVERITY_LEVEL,
ALERT_SEVERITY_VALUE,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
} from '@kbn/rule-data-utils';
export const apmAlertResponseExample = [
{
'rule.id': ['apm.error_rate'],
'service.name': ['opbeans-java'],
'rule.name': ['Error count threshold | opbeans-java (smith test)'],
'kibana.rac.alert.duration.us': [180057000],
'kibana.rac.alert.status': ['open'],
'kibana.rac.alert.severity.level': ['warning'],
[ALERT_DURATION]: [180057000],
[ALERT_STATUS]: ['open'],
[ALERT_SEVERITY_LEVEL]: ['warning'],
tags: ['apm', 'service.name:opbeans-java'],
'kibana.rac.alert.uuid': ['0175ec0a-a3b1-4d41-b557-e21c2d024352'],
[ALERT_UUID]: ['0175ec0a-a3b1-4d41-b557-e21c2d024352'],
'rule.uuid': ['474920d0-93e9-11eb-ac86-0b455460de81'],
'event.action': ['active'],
'@timestamp': ['2021-04-12T13:53:49.550Z'],
'kibana.rac.alert.id': ['apm.error_rate_opbeans-java_production'],
'kibana.rac.alert.start': ['2021-04-12T13:50:49.493Z'],
'kibana.rac.producer': ['apm'],
[ALERT_ID]: ['apm.error_rate_opbeans-java_production'],
[ALERT_START]: ['2021-04-12T13:50:49.493Z'],
'kibana.producer': ['apm'],
'event.kind': ['state'],
'rule.category': ['Error count threshold'],
'service.environment': ['production'],
@ -30,17 +41,17 @@ export const apmAlertResponseExample = [
'rule.id': ['apm.error_rate'],
'service.name': ['opbeans-java'],
'rule.name': ['Error count threshold | opbeans-java (smith test)'],
'kibana.rac.alert.duration.us': [2419005000],
'kibana.rac.alert.end': ['2021-04-12T13:49:49.446Z'],
'kibana.rac.alert.status': ['closed'],
[ALERT_DURATION]: [2419005000],
[ALERT_END]: ['2021-04-12T13:49:49.446Z'],
[ALERT_STATUS]: ['closed'],
tags: ['apm', 'service.name:opbeans-java'],
'kibana.rac.alert.uuid': ['32b940e1-3809-4c12-8eee-f027cbb385e2'],
[ALERT_UUID]: ['32b940e1-3809-4c12-8eee-f027cbb385e2'],
'rule.uuid': ['474920d0-93e9-11eb-ac86-0b455460de81'],
'event.action': ['close'],
'@timestamp': ['2021-04-12T13:49:49.446Z'],
'kibana.rac.alert.id': ['apm.error_rate_opbeans-java_production'],
'kibana.rac.alert.start': ['2021-04-12T13:09:30.441Z'],
'kibana.rac.producer': ['apm'],
[ALERT_ID]: ['apm.error_rate_opbeans-java_production'],
[ALERT_START]: ['2021-04-12T13:09:30.441Z'],
'kibana.producer': ['apm'],
'event.kind': ['state'],
'rule.category': ['Error count threshold'],
'service.environment': ['production'],
@ -83,7 +94,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.duration.us',
name: ALERT_DURATION,
type: 'number',
esTypes: ['long'],
searchable: true,
@ -91,7 +102,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.end',
name: ALERT_END,
type: 'date',
esTypes: ['date'],
searchable: true,
@ -99,7 +110,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.id',
name: ALERT_ID,
type: 'string',
esTypes: ['keyword'],
searchable: true,
@ -107,7 +118,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.severity.level',
name: ALERT_SEVERITY_LEVEL,
type: 'string',
esTypes: ['keyword'],
searchable: true,
@ -115,7 +126,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.severity.value',
name: ALERT_SEVERITY_VALUE,
type: 'number',
esTypes: ['long'],
searchable: true,
@ -123,7 +134,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.start',
name: ALERT_START,
type: 'date',
esTypes: ['date'],
searchable: true,
@ -131,7 +142,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.status',
name: ALERT_STATUS,
type: 'string',
esTypes: ['keyword'],
searchable: true,
@ -139,7 +150,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.alert.uuid',
name: ALERT_UUID,
type: 'string',
esTypes: ['keyword'],
searchable: true,
@ -147,7 +158,7 @@ export const dynamicIndexPattern = {
readFromDocValues: true,
},
{
name: 'kibana.rac.producer',
name: 'kibana.producer',
type: 'string',
esTypes: ['keyword'],
searchable: true,

View file

@ -58,7 +58,7 @@ await plugins.ruleRegistry.createOrUpdateComponentTemplate({
[PROCESSOR_EVENT]: {
type: 'keyword',
},
}),
}, 'strict'),
},
},
});
@ -124,21 +124,21 @@ The following fields are defined in the technical field component template and s
- `rule.uuid`: the saved objects id of the rule.
- `rule.name`: the name of the rule (as specified by the user).
- `rule.category`: the name of the rule type (as defined by the rule type producer)
- `kibana.rac.alert.producer`: the producer of the rule type. Usually a Kibana plugin. e.g., `APM`.
- `kibana.rac.alert.owner`: the feature which produced the alert. Usually a Kibana feature id like `apm`, `siem`...
- `kibana.rac.alert.id`: the id of the alert, that is unique within the context of the rule execution it was created in. E.g., for a rule that monitors latency for all services in all environments, this might be `opbeans-java:production`.
- `kibana.rac.alert.uuid`: the unique identifier for the alert during its lifespan. If an alert recovers (or closes), this identifier is re-generated when it is opened again.
- `kibana.rac.alert.status`: the status of the alert. Can be `open` or `closed`.
- `kibana.rac.alert.start`: the ISO timestamp of the time at which the alert started.
- `kibana.rac.alert.end`: the ISO timestamp of the time at which the alert recovered.
- `kibana.rac.alert.duration.us`: the duration of the alert, in microseconds. This is always the difference between either the current time, or the time when the alert recovered.
- `kibana.rac.alert.severity.level`: the severity of the alert, as a keyword (e.g. critical).
- `kibana.rac.alert.severity.value`: the severity of the alert, as a numerical value, which allows sorting.
- `kibana.rac.alert.evaluation.value`: The measured (numerical value).
- `kibana.rac.alert.threshold.value`: The threshold that was defined (or, in case of multiple thresholds, the one that was exceeded).
- `kibana.rac.alert.ancestors`: the array of ancestors (if any) for the alert.
- `kibana.rac.alert.depth`: the depth of the alert in the ancestral tree (default 0).
- `kibana.rac.alert.building_block_type`: the building block type of the alert (default undefined).
- `kibana.alert.producer`: the producer of the rule type. Usually a Kibana plugin. e.g., `APM`.
- `kibana.alert.owner`: the feature which produced the alert. Usually a Kibana feature id like `apm`, `siem`...
- `kibana.alert.id`: the id of the alert, that is unique within the context of the rule execution it was created in. E.g., for a rule that monitors latency for all services in all environments, this might be `opbeans-java:production`.
- `kibana.alert.uuid`: the unique identifier for the alert during its lifespan. If an alert recovers (or closes), this identifier is re-generated when it is opened again.
- `kibana.alert.status`: the status of the alert. Can be `open` or `closed`.
- `kibana.alert.start`: the ISO timestamp of the time at which the alert started.
- `kibana.alert.end`: the ISO timestamp of the time at which the alert recovered.
- `kibana.alert.duration.us`: the duration of the alert, in microseconds. This is always the difference between either the current time, or the time when the alert recovered.
- `kibana.alert.severity.level`: the severity of the alert, as a keyword (e.g. critical).
- `kibana.alert.severity.value`: the severity of the alert, as a numerical value, which allows sorting.
- `kibana.alert.evaluation.value`: The measured (numerical value).
- `kibana.alert.threshold.value`: The threshold that was defined (or, in case of multiple thresholds, the one that was exceeded).
- `kibana.alert.ancestors`: the array of ancestors (if any) for the alert.
- `kibana.alert.depth`: the depth of the alert in the ancestral tree (default 0).
- `kibana.alert.building_block_type`: the building block type of the alert (default undefined).
# Alerts as data

View file

@ -17,8 +17,8 @@ export const ecsComponentTemplate: ClusterPutComponentTemplateBody = {
},
mappings: merge(
{},
mappingFromFieldMap(ecsFieldMap),
mappingFromFieldMap(technicalRuleFieldMap)
mappingFromFieldMap(ecsFieldMap, 'strict'),
mappingFromFieldMap(technicalRuleFieldMap, 'strict')
),
},
};

View file

@ -14,6 +14,6 @@ export const technicalComponentTemplate: ClusterPutComponentTemplateBody = {
settings: {
number_of_shards: 1,
},
mappings: mappingFromFieldMap(technicalRuleFieldMap),
mappings: mappingFromFieldMap(technicalRuleFieldMap, 'strict'),
},
};

View file

@ -660,6 +660,11 @@ export const ecsFieldMap = {
array: false,
required: false,
},
'event.agent_id_status': {
type: 'keyword',
array: false,
required: false,
},
'event.category': {
type: 'keyword',
array: true,

View file

@ -5,58 +5,239 @@
* 2.0.
*/
import { pickWithPatterns } from '../../../common/pick_with_patterns';
import {
ALERT_DURATION,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_REASON,
ALERT_SEVERITY_LEVEL,
ALERT_SEVERITY_VALUE,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
EVENT_ACTION,
EVENT_KIND,
OWNER,
PRODUCER,
RULE_CATEGORY,
RULE_ID,
RULE_NAME,
RULE_UUID,
TAGS,
TIMESTAMP,
SPACE_IDS,
} from '../../../common/technical_rule_data_field_names';
import * as Fields from '../../../common/technical_rule_data_field_names';
import { ecsFieldMap } from './ecs_field_map';
export const technicalRuleFieldMap = {
...pickWithPatterns(
ecsFieldMap,
TIMESTAMP,
EVENT_KIND,
EVENT_ACTION,
RULE_UUID,
RULE_ID,
RULE_NAME,
RULE_CATEGORY,
TAGS
Fields.TIMESTAMP,
Fields.EVENT_KIND,
Fields.EVENT_ACTION,
Fields.RULE_UUID,
Fields.RULE_ID,
Fields.RULE_NAME,
Fields.RULE_CATEGORY,
Fields.TAGS
),
[OWNER]: { type: 'keyword' },
[PRODUCER]: { type: 'keyword' },
[SPACE_IDS]: { type: 'keyword', array: true },
[ALERT_UUID]: { type: 'keyword' },
[ALERT_ID]: { type: 'keyword' },
[ALERT_START]: { type: 'date' },
[ALERT_END]: { type: 'date' },
[ALERT_DURATION]: { type: 'long' },
[ALERT_SEVERITY_LEVEL]: { type: 'keyword' },
[ALERT_SEVERITY_VALUE]: { type: 'long' },
[ALERT_STATUS]: { type: 'keyword' },
[ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 },
[ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 },
[ALERT_REASON]: { type: 'keyword' },
[Fields.ALERT_OWNER]: { type: 'keyword' },
[Fields.ALERT_PRODUCER]: { type: 'keyword' },
[Fields.SPACE_IDS]: { type: 'keyword', array: true },
[Fields.ALERT_UUID]: { type: 'keyword' },
[Fields.ALERT_ID]: { type: 'keyword' },
[Fields.ALERT_START]: { type: 'date' },
[Fields.ALERT_END]: { type: 'date' },
[Fields.ALERT_DURATION]: { type: 'long' },
[Fields.ALERT_SEVERITY_LEVEL]: { type: 'keyword' },
[Fields.ALERT_SEVERITY_VALUE]: { type: 'long' },
[Fields.ALERT_STATUS]: { type: 'keyword' },
[Fields.ALERT_EVALUATION_THRESHOLD]: { type: 'scaled_float', scaling_factor: 100 },
[Fields.ALERT_EVALUATION_VALUE]: { type: 'scaled_float', scaling_factor: 100 },
[Fields.CONSUMERS]: {
type: 'keyword',
array: true,
required: false,
},
[Fields.VERSION]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ECS_VERSION]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_SEVERITY]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_RISK_SCORE]: {
type: 'float',
array: false,
required: false,
},
[Fields.ALERT_WORKFLOW_STATUS]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_WORKFLOW_USER]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_WORKFLOW_REASON]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_SYSTEM_STATUS]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_ACTION_GROUP]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_REASON]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_AUTHOR]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_CONSUMERS]: {
type: 'keyword',
array: true,
required: false,
},
[Fields.ALERT_RULE_CREATED_AT]: {
type: 'date',
array: false,
required: false,
},
[Fields.ALERT_RULE_CREATED_BY]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_DESCRIPTION]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_ENABLED]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_FROM]: {
type: 'date',
array: false,
required: false,
},
[Fields.ALERT_RULE_ID]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_INTERVAL]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_LICENSE]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_NAME]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_NOTE]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_REFERENCES]: {
type: 'keyword',
array: true,
required: false,
},
[Fields.ALERT_RULE_RISK_SCORE_MAPPING]: {
type: 'object',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_RISK_SCORE_MAPPING}.field`]: {
type: 'keyword',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_RISK_SCORE_MAPPING}.operator`]: {
type: 'keyword',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_RISK_SCORE_MAPPING}.value`]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_RULE_ID]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_RULE_NAME_OVERRIDE]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_SEVERITY_MAPPING]: {
type: 'object',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_SEVERITY_MAPPING}.field`]: {
type: 'keyword',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_SEVERITY_MAPPING}.operator`]: {
type: 'keyword',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_SEVERITY_MAPPING}.value`]: {
type: 'keyword',
array: false,
required: false,
},
[`${Fields.ALERT_RULE_SEVERITY_MAPPING}.severity`]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_TAGS]: {
type: 'keyword',
array: true,
required: false,
},
[Fields.ALERT_RULE_TO]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_TYPE]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_UPDATED_AT]: {
type: 'date',
array: false,
required: false,
},
[Fields.ALERT_RULE_UPDATED_BY]: {
type: 'keyword',
array: false,
required: false,
},
[Fields.ALERT_RULE_VERSION]: {
type: 'keyword',
array: false,
required: false,
},
} as const;
export type TechnicalRuleFieldMaps = typeof technicalRuleFieldMap;
export type TechnicalRuleFieldMap = typeof technicalRuleFieldMap;

View file

@ -9,9 +9,12 @@ import type { estypes } from '@elastic/elasticsearch';
import { set } from '@elastic/safer-lodash-set';
import { FieldMap } from './field_map/types';
export function mappingFromFieldMap(fieldMap: FieldMap): estypes.MappingTypeMapping {
export function mappingFromFieldMap(
fieldMap: FieldMap,
dynamic: 'strict' | boolean
): estypes.MappingTypeMapping {
const mappings = {
dynamic: 'strict' as const,
dynamic,
properties: {},
};

View file

@ -98,7 +98,7 @@ ___
### fetchAlert
`Private` **fetchAlert**(`__namedParameters`): `Promise`<undefined \| ``null`` \| `Omit`<OutputOf<SetOptional<`Object`\>\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\>
`Private` **fetchAlert**(`__namedParameters`): `Promise`<undefined \| ``null`` \| `Omit`<OutputOf<SetOptional<`Object`\>\>, ``"kibana.alert.owner"`` \| ``"rule.id"``\> & { `kibana.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\>
#### Parameters
@ -108,7 +108,7 @@ ___
#### Returns
`Promise`<undefined \| ``null`` \| `Omit`<OutputOf<SetOptional<`Object`\>\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\>
`Promise`<undefined \| ``null`` \| `Omit`<OutputOf<SetOptional<`Object`\>\>, ``"kibana.alert.owner"`` \| ``"rule.id"``\> & { `kibana.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\>
#### Defined in

View file

@ -24,7 +24,7 @@ import { alertAuditEvent, AlertAuditAction } from './audit_events';
import { AuditLogger } from '../../../security/server';
import {
ALERT_STATUS,
OWNER,
ALERT_OWNER,
RULE_ID,
SPACE_IDS,
} from '../../common/technical_rule_data_field_names';
@ -33,10 +33,10 @@ import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
// TODO: Fix typings https://github.com/elastic/kibana/issues/101776
type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> &
{ [K in Props]-?: NonNullable<Obj[K]> };
type AlertType = NonNullableProps<ParsedTechnicalFields, 'rule.id' | 'kibana.rac.alert.owner'>;
type AlertType = NonNullableProps<ParsedTechnicalFields, 'rule.id' | 'kibana.alert.owner'>;
const isValidAlert = (source?: ParsedTechnicalFields): source is AlertType => {
return source?.[RULE_ID] != null && source?.[OWNER] != null;
return source?.[RULE_ID] != null && source?.[ALERT_OWNER] != null;
};
export interface ConstructorOptions {
logger: Logger;
@ -156,7 +156,7 @@ export class AlertsClient {
// client exposed to us for reuse
await this.authorization.ensureAuthorized({
ruleTypeId: alert[RULE_ID],
consumer: alert[OWNER],
consumer: alert[ALERT_OWNER],
operation: ReadOperations.Get,
entity: AlertingAuthorizationEntity.Alert,
});
@ -200,7 +200,7 @@ export class AlertsClient {
await this.authorization.ensureAuthorized({
ruleTypeId: alert[RULE_ID],
consumer: alert[OWNER],
consumer: alert[ALERT_OWNER],
operation: WriteOperations.Update,
entity: AlertingAuthorizationEntity.Alert,
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { ALERT_OWNER, ALERT_STATUS, SPACE_IDS } from '@kbn/rule-data-utils';
import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@ -59,9 +60,9 @@ describe('get()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -73,11 +74,11 @@ describe('get()', () => {
expect(result).toMatchInlineSnapshot(`
Object {
"_version": "WzM2MiwyXQ==",
"kibana.rac.alert.owner": "apm",
"kibana.rac.alert.space_ids": Array [
"${ALERT_OWNER}": "apm",
"${ALERT_STATUS}": "open",
"${SPACE_IDS}": Array [
"test_default_space_id",
],
"kibana.rac.alert.status": "open",
"message": "hello world 1",
"rule.id": "apm.error_rate",
}
@ -140,9 +141,9 @@ describe('get()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -202,9 +203,9 @@ describe('get()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -227,11 +228,11 @@ describe('get()', () => {
expect(result).toMatchInlineSnapshot(`
Object {
"_version": "WzM2MiwyXQ==",
"kibana.rac.alert.owner": "apm",
"kibana.rac.alert.space_ids": Array [
"${ALERT_OWNER}": "apm",
"${ALERT_STATUS}": "open",
"${SPACE_IDS}": Array [
"test_default_space_id",
],
"kibana.rac.alert.status": "open",
"message": "hello world 1",
"rule.id": "apm.error_rate",
}

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { ALERT_OWNER, ALERT_STATUS, SPACE_IDS } from '@kbn/rule-data-utils';
import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { loggingSystemMock } from '../../../../../../src/core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
@ -56,9 +57,9 @@ describe('update()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -106,7 +107,7 @@ describe('update()', () => {
Object {
"body": Object {
"doc": Object {
"kibana.rac.alert.status": "closed",
"${ALERT_STATUS}": "closed",
},
},
"id": "1",
@ -142,9 +143,9 @@ describe('update()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -235,9 +236,9 @@ describe('update()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],
@ -295,9 +296,9 @@ describe('update()', () => {
_source: {
'rule.id': 'apm.error_rate',
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.space_ids': ['test_default_space_id'],
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[SPACE_IDS]: ['test_default_space_id'],
},
},
],

View file

@ -8,7 +8,7 @@
import { EventSchema, Event } from './schema_types';
import { FieldMap, runtimeTypeFromFieldMap, mergeFieldMaps } from '../../../common/field_map';
import {
TechnicalRuleFieldMaps,
TechnicalRuleFieldMap,
technicalRuleFieldMap,
} from '../../../common/assets/field_maps/technical_rule_field_map';
@ -27,13 +27,13 @@ export abstract class Schema {
return createSchema(combinedFields);
}
public static getBase(): EventSchema<TechnicalRuleFieldMaps> {
public static getBase(): EventSchema<TechnicalRuleFieldMap> {
return baseSchema;
}
public static extendBase<TMap extends FieldMap>(
fields: TMap
): EventSchema<TechnicalRuleFieldMaps & TMap> {
): EventSchema<TechnicalRuleFieldMap & TMap> {
const extensionSchema = createSchema(fields);
return this.combine(baseSchema, extensionSchema);
}

View file

@ -92,7 +92,7 @@ export class EventLogResolver implements IEventLogResolver {
kibanaSpaceId,
});
const indexMappings = mappingFromFieldMap(eventSchema.objectFields);
const indexMappings = mappingFromFieldMap(eventSchema.objectFields, 'strict');
return { indexNames, indexMappings, ilmPolicy };
}

View file

@ -42,8 +42,8 @@ export class EventLoggerTemplate<TEvent> implements IEventLoggerTemplate<TEvent>
const nextFields = mergeFields(baseFields, extFields, {
// TODO: Define a schema for own fields used/set by event log. Add it to the base schema.
// Then maybe introduce a base type for TEvent.
'kibana.rac.event_log.log_name': indexNames.logName,
'kibana.rac.event_log.logger_name': nextName,
'kibana.event_log.log_name': indexNames.logName,
'kibana.event_log.logger_name': nextName,
} as any);
return {

View file

@ -83,7 +83,7 @@ export class EventQueryBuilder<TEvent> implements IEventQueryBuilder<TEvent> {
if (this.loggerName) {
result.push({
term: { 'kibana.rac.event_log.logger_name': this.loggerName },
term: { 'kibana.event_log.logger_name': this.loggerName },
});
}

View file

@ -9,9 +9,12 @@ import { set } from '@elastic/safer-lodash-set';
import { FieldMap } from '../../../../common/field_map';
import { IndexMappings } from '../../elasticsearch';
export function mappingFromFieldMap(fieldMap: FieldMap): IndexMappings {
export function mappingFromFieldMap(
fieldMap: FieldMap,
dynamic: 'strict' | boolean
): IndexMappings {
const mappings = {
dynamic: 'strict' as const,
dynamic,
properties: {},
};

View file

@ -14,18 +14,17 @@ export type { RacRequestHandlerContext, RacApiRequestHandlerContext } from './ty
export { RuleDataClient } from './rule_data_client';
export { IRuleDataClient } from './rule_data_client/types';
export { getRuleData, RuleExecutorData } from './utils/get_rule_executor_data';
export {
createLifecycleRuleTypeFactory,
LifecycleAlertService,
} from './utils/create_lifecycle_rule_type_factory';
export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory';
export { RuleDataPluginService } from './rule_data_plugin_service';
export {
LifecycleRuleExecutor,
LifecycleAlertService,
LifecycleAlertServices,
createLifecycleExecutor,
} from './utils/create_lifecycle_executor';
export { createPersistenceRuleTypeFactory } from './utils/create_persistence_rule_type_factory';
export { AlertTypeWithExecutor } from './types';
export * from './utils/persistence_types';
export type { AlertTypeWithExecutor } from './types';
export const plugin = (initContext: PluginInitializerContext) =>
new RuleRegistryPlugin(initContext);

View file

@ -5,6 +5,18 @@
* 2.0.
*/
import {
ALERT_OWNER,
ALERT_RULE_RISK_SCORE,
ALERT_RULE_SEVERITY,
ALERT_STATUS,
CONSUMERS,
ECS_VERSION,
RULE_ID,
TIMESTAMP,
VERSION,
} from '@kbn/rule-data-utils';
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { getAlertByIdRoute } from './get_alert_by_id';
@ -13,10 +25,15 @@ import { getReadRequest } from './__mocks__/request_responses';
import { requestMock, serverMock } from './__mocks__/server';
const getMockAlert = (): ParsedTechnicalFields => ({
'@timestamp': '2021-06-21T21:33:05.713Z',
'rule.id': 'apm.error_rate',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
[TIMESTAMP]: '2021-06-21T21:33:05.713Z',
[ECS_VERSION]: '1.0.0',
[CONSUMERS]: [],
[VERSION]: '7.13.0',
[RULE_ID]: 'apm.error_rate',
[ALERT_OWNER]: 'apm',
[ALERT_STATUS]: 'open',
[ALERT_RULE_RISK_SCORE]: 20,
[ALERT_RULE_SEVERITY]: 'warning',
});
describe('getAlertByIdRoute', () => {

View file

@ -16,45 +16,32 @@ import { AlertExecutorOptions, AlertServices, AlertType } from '../../alerting/s
import { AlertsClient } from './alert_data_client/alerts_client';
type SimpleAlertType<
TState extends AlertTypeState,
TParams extends AlertTypeParams = {},
TAlertInstanceContext extends AlertInstanceContext = {}
> = AlertType<
TParams,
TParams,
AlertTypeState,
AlertInstanceState,
TAlertInstanceContext,
string,
string
>;
> = AlertType<TParams, TParams, TState, AlertInstanceState, TAlertInstanceContext, string, string>;
export type AlertTypeExecutor<
TState extends AlertTypeState,
TParams extends AlertTypeParams = {},
TAlertInstanceContext extends AlertInstanceContext = {},
TServices extends Record<string, any> = {}
> = (
options: Parameters<SimpleAlertType<TParams, TAlertInstanceContext>['executor']>[0] & {
options: Parameters<SimpleAlertType<TState, TParams, TAlertInstanceContext>['executor']>[0] & {
services: TServices;
}
) => Promise<any>;
) => Promise<TState | void>;
export type AlertTypeWithExecutor<
TState extends AlertTypeState = {},
TParams extends AlertTypeParams = {},
TAlertInstanceContext extends AlertInstanceContext = {},
TServices extends Record<string, any> = {}
> = Omit<
AlertType<
TParams,
TParams,
AlertTypeState,
AlertInstanceState,
TAlertInstanceContext,
string,
string
>,
AlertType<TParams, TParams, TState, AlertInstanceState, TAlertInstanceContext, string, string>,
'executor'
> & {
executor: AlertTypeExecutor<TParams, TAlertInstanceContext, TServices>;
executor: AlertTypeExecutor<TState, TParams, TAlertInstanceContext, TServices>;
};
export type AlertExecutorOptionsWithExtraServices<

View file

@ -349,7 +349,7 @@ const createDefaultAlertExecutorOptions = <
actions: [],
enabled: true,
consumer: 'CONSUMER',
producer: 'PRODUCER',
producer: 'ALERT_PRODUCER',
schedule: { interval: '1m' },
throttle: null,
createdAt,

View file

@ -29,7 +29,7 @@ import {
ALERT_UUID,
EVENT_ACTION,
EVENT_KIND,
OWNER,
ALERT_OWNER,
RULE_UUID,
TIMESTAMP,
SPACE_IDS,
@ -38,7 +38,7 @@ import { RuleDataClient } from '../rule_data_client';
import { AlertExecutorOptionsWithExtraServices } from '../types';
import { getRuleData } from './get_rule_executor_data';
type LifecycleAlertService<
export type LifecycleAlertService<
InstanceState extends AlertInstanceState = never,
InstanceContext extends AlertInstanceContext = never,
ActionGroupIds extends string = never
@ -242,9 +242,9 @@ export const createLifecycleExecutor = (
...ruleExecutorData,
[TIMESTAMP]: timestamp,
[EVENT_KIND]: 'signal',
[OWNER]: rule.consumer,
[ALERT_OWNER]: rule.consumer,
[ALERT_ID]: alertId,
};
} as ParsedTechnicalFields;
const isNew = !state.trackedAlerts[alertId];
const isRecovered = !currentAlerts[alertId];

View file

@ -6,6 +6,15 @@
*/
import { schema } from '@kbn/config-schema';
import {
ALERT_DURATION,
ALERT_ID,
ALERT_OWNER,
ALERT_PRODUCER,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { loggerMock } from '@kbn/logging/target/mocks';
import { castArray, omit, mapValues } from 'lodash';
import { RuleDataClient } from '../rule_data_client';
@ -73,7 +82,7 @@ function createRule() {
scheduleActions.mockClear();
state = await type.executor({
state = ((await type.executor({
alertId: 'alertId',
createdBy: 'createdBy',
name: 'name',
@ -109,7 +118,7 @@ function createRule() {
tags: ['tags'],
updatedBy: 'updatedBy',
namespace: 'namespace',
});
})) ?? {}) as Record<string, any>;
previousStartedAt = startedAt;
},
@ -176,28 +185,24 @@ describe('createLifecycleRuleTypeFactory', () => {
expect(evaluationDocuments.length).toBe(0);
expect(alertDocuments.length).toBe(2);
expect(
alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open')
).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_STATUS] === 'open')).toBeTruthy();
expect(
alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] === 0)
).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_DURATION] === 0)).toBeTruthy();
expect(alertDocuments.every((doc) => doc['event.action'] === 'open')).toBeTruthy();
expect(documents.map((doc) => omit(doc, 'kibana.rac.alert.uuid'))).toMatchInlineSnapshot(`
expect(documents.map((doc) => omit(doc, ALERT_UUID))).toMatchInlineSnapshot(`
Array [
Object {
"@timestamp": "2021-06-16T09:01:00.000Z",
"event.action": "open",
"event.kind": "signal",
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-java",
"kibana.rac.alert.owner": "consumer",
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
"${ALERT_DURATION}": 0,
"${ALERT_ID}": "opbeans-java",
"${ALERT_OWNER}": "consumer",
"${ALERT_PRODUCER}": "producer",
"${ALERT_START}": "2021-06-16T09:01:00.000Z",
"${ALERT_STATUS}": "open",
"kibana.space_ids": Array [
"spaceId",
],
@ -214,12 +219,12 @@ describe('createLifecycleRuleTypeFactory', () => {
"@timestamp": "2021-06-16T09:01:00.000Z",
"event.action": "open",
"event.kind": "signal",
"kibana.rac.alert.duration.us": 0,
"kibana.rac.alert.id": "opbeans-node",
"kibana.rac.alert.owner": "consumer",
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
"${ALERT_DURATION}": 0,
"${ALERT_ID}": "opbeans-node",
"${ALERT_OWNER}": "consumer",
"${ALERT_PRODUCER}": "producer",
"${ALERT_START}": "2021-06-16T09:01:00.000Z",
"${ALERT_STATUS}": "open",
"kibana.space_ids": Array [
"spaceId",
],
@ -283,12 +288,10 @@ describe('createLifecycleRuleTypeFactory', () => {
expect(evaluationDocuments.length).toBe(0);
expect(alertDocuments.length).toBe(2);
expect(
alertDocuments.every((doc) => doc['kibana.rac.alert.status'] === 'open')
).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_STATUS] === 'open')).toBeTruthy();
expect(alertDocuments.every((doc) => doc['event.action'] === 'active')).toBeTruthy();
expect(alertDocuments.every((doc) => doc['kibana.rac.alert.duration.us'] > 0)).toBeTruthy();
expect(alertDocuments.every((doc) => doc[ALERT_DURATION] > 0)).toBeTruthy();
});
});
@ -363,10 +366,10 @@ describe('createLifecycleRuleTypeFactory', () => {
);
expect(opbeansJavaAlertDoc['event.action']).toBe('active');
expect(opbeansJavaAlertDoc['kibana.rac.alert.status']).toBe('open');
expect(opbeansJavaAlertDoc[ALERT_STATUS]).toBe('open');
expect(opbeansNodeAlertDoc['event.action']).toBe('close');
expect(opbeansNodeAlertDoc['kibana.rac.alert.status']).toBe('closed');
expect(opbeansNodeAlertDoc[ALERT_STATUS]).toBe('closed');
});
});
});

View file

@ -12,31 +12,24 @@ import {
AlertTypeParams,
AlertTypeState,
} from '../../../alerting/common';
import { AlertInstance } from '../../../alerting/server';
import { AlertTypeWithExecutor } from '../types';
import { createLifecycleExecutor } from './create_lifecycle_executor';
export type LifecycleAlertService<
TAlertInstanceContext extends Record<string, unknown>,
TActionGroupIds extends string = string
> = (alert: {
id: string;
fields: Record<string, unknown>;
}) => AlertInstance<AlertInstanceState, TAlertInstanceContext, TActionGroupIds>;
import { LifecycleAlertService, createLifecycleExecutor } from './create_lifecycle_executor';
export const createLifecycleRuleTypeFactory = ({
logger,
ruleDataClient,
}: {
ruleDataClient: RuleDataClient;
logger: Logger;
ruleDataClient: RuleDataClient;
}) => <
TParams extends AlertTypeParams,
TAlertInstanceContext extends AlertInstanceContext,
TServices extends { alertWithLifecycle: LifecycleAlertService<TAlertInstanceContext> }
TServices extends {
alertWithLifecycle: LifecycleAlertService<Record<string, any>, TAlertInstanceContext, string>;
}
>(
type: AlertTypeWithExecutor<TParams, TAlertInstanceContext, TServices>
): AlertTypeWithExecutor<TParams, TAlertInstanceContext, any> => {
type: AlertTypeWithExecutor<Record<string, any>, TParams, TAlertInstanceContext, TServices>
): AlertTypeWithExecutor<Record<string, any>, TParams, TAlertInstanceContext, any> => {
const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient);
const executor = createBoundLifecycleExecutor<
TParams,

View file

@ -4,40 +4,9 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ESSearchRequest } from 'src/core/types/elasticsearch';
import v4 from 'uuid/v4';
import { Logger } from '@kbn/logging';
import { AlertInstance } from '../../../alerting/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertTypeParams,
} from '../../../alerting/common';
import { RuleDataClient } from '../rule_data_client';
import { AlertTypeWithExecutor } from '../types';
type PersistenceAlertService<TAlertInstanceContext extends Record<string, unknown>> = (
alerts: Array<Record<string, unknown>>
) => Array<AlertInstance<AlertInstanceState, TAlertInstanceContext, string>>;
type PersistenceAlertQueryService = (
query: ESSearchRequest
) => Promise<Array<Record<string, unknown>>>;
type CreatePersistenceRuleTypeFactory = (options: {
ruleDataClient: RuleDataClient;
logger: Logger;
}) => <
TParams extends AlertTypeParams,
TAlertInstanceContext extends AlertInstanceContext,
TServices extends {
alertWithPersistence: PersistenceAlertService<TAlertInstanceContext>;
findAlerts: PersistenceAlertQueryService;
}
>(
type: AlertTypeWithExecutor<TParams, TAlertInstanceContext, TServices>
) => AlertTypeWithExecutor<TParams, TAlertInstanceContext, any>;
import { ALERT_ID } from '@kbn/rule-data-utils/target/technical_field_names';
import { CreatePersistenceRuleTypeFactory } from './persistence_types';
export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory = ({
logger,
@ -46,66 +15,33 @@ export const createPersistenceRuleTypeFactory: CreatePersistenceRuleTypeFactory
return {
...type,
executor: async (options) => {
const {
services: { alertInstanceFactory, scopedClusterClient },
} = options;
const currentAlerts: Array<Record<string, unknown>> = [];
const timestamp = options.startedAt.toISOString();
const state = await type.executor({
...options,
services: {
...options.services,
alertWithPersistence: (alerts) => {
alerts.forEach((alert) => currentAlerts.push(alert));
return alerts.map((alert) =>
alertInstanceFactory(alert['kibana.rac.alert.uuid']! as string)
);
},
findAlerts: async (query) => {
const { body } = await scopedClusterClient.asCurrentUser.search({
...query,
body: {
...query.body,
},
ignore_unavailable: true,
});
return body.hits.hits
.map((event: { _source: any }) => event._source!)
.map((event: { [x: string]: any }) => {
const alertUuid = event['kibana.rac.alert.uuid'];
const isAlert = alertUuid != null;
return {
...event,
'event.kind': 'signal',
'kibana.rac.alert.id': '???',
'kibana.rac.alert.status': 'open',
'kibana.rac.alert.uuid': v4(),
'kibana.rac.alert.ancestors': isAlert
? ((event['kibana.rac.alert.ancestors'] as string[]) ?? []).concat([
alertUuid!,
] as string[])
: [],
'kibana.rac.alert.depth': isAlert
? ((event['kibana.rac.alert.depth'] as number) ?? 0) + 1
: 0,
'@timestamp': timestamp,
};
alertWithPersistence: async (alerts, refresh) => {
const numAlerts = alerts.length;
logger.debug(`Found ${numAlerts} alerts.`);
if (ruleDataClient.isWriteEnabled() && numAlerts) {
const response = await ruleDataClient.getWriter().bulk({
body: alerts.flatMap((event) => [
{ index: {} },
{
[ALERT_ID]: event.id,
...event.fields,
},
]),
refresh,
});
return response;
} else {
logger.debug('Writing is disabled.');
}
},
},
});
const numAlerts = currentAlerts.length;
logger.debug(`Found ${numAlerts} alerts.`);
if (ruleDataClient.isWriteEnabled() && numAlerts) {
await ruleDataClient.getWriter().bulk({
body: currentAlerts.flatMap((event) => [{ index: {} }, event]),
});
}
return state;
},
};

View file

@ -7,7 +7,7 @@
import { AlertExecutorOptions } from '../../../alerting/server';
import {
PRODUCER,
ALERT_PRODUCER,
RULE_CATEGORY,
RULE_ID,
RULE_NAME,
@ -20,7 +20,7 @@ export interface RuleExecutorData {
[RULE_ID]: string;
[RULE_UUID]: string;
[RULE_NAME]: string;
[PRODUCER]: string;
[ALERT_PRODUCER]: string;
[TAGS]: string[];
}
@ -31,6 +31,6 @@ export function getRuleData(options: AlertExecutorOptions<any, any, any, any, an
[RULE_CATEGORY]: options.rule.ruleTypeName,
[RULE_NAME]: options.rule.name,
[TAGS]: options.tags,
[PRODUCER]: options.rule.producer,
[ALERT_PRODUCER]: options.rule.producer,
};
}

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ApiResponse } from '@elastic/elasticsearch';
import { BulkResponse } from '@elastic/elasticsearch/api/types';
import { Logger } from '@kbn/logging';
import { ESSearchRequest } from 'src/core/types/elasticsearch';
import {
AlertInstanceContext,
AlertInstanceState,
AlertTypeParams,
AlertTypeState,
} from '../../../alerting/server';
import { RuleDataClient } from '../rule_data_client';
import { AlertTypeWithExecutor } from '../types';
export type PersistenceAlertService<
TState extends AlertInstanceState = never,
TContext extends AlertInstanceContext = never,
TActionGroupIds extends string = never
> = (
alerts: Array<{
id: string;
fields: Record<string, unknown>;
}>,
refresh: boolean | 'wait_for'
) => Promise<ApiResponse<BulkResponse, unknown>>;
export type PersistenceAlertQueryService = (
query: ESSearchRequest
) => Promise<Array<Record<string, unknown>>>;
export interface PersistenceServices<TAlertInstanceContext extends AlertInstanceContext = {}> {
alertWithPersistence: PersistenceAlertService<TAlertInstanceContext>;
}
export type CreatePersistenceRuleTypeFactory = (options: {
ruleDataClient: RuleDataClient;
logger: Logger;
}) => <
TState extends AlertTypeState,
TParams extends AlertTypeParams,
TServices extends PersistenceServices<TAlertInstanceContext>,
TAlertInstanceContext extends AlertInstanceContext = {}
>(
type: AlertTypeWithExecutor<TState, TParams, TAlertInstanceContext, TServices>
) => AlertTypeWithExecutor<TState, TParams, TAlertInstanceContext, TServices>;

View file

@ -5,21 +5,24 @@
* 2.0.
*/
import { AlertInstanceContext, AlertTypeParams } from '../../../alerting/common';
import { AlertInstanceContext, AlertTypeParams, AlertTypeState } from '../../../alerting/common';
import { RuleDataClient } from '../rule_data_client';
import { AlertTypeWithExecutor } from '../types';
export const withRuleDataClientFactory = (ruleDataClient: RuleDataClient) => <
TState extends AlertTypeState,
TParams extends AlertTypeParams,
TAlertInstanceContext extends AlertInstanceContext,
TServices extends Record<string, any> = {}
>(
type: AlertTypeWithExecutor<
TState,
TParams,
TAlertInstanceContext,
TServices & { ruleDataClient: RuleDataClient }
>
): AlertTypeWithExecutor<
TState,
TParams,
TAlertInstanceContext,
TServices & { ruleDataClient: RuleDataClient }

View file

@ -193,7 +193,7 @@ export const SIGNALS_ID = `siem.signals`;
export const REFERENCE_RULE_ALERT_TYPE_ID = `siem.referenceRule`;
export const REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID = `siem.referenceRulePersistence`;
export const CUSTOM_ALERT_TYPE_ID = `siem.customRule`;
export const QUERY_ALERT_TYPE_ID = `siem.queryRule`;
export const EQL_ALERT_TYPE_ID = `siem.eqlRule`;
export const INDICATOR_ALERT_TYPE_ID = `siem.indicatorRule`;
export const ML_ALERT_TYPE_ID = `siem.mlRule`;

View file

@ -5,6 +5,15 @@
* 2.0.
*/
import {
ALERT_DURATION,
ALERT_ID,
ALERT_PRODUCER,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { ColumnHeaderOptions, RowRendererId } from '../../../../common/types/timeline';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
@ -134,14 +143,14 @@ export const buildAlertStatusFilterRuleRegistry = (status: Status): Filter[] =>
negate: false,
disabled: false,
type: 'phrase',
key: 'kibana.rac.alert.status',
key: ALERT_STATUS,
params: {
query: status,
},
},
query: {
term: {
'kibana.rac.alert.status': status,
[ALERT_STATUS]: status,
},
},
},
@ -159,28 +168,28 @@ export const buildShowBuildingBlockFilterRuleRegistry = (
negate: true,
disabled: false,
type: 'exists',
key: 'kibana.rac.rule.building_block_type',
key: 'kibana.rule.building_block_type',
value: 'exists',
},
// @ts-expect-error TODO: Rework parent typings to support ExistsFilter[]
exists: { field: 'kibana.rac.rule.building_block_type' },
exists: { field: 'kibana.rule.building_block_type' },
},
];
export const requiredFieldMappingsForActionsRuleRegistry = {
'@timestamp': '@timestamp',
'alert.id': 'kibana.rac.alert.id',
'alert.id': ALERT_ID,
'event.kind': 'event.kind',
'alert.start': 'kibana.rac.alert.start',
'alert.uuid': 'kibana.rac.alert.uuid',
'alert.start': ALERT_START,
'alert.uuid': ALERT_UUID,
'event.action': 'event.action',
'alert.status': 'kibana.rac.alert.status',
'alert.duration.us': 'kibana.rac.alert.duration.us',
'alert.status': ALERT_STATUS,
'alert.duration.us': ALERT_DURATION,
'rule.uuid': 'rule.uuid',
'rule.id': 'rule.id',
'rule.name': 'rule.name',
'rule.category': 'rule.category',
producer: 'kibana.rac.alert.producer',
producer: ALERT_PRODUCER,
tags: 'tags',
};

View file

@ -6,6 +6,8 @@
*/
import { EuiDataGridColumn } from '@elastic/eui';
import { ALERT_DURATION, ALERT_STATUS } from '@kbn/rule-data-utils';
import { ColumnHeaderOptions } from '../../../../../common';
import { defaultColumnHeaderType } from '../../../../timelines/components/timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../../timelines/components/timeline/body/constants';
@ -22,7 +24,7 @@ export const columns: Array<
{
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.STATUS,
id: 'kibana.rac.alert.status',
id: ALERT_STATUS,
initialWidth: 74,
},
{
@ -34,7 +36,7 @@ export const columns: Array<
{
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.ALERT_DURATION,
id: 'kibana.rac.alert.duration.us',
id: ALERT_DURATION,
initialWidth: 116,
},
{

View file

@ -9,6 +9,8 @@ import { mount } from 'enzyme';
import { cloneDeep } from 'lodash/fp';
import React from 'react';
import { ALERT_DURATION, ALERT_STATUS } from '@kbn/rule-data-utils';
import { mockBrowserFields } from '../../../../common/containers/source/mock';
import { DragDropContextWrapper } from '../../../../common/components/drag_and_drop/drag_drop_context_wrapper';
import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../common/mock';
@ -55,7 +57,7 @@ describe('RenderCellValue', () => {
const wrapper = mount(
<TestProviders>
<DragDropContextWrapper browserFields={mockBrowserFields}>
<RenderCellValue {...props} columnId="kibana.rac.alert.status" />
<RenderCellValue {...props} columnId={ALERT_STATUS} />
</DragDropContextWrapper>
</TestProviders>
);
@ -67,7 +69,7 @@ describe('RenderCellValue', () => {
const wrapper = mount(
<TestProviders>
<DragDropContextWrapper browserFields={mockBrowserFields}>
<RenderCellValue {...props} columnId="kibana.rac.alert.duration.us" />
<RenderCellValue {...props} columnId={ALERT_DURATION} />
</DragDropContextWrapper>
</TestProviders>
);

View file

@ -4,12 +4,13 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiDataGridCellValueElementProps, EuiLink } from '@elastic/eui';
import { random } from 'lodash/fp';
import moment from 'moment';
import React from 'react';
import { EuiDataGridCellValueElementProps, EuiLink } from '@elastic/eui';
import { ALERT_DURATION, ALERT_STATUS } from '@kbn/rule-data-utils';
import { TruncatableText } from '../../../../common/components/truncatable_text';
import { Severity } from '../../../components/severity';
import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/body/data_driven_columns';
@ -48,11 +49,11 @@ export const RenderCellValue: React.FC<
})?.reduce((x) => x[0]) ?? '';
switch (columnId) {
case 'kibana.rac.alert.status':
case ALERT_STATUS:
return (
<Status data-test-subj="alert-status" status={random(0, 1) ? 'recovered' : 'active'} />
);
case 'kibana.rac.alert.duration.us':
case ALERT_DURATION:
return <span data-test-subj="alert-duration">{moment().fromNow(true)}</span>;
case 'signal.rule.severity':
return <Severity data-test-subj="rule-severity" severity={value} />;

View file

@ -10,8 +10,8 @@ import { AlertInstance } from '../../../../../alerting/server';
import { RuleParams } from '../schemas/rule_schemas';
export type NotificationRuleTypeParams = RuleParams & {
name: string;
id: string;
name: string;
};
interface ScheduleNotificationActions {

View file

@ -1,90 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { createEqlAlertType } from './eql';
import { sequenceResponse } from './__mocks__/eql';
import { createRuleTypeMocks } from './__mocks__/rule_type';
describe('EQL alerts', () => {
it('does not send an alert when sequence not found', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const eqlAlertType = createEqlAlertType(dependencies.ruleDataClient, dependencies.logger);
dependencies.alerting.registerType(eqlAlertType);
const params = {
eqlQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]',
indexPatterns: ['*'],
};
services.scopedClusterClient.asCurrentUser.transport.request.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: {
hits: [],
sequences: [],
events: [],
total: {
relation: 'eq',
value: 0,
},
},
took: 0,
timed_out: false,
_shards: {
failed: 0,
skipped: 0,
successful: 1,
total: 1,
},
})
);
await executor({ params });
expect(services.alertInstanceFactory).not.toBeCalled();
});
it('sends a properly formatted alert when sequence is found', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const eqlAlertType = createEqlAlertType(dependencies.ruleDataClient, dependencies.logger);
dependencies.alerting.registerType(eqlAlertType);
const params = {
eqlQuery: 'sequence by host.name↵[any where true]↵[any where true]↵[any where true]',
indexPatterns: ['*'],
};
services.scopedClusterClient.asCurrentUser.transport.request.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: sequenceResponse.rawResponse.body.hits,
took: 0,
timed_out: false,
_shards: {
failed: 0,
skipped: 0,
successful: 1,
total: 1,
},
})
);
await executor({ params });
expect(services.alertInstanceFactory).toBeCalled();
/*
expect(services.alertWithPersistence).toBeCalledWith(
expect.arrayContaining([
expect.objectContaining({
'event.kind': 'signal',
'kibana.rac.alert.building_block_type': 'default',
}),
])
);
*/
});
});

View file

@ -1,122 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import v4 from 'uuid/v4';
import { ApiResponse } from '@elastic/elasticsearch';
import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/logging';
import {
RuleDataClient,
createPersistenceRuleTypeFactory,
} from '../../../../../rule_registry/server';
import { EQL_ALERT_TYPE_ID } from '../../../../common/constants';
import { buildEqlSearchRequest } from '../../../../common/detection_engine/get_query_filter';
import { BaseSignalHit, EqlSignalSearchResponse } from '../signals/types';
export const createEqlAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => {
const createPersistenceRuleType = createPersistenceRuleTypeFactory({
ruleDataClient,
logger,
});
return createPersistenceRuleType({
id: EQL_ALERT_TYPE_ID,
name: 'EQL Rule',
validate: {
params: schema.object({
eqlQuery: schema.string(),
indexPatterns: schema.arrayOf(schema.string()),
}),
},
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
actionVariables: {
context: [{ name: 'server', description: 'the server' }],
},
minimumLicenseRequired: 'basic',
isExportable: false,
producer: 'security-solution',
async executor({
startedAt,
services: { alertWithPersistence, findAlerts, scopedClusterClient },
params: { indexPatterns, eqlQuery },
}) {
const from = moment(startedAt).subtract(moment.duration(5, 'm')).toISOString(); // hardcoded 5-minute rule interval
const to = startedAt.toISOString();
const request = buildEqlSearchRequest(
eqlQuery,
indexPatterns,
from,
to,
10,
undefined,
[],
undefined
);
const { body: response } = (await scopedClusterClient.asCurrentUser.transport.request(
request
)) as ApiResponse<EqlSignalSearchResponse>;
const buildSignalFromEvent = (event: BaseSignalHit) => {
return {
...event,
'event.kind': 'signal',
'kibana.rac.alert.id': '???',
'kibana.rac.alert.uuid': v4(),
'@timestamp': new Date().toISOString(),
};
};
/* eslint-disable @typescript-eslint/no-explicit-any */
let alerts: any[] = [];
if (response.hits.sequences !== undefined) {
alerts = response.hits.sequences.reduce((allAlerts: any[], sequence) => {
let previousAlertUuid: string | undefined;
return [
...allAlerts,
...sequence.events.map((event, idx) => {
const alert = {
...buildSignalFromEvent(event),
'kibana.rac.alert.ancestors': previousAlertUuid != null ? [previousAlertUuid] : [],
'kibana.rac.alert.building_block_type': 'default',
'kibana.rac.alert.depth': idx,
};
previousAlertUuid = alert['kibana.rac.alert.uuid'];
return alert;
}),
];
}, []);
} else if (response.hits.events !== undefined) {
alerts = response.hits.events.map((event) => {
return buildSignalFromEvent(event);
}, []);
} else {
throw new Error(
'eql query response should have either `sequences` or `events` but had neither'
);
}
if (alerts.length > 0) {
alertWithPersistence(alerts).forEach((alert) => {
alert.scheduleActions('default', { server: 'server-test' });
});
}
return {
lastChecked: new Date(),
};
},
});
};

View file

@ -1,89 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/logging';
import { ESSearchRequest } from 'src/core/types/elasticsearch';
import { buildEsQuery } from '@kbn/es-query';
import type { IIndexPattern } from 'src/plugins/data/public';
import {
RuleDataClient,
createPersistenceRuleTypeFactory,
} from '../../../../../rule_registry/server';
import { CUSTOM_ALERT_TYPE_ID } from '../../../../common/constants';
export const createQueryAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => {
const createPersistenceRuleType = createPersistenceRuleTypeFactory({
ruleDataClient,
logger,
});
return createPersistenceRuleType({
id: CUSTOM_ALERT_TYPE_ID,
name: 'Custom Query Rule',
validate: {
params: schema.object({
indexPatterns: schema.arrayOf(schema.string()),
customQuery: schema.string(),
}),
},
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
actionVariables: {
context: [{ name: 'server', description: 'the server' }],
},
minimumLicenseRequired: 'basic',
isExportable: false,
producer: 'security-solution',
async executor({
services: { alertWithPersistence, findAlerts },
params: { indexPatterns, customQuery },
}) {
try {
const indexPattern: IIndexPattern = {
fields: [],
title: indexPatterns.join(),
};
// TODO: kql or lucene?
const esQuery = buildEsQuery(
indexPattern,
{ query: customQuery, language: 'kuery' },
[]
) as estypes.QueryDslQueryContainer;
const query: ESSearchRequest = {
body: {
query: esQuery,
fields: ['*'],
sort: {
'@timestamp': 'asc' as const,
},
},
};
const alerts = await findAlerts(query);
alertWithPersistence(alerts).forEach((alert) => {
alert.scheduleActions('default', { server: 'server-test' });
});
return {
lastChecked: new Date(),
};
} catch (error) {
logger.error(error);
}
},
});
};

View file

@ -1,34 +0,0 @@
#!/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
# 2.0; you may not use this file except in compliance with the Elastic License
# 2.0.
#
curl -X POST http://localhost:5601/${BASE_PATH}/api/alerts/alert \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-H 'kbn-xsrf: true' \
-H 'Content-Type: application/json' \
--verbose \
-d '
{
"params":{
"indexPatterns": ["*"],
"eqlQuery": "sequence by host.name↵[any where true]↵[any where true]↵[any where true]"
},
"consumer":"alerts",
"alertTypeId":"siem.eqlRule",
"schedule":{
"interval":"1m"
},
"actions":[],
"tags":[
"eql",
"persistence"
],
"notifyWhen":"onActionGroupChange",
"name":"Basic EQL rule"
}'

View file

@ -1,37 +0,0 @@
#!/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
# 2.0; you may not use this file except in compliance with the Elastic License
# 2.0.
#
curl -X POST http://localhost:5601/${BASE_PATH}/api/alerts/alert \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-H 'kbn-xsrf: true' \
-H 'Content-Type: application/json' \
--verbose \
-d '
{
"params":{
"indexPatterns": ["*"],
"customQuery": "*:*",
"thresholdFields": ["source.ip", "destination.ip"],
"thresholdValue": 50,
"thresholdCardinality": []
},
"consumer":"alerts",
"alertTypeId":"siem.thresholdRule",
"schedule":{
"interval":"1m"
},
"actions":[],
"tags":[
"persistence",
"threshold"
],
"notifyWhen":"onActionGroupChange",
"name":"Basic Threshold rule"
}'

View file

@ -1,132 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { createRuleTypeMocks } from './__mocks__/rule_type';
import { mockThresholdResults } from './__mocks__/threshold';
import { createThresholdAlertType } from './threshold';
describe('Threshold alerts', () => {
it('does not send an alert when threshold is not met', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const thresholdAlertType = createThresholdAlertType(
dependencies.ruleDataClient,
dependencies.logger
);
dependencies.alerting.registerType(thresholdAlertType);
const params = {
indexPatterns: ['*'],
customQuery: '*:*',
thresholdFields: ['source.ip', 'host.name'],
thresholdValue: 4,
};
services.scopedClusterClient.asCurrentUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: {
hits: [],
sequences: [],
events: [],
total: {
relation: 'eq',
value: 0,
},
},
aggregations: {
'threshold_0:source.ip': {
buckets: [],
},
},
took: 0,
timed_out: false,
_shards: {
failed: 0,
skipped: 0,
successful: 1,
total: 1,
},
})
);
await executor({ params });
expect(services.alertInstanceFactory).not.toBeCalled();
});
it('sends a properly formatted alert when threshold is met', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const thresholdAlertType = createThresholdAlertType(
dependencies.ruleDataClient,
dependencies.logger
);
dependencies.alerting.registerType(thresholdAlertType);
const params = {
indexPatterns: ['*'],
customQuery: '*:*',
thresholdFields: ['source.ip', 'host.name'],
thresholdValue: 4,
};
services.scopedClusterClient.asCurrentUser.search
.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: {
hits: [],
total: {
relation: 'eq',
value: 0,
},
},
took: 0,
timed_out: false,
_shards: {
failed: 0,
skipped: 0,
successful: 1,
total: 1,
},
})
)
.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
hits: {
hits: [],
total: {
relation: 'eq',
value: 0,
},
},
aggregations: mockThresholdResults.rawResponse.body.aggregations,
took: 0,
timed_out: false,
_shards: {
failed: 0,
skipped: 0,
successful: 1,
total: 1,
},
})
);
await executor({ params });
expect(services.alertInstanceFactory).toBeCalled();
/*
expect(services.alertWithPersistence).toBeCalledWith(
expect.arrayContaining([
expect.objectContaining({
'event.kind': 'signal',
}),
])
);
*/
});
});

View file

@ -1,207 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import moment from 'moment';
import v4 from 'uuid/v4';
import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/logging';
import { AlertServices } from '../../../../../alerting/server';
import {
RuleDataClient,
createPersistenceRuleTypeFactory,
} from '../../../../../rule_registry/server';
import { THRESHOLD_ALERT_TYPE_ID } from '../../../../common/constants';
import { SignalSearchResponse, ThresholdSignalHistory } from '../signals/types';
import {
findThresholdSignals,
getThresholdBucketFilters,
getThresholdSignalHistory,
transformThresholdResultsToEcs,
} from '../signals/threshold';
import { getFilter } from '../signals/get_filter';
import { BuildRuleMessage } from '../signals/rule_messages';
interface RuleParams {
indexPatterns: string[];
customQuery: string;
thresholdFields: string[];
thresholdValue: number;
thresholdCardinality: Array<{
field: string;
value: number;
}>;
}
interface BulkCreateThresholdSignalParams {
results: SignalSearchResponse;
ruleParams: RuleParams;
services: AlertServices & { logger: Logger };
inputIndexPattern: string[];
ruleId: string;
startedAt: Date;
from: Date;
thresholdSignalHistory: ThresholdSignalHistory;
buildRuleMessage: BuildRuleMessage;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const formatThresholdSignals = (params: BulkCreateThresholdSignalParams): any[] => {
const thresholdResults = params.results;
const threshold = {
field: params.ruleParams.thresholdFields,
value: params.ruleParams.thresholdValue,
};
const results = transformThresholdResultsToEcs(
thresholdResults,
params.ruleParams.indexPatterns.join(','),
params.startedAt,
params.from,
undefined,
params.services.logger,
threshold,
params.ruleId,
undefined,
params.thresholdSignalHistory
);
return results.hits.hits.map((hit) => {
return {
...hit,
'event.kind': 'signal',
'kibana.rac.alert.id': '???',
'kibana.rac.alert.uuid': v4(),
'@timestamp': new Date().toISOString(),
};
});
};
export const createThresholdAlertType = (ruleDataClient: RuleDataClient, logger: Logger) => {
const createPersistenceRuleType = createPersistenceRuleTypeFactory({
ruleDataClient,
logger,
});
return createPersistenceRuleType({
id: THRESHOLD_ALERT_TYPE_ID,
name: 'Threshold Rule',
validate: {
params: schema.object({
indexPatterns: schema.arrayOf(schema.string()),
customQuery: schema.string(),
thresholdFields: schema.arrayOf(schema.string()),
thresholdValue: schema.number(),
thresholdCardinality: schema.arrayOf(
schema.object({
field: schema.string(),
value: schema.number(),
})
),
}),
},
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
actionVariables: {
context: [{ name: 'server', description: 'the server' }],
},
minimumLicenseRequired: 'basic',
isExportable: false,
producer: 'security-solution',
async executor({ startedAt, services, params, alertId }) {
const fromDate = moment(startedAt).subtract(moment.duration(5, 'm')); // hardcoded 5-minute rule interval
const from = fromDate.toISOString();
const to = startedAt.toISOString();
// TODO: how to get the output index?
const outputIndex = ['.kibana-madi-8-alerts-security-solution-8.0.0-000001'];
const buildRuleMessage = (...messages: string[]) => messages.join();
const timestampOverride = undefined;
const {
thresholdSignalHistory,
searchErrors: previousSearchErrors,
} = await getThresholdSignalHistory({
indexPattern: outputIndex,
from,
to,
services: (services as unknown) as AlertServices,
logger,
ruleId: alertId,
bucketByFields: params.thresholdFields,
timestampOverride,
buildRuleMessage,
});
const bucketFilters = await getThresholdBucketFilters({
thresholdSignalHistory,
timestampOverride,
});
const esFilter = await getFilter({
type: 'threshold',
filters: bucketFilters,
language: 'kuery',
query: params.customQuery,
savedId: undefined,
services: (services as unknown) as AlertServices,
index: params.indexPatterns,
lists: [],
});
const {
searchResult: thresholdResults,
searchErrors,
searchDuration: thresholdSearchDuration,
} = await findThresholdSignals({
inputIndexPattern: params.indexPatterns,
from,
to,
services: (services as unknown) as AlertServices,
logger,
filter: esFilter,
threshold: {
field: params.thresholdFields,
value: params.thresholdValue,
cardinality: params.thresholdCardinality,
},
timestampOverride,
buildRuleMessage,
});
logger.info(`Threshold search took ${thresholdSearchDuration}ms`); // TODO: rule status service
const alerts = formatThresholdSignals({
results: thresholdResults,
ruleParams: params,
services: (services as unknown) as AlertServices & { logger: Logger },
inputIndexPattern: ['TODO'],
ruleId: alertId,
startedAt,
from: fromDate.toDate(),
thresholdSignalHistory,
buildRuleMessage,
});
const errors = searchErrors.concat(previousSearchErrors);
if (errors.length === 0) {
services.alertWithPersistence(alerts).forEach((alert) => {
alert.scheduleActions('default', { server: 'server-test' });
});
} else {
throw new Error(errors.join('\n'));
}
return {
lastChecked: new Date(),
};
},
});
};

View file

@ -28,7 +28,7 @@ export const bootstrapRuleExecutionLog = async (
settings: {
number_of_shards: 1,
},
mappings: mappingFromFieldMap(ruleExecutionFieldMap),
mappings: mappingFromFieldMap(ruleExecutionFieldMap, 'strict'),
},
},
});

View file

@ -6,7 +6,11 @@
*/
import { Logger } from '@kbn/logging';
import { AlertInstanceContext, AlertTypeParams } from '../../../../../alerting/common';
import {
AlertInstanceContext,
AlertTypeParams,
AlertTypeState,
} from '../../../../../alerting/common';
import { AlertTypeWithExecutor, RuleDataPluginService } from '../../../../../rule_registry/server';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { RuleExecutionLogClient } from './rule_execution_log_client';
@ -21,12 +25,13 @@ type WithRuleExecutionLog = (args: {
logger: Logger;
ruleDataService: RuleDataPluginService;
}) => <
TState extends AlertTypeState,
TParams extends AlertTypeParams,
TAlertInstanceContext extends AlertInstanceContext,
TServices extends ExecutionLogServices
>(
type: AlertTypeWithExecutor<TParams, TAlertInstanceContext, TServices>
) => AlertTypeWithExecutor<TParams, TAlertInstanceContext, TServices>;
type: AlertTypeWithExecutor<TState, TParams, TAlertInstanceContext, TServices>
) => AlertTypeWithExecutor<TState, TParams, TAlertInstanceContext, TServices>;
export const withRuleExecutionLogFactory: WithRuleExecutionLog = ({ logger, ruleDataService }) => (
type

View file

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const createRuleMock = () => ({
actions: [],
author: [],
buildingBlockType: undefined,
createdAt: '2020-01-10T21:11:45.839Z',
createdBy: 'elastic',
description: '24/7',
enabled: true,
eventCategoryOverride: undefined,
exceptionsList: [],
falsePositives: [],
filters: [],
from: 'now-300s',
id: 'cf1f6a49-18a3-4794-aad7-0e8482e075e9',
immutable: false,
index: ['auditbeat-*'],
interval: '5m',
language: 'kuery',
license: 'basic',
maxSignals: 100,
meta: { from: '0m' },
name: 'Home Grown!',
note: '# this is some markdown documentation',
outputIndex: '.siem-signals-default',
query: '',
riskScore: 21,
riskScoreMapping: [],
references: [],
ruleId: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea',
ruleNameOverride: undefined,
savedId: "Garrett's IP",
tags: [],
threat: [],
throttle: 'no_actions',
timelineId: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2',
timelineTitle: 'Untitled timeline',
timestampOverride: undefined,
to: 'now',
type: 'query',
severity: 'low',
severityMapping: [],
updatedAt: '2020-01-10T21:11:45.839Z',
updatedBy: 'elastic',
version: 1,
});

View file

@ -8,12 +8,15 @@
import { of } from 'rxjs';
import { v4 } from 'uuid';
import { Logger } from 'kibana/server';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { Logger, SavedObject } from 'kibana/server';
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { RuleDataClient } from '../../../../../../rule_registry/server';
import { PluginSetupContract as AlertingPluginSetupContract } from '../../../../../../alerting/server';
import { ConfigType } from '../../../../config';
import { AlertAttributes } from '../../signals/types';
import { createRuleMock } from './rule';
import { listMock } from '../../../../../../lists/server/mocks';
export const createRuleTypeMocks = () => {
/* eslint-disable @typescript-eslint/no-explicit-any */
@ -36,7 +39,29 @@ export const createRuleTypeMocks = () => {
const scheduleActions = jest.fn();
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.get.mockResolvedValue({
id: 'de2f6a49-28a3-4794-bad7-0e9482e075f8',
type: 'query',
references: [],
attributes: {
actions: [],
enabled: true,
name: 'mock rule',
tags: [],
createdBy: 'user1',
createdAt: '',
updatedBy: 'user2',
schedule: {
interval: '30m',
},
throttle: '',
params: createRuleMock(),
},
} as SavedObject<AlertAttributes>);
const services = {
savedObjectsClient: mockSavedObjectsClient,
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
alertInstanceFactory: jest.fn(() => ({ scheduleActions })),
findAlerts: jest.fn(), // TODO: does this stay?
@ -47,19 +72,17 @@ export const createRuleTypeMocks = () => {
return {
dependencies: {
alerting,
buildRuleMessage: jest.fn(),
config$: mockedConfig$,
lists: listMock.createSetup(),
logger: loggerMock,
ruleDataClient: ({
getReader: () => {
return {
search: jest.fn(),
};
},
getWriter: () => {
return {
bulk: jest.fn(),
};
},
getReader: jest.fn(() => ({
search: jest.fn(),
})),
getWriter: jest.fn(() => ({
bulk: jest.fn(),
})),
isWriteEnabled: jest.fn(() => true),
} as unknown) as RuleDataClient,
},

View file

@ -0,0 +1,324 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isEmpty } from 'lodash';
import { flow } from 'fp-ts/lib/function';
import { Either, chain, fold, tryCatch } from 'fp-ts/lib/Either';
import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils';
import { ListArray } from '@kbn/securitysolution-io-ts-list-types';
import { toError } from '@kbn/securitysolution-list-api';
import { createPersistenceRuleTypeFactory } from '../../../../../rule_registry/server';
import { ruleStatusSavedObjectsClientFactory } from '../signals/rule_status_saved_objects_client';
import { ruleStatusServiceFactory } from '../signals/rule_status_service';
import { buildRuleMessageFactory } from './factories/build_rule_message_factory';
import {
checkPrivilegesFromEsClient,
getExceptions,
getRuleRangeTuples,
hasReadIndexPrivileges,
hasTimestampFields,
isMachineLearningParams,
} from '../signals/utils';
import { DEFAULT_MAX_SIGNALS, DEFAULT_SEARCH_AFTER_PAGE_SIZE } from '../../../../common/constants';
import { CreateSecurityRuleTypeFactory } from './types';
import { getListClient } from './utils/get_list_client';
import {
NotificationRuleTypeParams,
scheduleNotificationActions,
} from '../notifications/schedule_notification_actions';
import { getNotificationResultsLink } from '../notifications/utils';
import { createResultObject } from './utils';
import { bulkCreateFactory, wrapHitsFactory } from './factories';
/* eslint-disable complexity */
export const createSecurityRuleTypeFactory: CreateSecurityRuleTypeFactory = ({
indexAlias,
lists,
logger,
mergeStrategy,
ruleDataClient,
}) => (type) => {
const persistenceRuleType = createPersistenceRuleTypeFactory({ ruleDataClient, logger });
return persistenceRuleType({
...type,
async executor(options) {
const {
alertId,
params,
previousStartedAt,
services,
spaceId,
state,
updatedBy: updatedByUser,
} = options;
let runState = state;
const { from, maxSignals, meta, ruleId, timestampOverride, to } = params;
const { alertWithPersistence, savedObjectsClient, scopedClusterClient } = services;
const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE);
const esClient = scopedClusterClient.asCurrentUser;
const ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient);
const ruleStatusService = await ruleStatusServiceFactory({
alertId,
ruleStatusClient,
});
const ruleSO = await savedObjectsClient.get('alert', alertId);
const {
actions,
name,
schedule: { interval },
} = ruleSO.attributes;
const refresh = actions.length ? 'wait_for' : false;
const buildRuleMessage = buildRuleMessageFactory({
id: alertId,
ruleId,
name,
index: indexAlias,
});
logger.debug(buildRuleMessage('[+] Starting Signal Rule execution'));
logger.debug(buildRuleMessage(`interval: ${interval}`));
let wroteWarningStatus = false;
await ruleStatusService.goingToRun();
let result = createResultObject(state);
// check if rule has permissions to access given index pattern
// move this collection of lines into a function in utils
// so that we can use it in create rules route, bulk, etc.
try {
if (!isMachineLearningParams(params)) {
const index = params.index;
const hasTimestampOverride = !!timestampOverride;
const inputIndices = params.index ?? [];
const [privileges, timestampFieldCaps] = await Promise.all([
checkPrivilegesFromEsClient(esClient, inputIndices),
esClient.fieldCaps({
index: index ?? ['*'],
fields: hasTimestampOverride
? ['@timestamp', timestampOverride as string]
: ['@timestamp'],
include_unmapped: true,
}),
]);
fold<Error, Promise<boolean>, void>(
async (error: Error) => logger.error(buildRuleMessage(error.message)),
async (status: Promise<boolean>) => (wroteWarningStatus = await status)
)(
flow(
() =>
tryCatch(
() =>
hasReadIndexPrivileges(privileges, logger, buildRuleMessage, ruleStatusService),
toError
),
chain((wroteStatus: unknown) =>
tryCatch(
() =>
hasTimestampFields(
wroteStatus as boolean,
hasTimestampOverride ? (timestampOverride as string) : '@timestamp',
name,
timestampFieldCaps,
inputIndices,
ruleStatusService,
logger,
buildRuleMessage
),
toError
)
)
)() as Either<Error, Promise<boolean>>
);
}
} catch (exc) {
logger.error(buildRuleMessage(`Check privileges failed to execute ${exc}`));
}
let hasError = false;
const { tuples, remainingGap } = getRuleRangeTuples({
logger,
previousStartedAt,
from: from as string,
to: to as string,
interval,
maxSignals: DEFAULT_MAX_SIGNALS,
buildRuleMessage,
});
if (remainingGap.asMilliseconds() > 0) {
const gapString = remainingGap.humanize();
const gapMessage = buildRuleMessage(
`${gapString} (${remainingGap.asMilliseconds()}ms) were not queried between this rule execution and the last execution, so signals may have been missed.`,
'Consider increasing your look behind time or adding more Kibana instances.'
);
logger.warn(gapMessage);
hasError = true;
await ruleStatusService.error(gapMessage, { gap: gapString });
}
try {
const { listClient, exceptionsClient } = getListClient({
esClient: services.scopedClusterClient.asCurrentUser,
updatedByUser,
spaceId,
lists,
savedObjectClient: options.services.savedObjectsClient,
});
const exceptionItems = await getExceptions({
client: exceptionsClient,
lists: (params.exceptionsList as ListArray) ?? [],
});
const bulkCreate = bulkCreateFactory(
logger,
alertWithPersistence,
buildRuleMessage,
refresh
);
const wrapHits = wrapHitsFactory({
ruleSO,
mergeStrategy,
});
for (const tuple of tuples) {
const runResult = await type.executor({
...options,
services,
state: runState,
runOpts: {
buildRuleMessage,
bulkCreate,
exceptionItems,
listClient,
rule: ruleSO,
searchAfterSize,
tuple,
wrapHits,
},
});
const createdSignals = result.createdSignals.concat(runResult.createdSignals);
const warningMessages = result.warningMessages.concat(runResult.warningMessages);
result = {
bulkCreateTimes: result.bulkCreateTimes.concat(runResult.bulkCreateTimes),
createdSignals,
createdSignalsCount: createdSignals.length,
errors: result.errors.concat(runResult.errors),
lastLookbackDate: runResult.lastLookbackDate,
searchAfterTimes: result.searchAfterTimes.concat(runResult.searchAfterTimes),
state: runState,
success: result.success && runResult.success,
warning: warningMessages.length > 0,
warningMessages,
};
runState = runResult.state;
}
if (result.warningMessages.length) {
const warningMessage = buildRuleMessage(result.warningMessages.join());
await ruleStatusService.partialFailure(warningMessage);
}
if (result.success) {
const createdSignalsCount = result.createdSignals.length;
if (actions.length) {
const notificationRuleParams: NotificationRuleTypeParams = ({
...params,
name: name as string,
id: ruleSO.id as string,
} as unknown) as NotificationRuleTypeParams;
const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x');
const toInMs = parseScheduleDates('now')?.format('x');
const resultsLink = getNotificationResultsLink({
from: fromInMs,
to: toInMs,
id: ruleSO.id,
kibanaSiemAppUrl: (meta as { kibana_siem_app_url?: string } | undefined)
?.kibana_siem_app_url,
});
logger.info(buildRuleMessage(`Found ${createdSignalsCount} signals for notification.`));
if (createdSignalsCount) {
const alertInstance = services.alertInstanceFactory(alertId);
scheduleNotificationActions({
alertInstance,
signalsCount: createdSignalsCount,
signals: result.createdSignals,
resultsLink,
ruleParams: notificationRuleParams,
});
}
}
logger.debug(buildRuleMessage('[+] Signal Rule execution completed.'));
logger.debug(
buildRuleMessage(
`[+] Finished indexing ${createdSignalsCount} signals into alias ${indexAlias}`
)
);
if (!hasError && !wroteWarningStatus && !result.warning) {
await ruleStatusService.success('succeeded', {
bulkCreateTimeDurations: result.bulkCreateTimes,
searchAfterTimeDurations: result.searchAfterTimes,
lastLookBackDate: result.lastLookbackDate?.toISOString(),
});
}
// adding this log line so we can get some information from cloud
logger.info(
buildRuleMessage(
`[+] Finished indexing ${createdSignalsCount} ${
!isEmpty(tuples)
? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}`
: ''
}`
)
);
} else {
const errorMessage = buildRuleMessage(
'Bulk Indexing of signals failed:',
result.errors.join()
);
logger.error(errorMessage);
await ruleStatusService.error(errorMessage, {
bulkCreateTimeDurations: result.bulkCreateTimes,
searchAfterTimeDurations: result.searchAfterTimes,
lastLookBackDate: result.lastLookbackDate?.toISOString(),
});
}
} catch (error) {
const errorMessage = error.message ?? '(no error message given)';
const message = buildRuleMessage(
'An error occurred during rule execution:',
`message: "${errorMessage}"`
);
logger.error(message);
await ruleStatusService.error(message, {
bulkCreateTimeDurations: result.bulkCreateTimes,
searchAfterTimeDurations: result.searchAfterTimes,
lastLookBackDate: result.lastLookbackDate?.toISOString(),
});
}
return result.state;
},
});
};

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export type BuildRuleMessage = (...messages: string[]) => string;
export interface BuildRuleMessageFactoryParams {
name: string;
id: string;
ruleId: string | null | undefined;
index: string;
}
export const buildRuleMessageFactory = ({
id,
ruleId,
index,
name,
}: BuildRuleMessageFactoryParams): BuildRuleMessage => (...messages) =>
[
...messages,
`name: "${name}"`,
`id: "${id}"`,
`rule id: "${ruleId ?? '(unknown rule id)'}"`,
`signals index alias: "${index}"`,
].join(' ');

View file

@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { performance } from 'perf_hooks';
import { countBy, isEmpty } from 'lodash';
import { Logger } from 'kibana/server';
import { BaseHit } from '../../../../../common/detection_engine/types';
import { BuildRuleMessage } from '../../signals/rule_messages';
import { errorAggregator, makeFloatString } from '../../signals/utils';
import { RefreshTypes } from '../../types';
import { PersistenceAlertService } from '../../../../../../rule_registry/server';
import { AlertInstanceContext } from '../../../../../../alerting/common';
export interface GenericBulkCreateResponse<T> {
success: boolean;
bulkCreateDuration: string;
createdItemsCount: number;
createdItems: Array<T & { _id: string; _index: string }>;
errors: string[];
}
export const bulkCreateFactory = <TContext extends AlertInstanceContext>(
logger: Logger,
alertWithPersistence: PersistenceAlertService<TContext>,
buildRuleMessage: BuildRuleMessage,
refreshForBulkCreate: RefreshTypes
) => async <T>(wrappedDocs: Array<BaseHit<T>>): Promise<GenericBulkCreateResponse<T>> => {
if (wrappedDocs.length === 0) {
return {
errors: [],
success: true,
bulkCreateDuration: '0',
createdItemsCount: 0,
createdItems: [],
};
}
const start = performance.now();
const response = await alertWithPersistence(
wrappedDocs.map((doc) => ({
id: doc._id,
fields: doc.fields ?? doc._source ?? {},
})),
refreshForBulkCreate
);
const end = performance.now();
logger.debug(
buildRuleMessage(
`individual bulk process time took: ${makeFloatString(end - start)} milliseconds`
)
);
logger.debug(
buildRuleMessage(`took property says bulk took: ${response.body.took} milliseconds`)
);
const createdItems = wrappedDocs
.map((doc, index) => ({
_id: response.body.items[index].index?._id ?? '',
_index: response.body.items[index].index?._index ?? '',
...doc._source,
}))
.filter((_, index) => response.body.items[index].index?.status === 201);
const createdItemsCount = createdItems.length;
const duplicateSignalsCount = countBy(response.body.items, 'create.status')['409'];
const errorCountByMessage = errorAggregator(response.body, [409]);
logger.debug(buildRuleMessage(`bulk created ${createdItemsCount} signals`));
if (duplicateSignalsCount > 0) {
logger.debug(buildRuleMessage(`ignored ${duplicateSignalsCount} duplicate signals`));
}
if (!isEmpty(errorCountByMessage)) {
logger.error(
buildRuleMessage(
`[-] bulkResponse had errors with responses of: ${JSON.stringify(errorCountByMessage)}`
)
);
return {
errors: Object.keys(errorCountByMessage),
success: false,
bulkCreateDuration: makeFloatString(end - start),
createdItemsCount: createdItems.length,
createdItems,
};
} else {
return {
errors: [],
success: true,
bulkCreateDuration: makeFloatString(end - start),
createdItemsCount: createdItems.length,
createdItems,
};
}
};

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './build_rule_message_factory';
export * from './bulk_create_factory';
export * from './wrap_hits_factory';

View file

@ -0,0 +1,147 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ALERT_STATUS, ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
import { SearchTypes } from '../../../../../../common/detection_engine/types';
import { RulesSchema } from '../../../../../../common/detection_engine/schemas/response/rules_schema';
import { isEventTypeSignal } from '../../../signals/build_event_type_signal';
import {
Ancestor,
BaseSignalHit,
SignalHit,
SignalSourceHit,
ThresholdResult,
} from '../../../signals/types';
import { getValidDateFromDoc } from '../../../signals/utils';
import { invariant } from '../../../../../../common/utils/invariant';
import { DEFAULT_MAX_SIGNALS } from '../../../../../../common/constants';
/**
* Takes a parent signal or event document and extracts the information needed for the corresponding entry in the child
* signal's `signal.parents` array.
* @param doc The parent signal or event
*/
export const buildParent = (doc: BaseSignalHit): Ancestor => {
if (doc._source?.signal != null) {
return {
rule: doc._source?.signal.rule.id,
id: doc._id,
type: 'signal',
index: doc._index,
// We first look for signal.depth and use that if it exists. If it doesn't exist, this should be a pre-7.10 signal
// and should have signal.parent.depth instead. signal.parent.depth in this case is treated as equivalent to signal.depth.
depth: doc._source?.signal.depth ?? doc._source?.signal.parent?.depth ?? 1,
};
} else {
return {
id: doc._id,
type: 'event',
index: doc._index,
depth: 0,
};
}
};
/**
* Takes a parent signal or event document with N ancestors and adds the parent document to the ancestry array,
* creating an array of N+1 ancestors.
* @param doc The parent signal/event for which to extend the ancestry.
*/
export const buildAncestors = (doc: BaseSignalHit): Ancestor[] => {
const newAncestor = buildParent(doc);
const existingAncestors = doc._source?.signal?.ancestors;
if (existingAncestors != null) {
return [...existingAncestors, newAncestor];
} else {
return [newAncestor];
}
};
/**
* This removes any signal name clashes such as if a source index has
* "signal" but is not a signal object we put onto the object. If this
* is our "signal object" then we don't want to remove it.
* @param doc The source index doc to a signal.
*/
export const removeClashes = (doc: BaseSignalHit): BaseSignalHit => {
invariant(doc._source, '_source field not found');
const { signal, ...noSignal } = doc._source;
if (signal == null || isEventTypeSignal(doc)) {
return doc;
} else {
return {
...doc,
_source: { ...noSignal },
};
}
};
/**
* Builds the `signal.*` fields that are common across all signals.
* @param docs The parent signals/events of the new signal to be built.
* @param rule The rule that is generating the new signal.
*/
export const buildAlert = (doc: SignalSourceHit, rule: RulesSchema) => {
const removedClashes = removeClashes(doc);
const parent = buildParent(removedClashes);
const ancestors = buildAncestors(removedClashes);
const immutable = doc._source?.signal?.rule.immutable ? 'true' : 'false';
const source = doc._source as SignalHit;
const signal = source?.signal;
const signalRule = signal?.rule;
return {
'kibana.alert.ancestors': ancestors as object[],
[ALERT_STATUS]: 'open',
[ALERT_WORKFLOW_STATUS]: 'open',
'kibana.alert.depth': parent.depth,
'kibana.alert.rule.false_positives': signalRule?.false_positives ?? [],
'kibana.alert.rule.id': rule.id,
'kibana.alert.rule.immutable': immutable,
'kibana.alert.rule.index': signalRule?.index ?? [],
'kibana.alert.rule.language': signalRule?.language ?? 'kuery',
'kibana.alert.rule.max_signals': signalRule?.max_signals ?? DEFAULT_MAX_SIGNALS,
'kibana.alert.rule.query': signalRule?.query ?? '*:*',
'kibana.alert.rule.saved_id': signalRule?.saved_id ?? '',
'kibana.alert.rule.threat_index': signalRule?.threat_index,
'kibana.alert.rule.threat_indicator_path': signalRule?.threat_indicator_path,
'kibana.alert.rule.threat_language': signalRule?.threat_language,
'kibana.alert.rule.threat_mapping.field': '', // TODO
'kibana.alert.rule.threat_mapping.value': '', // TODO
'kibana.alert.rule.threat_mapping.type': '', // TODO
'kibana.alert.rule.threshold.field': signalRule?.threshold?.field,
'kibana.alert.rule.threshold.value': signalRule?.threshold?.value,
'kibana.alert.rule.threshold.cardinality.field': '', // TODO
'kibana.alert.rule.threshold.cardinality.value': 0, // TODO
};
};
const isThresholdResult = (thresholdResult: SearchTypes): thresholdResult is ThresholdResult => {
return typeof thresholdResult === 'object';
};
/**
* Creates signal fields that are only available in the special case where a signal has only 1 parent signal/event.
* We copy the original time from the document as "original_time" since we override the timestamp with the current date time.
* @param doc The parent signal/event of the new signal to be built.
*/
export const additionalAlertFields = (doc: BaseSignalHit) => {
const thresholdResult = doc._source?.threshold_result;
if (thresholdResult != null && !isThresholdResult(thresholdResult)) {
throw new Error(`threshold_result failed to validate: ${thresholdResult}`);
}
const originalTime = getValidDateFromDoc({
doc,
timestampOverride: undefined,
});
return {
'kibana.alert.original_time': originalTime != null ? originalTime.toISOString() : undefined,
'kibana.alert.original_event': doc._source?.event ?? undefined,
'kibana.alert.threshold_result': thresholdResult,
};
};

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SavedObject } from 'src/core/types';
import type { ConfigType } from '../../../../../config';
import { buildRuleWithOverrides } from '../../../signals/build_rule';
import { getMergeStrategy } from '../../../signals/source_fields_merging/strategies';
import { AlertAttributes, SignalSourceHit } from '../../../signals/types';
import { RACAlert } from '../../types';
import { additionalAlertFields, buildAlert } from './build_alert';
import { filterSource } from './filter_source';
/**
* Formats the search_after result for insertion into the signals index. We first create a
* "best effort" merged "fields" with the "_source" object, then build the signal object,
* then the event object, and finally we strip away any additional temporary data that was added
* such as the "threshold_result".
* @param ruleSO The rule saved object to build overrides
* @param doc The SignalSourceHit with "_source", "fields", and additional data such as "threshold_result"
* @returns The body that can be added to a bulk call for inserting the signal.
*/
export const buildBulkBody = (
ruleSO: SavedObject<AlertAttributes>,
doc: SignalSourceHit,
mergeStrategy: ConfigType['alertMergeStrategy']
): RACAlert => {
const mergedDoc = getMergeStrategy(mergeStrategy)({ doc });
const rule = buildRuleWithOverrides(ruleSO, mergedDoc._source ?? {});
const filteredSource = filterSource(mergedDoc);
return {
...filteredSource,
...buildAlert(mergedDoc, rule),
...additionalAlertFields(mergedDoc),
'@timestamp': new Date().toISOString(),
};
};

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { buildEventTypeSignal } from '../../../signals/build_event_type_signal';
import { SignalSourceHit } from '../../../signals/types';
import { RACAlert } from '../../types';
export const filterSource = (doc: SignalSourceHit): Partial<RACAlert> => {
const event = buildEventTypeSignal(doc);
const docSource = doc._source ?? {};
const { threshold_result: thresholdResult, ...filteredSource } = docSource || {
threshold_result: null,
};
return {
...filteredSource,
event,
};
};

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SearchAfterAndBulkCreateParams, SignalSourceHit, WrapHits } from '../../signals/types';
import { buildBulkBody } from './utils/build_bulk_body';
import { generateId } from '../../signals/utils';
import { filterDuplicateSignals } from '../../signals/filter_duplicate_signals';
import type { ConfigType } from '../../../../config';
import { WrappedRACAlert } from '../types';
export const wrapHitsFactory = ({
ruleSO,
mergeStrategy,
}: {
ruleSO: SearchAfterAndBulkCreateParams['ruleSO'];
mergeStrategy: ConfigType['alertMergeStrategy'];
}): WrapHits => (events) => {
const wrappedDocs: WrappedRACAlert[] = events.flatMap((doc) => [
{
_index: '',
_id: generateId(
doc._index,
doc._id,
String(doc._version),
ruleSO.attributes.params.ruleId ?? ''
),
_source: buildBulkBody(ruleSO, doc as SignalSourceHit, mergeStrategy),
},
]);
return filterDuplicateSignals(ruleSO.id, wrappedDocs, true);
};

View file

@ -0,0 +1,298 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FieldMap } from '../../../../../../rule_registry/common/field_map';
export const alertsFieldMap: FieldMap = {
'kibana.alert.ancestors': {
type: 'object',
array: true,
required: true,
},
'kibana.alert.ancestors.depth': {
type: 'long',
array: false,
required: true,
},
'kibana.alert.ancestors.id': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.ancestors.index': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.ancestors.rule': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.ancestors.type': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.depth': {
type: 'long',
array: false,
required: true,
},
'kibana.alert.original_event': {
type: 'object',
array: false,
required: false,
},
'kibana.alert.original_event.action': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.agent_id_status': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.category': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.original_event.code': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.created': {
type: 'date',
array: false,
required: true,
},
'kibana.alert.original_event.dataset': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.duration': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.end': {
type: 'date',
array: false,
required: false,
},
'kibana.alert.original_event.hash': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.id': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.ingested': {
type: 'date',
array: false,
required: true,
},
'kibana.alert.original_event.kind': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.module': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.original': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.outcome': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.provider': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.original_event.reason': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.reference': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.risk_score': {
type: 'float',
array: false,
required: false,
},
'kibana.alert.original_event.risk_score_norm': {
type: 'float',
array: false,
required: false,
},
'kibana.alert.original_event.sequence': {
type: 'long',
array: false,
required: true,
},
'kibana.alert.original_event.start': {
type: 'date',
array: false,
required: false,
},
'kibana.alert.original_event.timezone': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_event.type': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.original_event.url': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.original_time': {
type: 'date',
array: false,
required: true,
},
'kibana.alert.threat': {
type: 'object',
array: false,
required: false,
},
'kibana.alert.threat.framework': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.tactic': {
type: 'object',
array: false,
required: true,
},
'kibana.alert.threat.tactic.id': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.tactic.name': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.tactic.reference': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique': {
type: 'object',
array: false,
required: true,
},
'kibana.alert.threat.technique.id': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique.name': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique.reference': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique.subtechnique': {
type: 'object',
array: false,
required: true,
},
'kibana.alert.threat.technique.subtechnique.id': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique.subtechnique.name': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threat.technique.subtechnique.reference': {
type: 'keyword',
array: false,
required: true,
},
'kibana.alert.threshold_result': {
type: 'object',
array: false,
required: false,
},
'kibana.alert.threshold_result.cardinality': {
type: 'object',
array: false,
required: false,
},
'kibana.alert.threshold_result.cardinality.field': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.threshold_result.cardinality.value': {
type: 'long',
array: false,
required: false,
},
'kibana.alert.threshold_result.count': {
type: 'long',
array: false,
required: false,
},
'kibana.alert.threshold_result.from': {
type: 'date',
array: false,
required: false,
},
'kibana.alert.threshold_result.terms': {
type: 'object',
array: false,
required: false,
},
'kibana.alert.threshold_result.terms.field': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.threshold_result.terms.value': {
type: 'keyword',
array: false,
required: false,
},
} as const;
export type AlertsFieldMap = typeof alertsFieldMap;

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AlertsFieldMap, alertsFieldMap } from './alerts';
import { RulesFieldMap, rulesFieldMap } from './rules';
export { AlertsFieldMap, RulesFieldMap, alertsFieldMap, rulesFieldMap };

View file

@ -0,0 +1,136 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const rulesFieldMap = {
'kibana.alert.rule.building_block_type': {
type: 'keyword',
array: false,
required: false,
},
'kibana.alert.rule.false_positives': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.rule.immutable': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.index': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.rule.language': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.rule.max_signals': {
type: 'long',
array: true,
required: true,
},
'kibana.alert.rule.query': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.rule.saved_id': {
type: 'keyword',
array: true,
required: true,
},
'kibana.alert.rule.threat_filters': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_index': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_indicator_path': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_language': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_mapping': {
type: 'object',
array: true,
required: false,
},
'kibana.alert.rule.threat_mapping.field': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_mapping.value': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_mapping.type': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threat_query': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threshold': {
type: 'object',
array: true,
required: false,
},
'kibana.alert.rule.threshold.field': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threshold.value': {
type: 'float', // TODO: should be 'long' (eventually, after we stabilize)
array: true,
required: false,
},
'kibana.alert.rule.threshold.cardinality': {
type: 'object',
array: true,
required: false,
},
'kibana.alert.rule.threshold.cardinality.field': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.threshold.cardinality.value': {
type: 'long',
array: true,
required: false,
},
'kibana.alert.rule.timeline_id': {
type: 'keyword',
array: true,
required: false,
},
'kibana.alert.rule.timeline_title': {
type: 'keyword',
array: true,
required: false,
},
} as const;
export type RulesFieldMap = typeof rulesFieldMap;

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { createQueryAlertType } from './query/create_query_alert_type';

View file

@ -10,21 +10,47 @@ import { v4 } from 'uuid';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { sampleDocNoSortId } from '../signals/__mocks__/es_results';
import { allowedExperimentalValues } from '../../../../../common/experimental_features';
import { sampleDocNoSortId } from '../../signals/__mocks__/es_results';
import { createQueryAlertType } from './create_query_alert_type';
import { createRuleTypeMocks } from '../__mocks__/rule_type';
import { createQueryAlertType } from './query';
import { createRuleTypeMocks } from './__mocks__/rule_type';
jest.mock('../utils/get_list_client', () => ({
getListClient: jest.fn().mockReturnValue({
listClient: jest.fn(),
exceptionsClient: jest.fn(),
}),
}));
jest.mock('../../signals/rule_status_service', () => ({
ruleStatusServiceFactory: () => ({
goingToRun: jest.fn(),
success: jest.fn(),
partialFailure: jest.fn(),
error: jest.fn(),
}),
}));
describe('Custom query alerts', () => {
it('does not send an alert when no events found', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const queryAlertType = createQueryAlertType(dependencies.ruleDataClient, dependencies.logger);
const queryAlertType = createQueryAlertType({
experimentalFeatures: allowedExperimentalValues,
indexAlias: 'alerts.security-alerts',
lists: dependencies.lists,
logger: dependencies.logger,
mergeStrategy: 'allFields',
ruleDataClient: dependencies.ruleDataClient,
version: '1.0.0',
});
dependencies.alerting.registerType(queryAlertType);
const params = {
customQuery: 'dne:42',
indexPatterns: ['*'],
query: 'dne:42',
index: ['*'],
from: 'now-1m',
to: 'now',
};
services.scopedClusterClient.asCurrentUser.search.mockReturnValue(
@ -50,18 +76,28 @@ describe('Custom query alerts', () => {
);
await executor({ params });
expect(services.alertInstanceFactory).not.toBeCalled();
expect(dependencies.ruleDataClient.getWriter).not.toBeCalled();
});
it('sends a properly formatted alert when events are found', async () => {
const { services, dependencies, executor } = createRuleTypeMocks();
const queryAlertType = createQueryAlertType(dependencies.ruleDataClient, dependencies.logger);
const queryAlertType = createQueryAlertType({
experimentalFeatures: allowedExperimentalValues,
indexAlias: 'alerts.security-alerts',
lists: dependencies.lists,
logger: dependencies.logger,
mergeStrategy: 'allFields',
ruleDataClient: dependencies.ruleDataClient,
version: '1.0.0',
});
dependencies.alerting.registerType(queryAlertType);
const params = {
customQuery: '*:*',
indexPatterns: ['*'],
query: '*:*',
index: ['*'],
from: 'now-1m',
to: 'now',
};
services.scopedClusterClient.asCurrentUser.search.mockReturnValue(
@ -85,15 +121,6 @@ describe('Custom query alerts', () => {
);
await executor({ params });
expect(services.alertInstanceFactory).toBeCalled();
/*
expect(services.alertWithPersistence).toBeCalledWith(
expect.arrayContaining([
expect.objectContaining({
'event.kind': 'signal',
}),
])
);
*/
expect(dependencies.ruleDataClient.getWriter).toBeCalled();
});
});

View file

@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Logger } from '@kbn/logging';
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
import { PersistenceServices, RuleDataClient } from '../../../../../../rule_registry/server';
import { QUERY_ALERT_TYPE_ID } from '../../../../../common/constants';
import { ExperimentalFeatures } from '../../../../../common/experimental_features';
import { ConfigType } from '../../../../config';
import { SetupPlugins } from '../../../../plugin';
import { queryRuleParams, QueryRuleParams } from '../../schemas/rule_schemas';
import { queryExecutor } from '../../signals/executors/query';
import { createSecurityRuleTypeFactory } from '../create_security_rule_type_factory';
export const createQueryAlertType = (createOptions: {
experimentalFeatures: ExperimentalFeatures;
indexAlias: string;
lists: SetupPlugins['lists'];
logger: Logger;
mergeStrategy: ConfigType['alertMergeStrategy'];
ruleDataClient: RuleDataClient;
version: string;
}) => {
const {
experimentalFeatures,
indexAlias,
lists,
logger,
mergeStrategy,
ruleDataClient,
version,
} = createOptions;
const createSecurityRuleType = createSecurityRuleTypeFactory({
indexAlias,
lists,
logger,
mergeStrategy,
ruleDataClient,
});
return createSecurityRuleType<QueryRuleParams, {}, PersistenceServices, {}>({
id: QUERY_ALERT_TYPE_ID,
name: 'Custom Query Rule',
validate: {
params: {
validate: (object: unknown) => {
const [validated, errors] = validateNonExact(object, queryRuleParams);
if (errors != null) {
throw new Error(errors);
}
if (validated == null) {
throw new Error('Validation of rule params failed');
}
return validated;
},
},
},
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
actionVariables: {
context: [{ name: 'server', description: 'the server' }],
},
minimumLicenseRequired: 'basic',
isExportable: false,
producer: 'security-solution',
async executor(execOptions) {
const {
runOpts: {
buildRuleMessage,
bulkCreate,
exceptionItems,
listClient,
rule,
searchAfterSize,
tuple,
wrapHits,
},
services,
state,
} = execOptions;
const result = await queryExecutor({
buildRuleMessage,
bulkCreate,
exceptionItems,
experimentalFeatures,
eventsTelemetry: undefined,
listClient,
logger,
rule,
searchAfterSize,
services,
tuple,
version,
wrapHits,
});
return { ...result, state };
},
});
};

View file

@ -14,11 +14,30 @@ curl -X POST ${KIBANA_URL}${SPACE_URL}/api/alerts/alert \
-d '
{
"params":{
"indexPatterns": ["*"],
"customQuery": "*:*"
"author": [],
"description": "Basic custom query rule",
"exceptionsList": [],
"falsePositives": [],
"from": "now-300s",
"query": "*:*",
"immutable": false,
"index": ["*"],
"language": "kuery",
"maxSignals": 10,
"outputIndex": "",
"references": [],
"riskScore": 21,
"riskScoreMapping": [],
"ruleId": "81dec1ba-b779-469c-9667-6b0e865fb86b",
"severity": "low",
"severityMapping": [],
"threat": [],
"to": "now",
"type": "query",
"version": 1
},
"consumer":"alerts",
"alertTypeId":"siem.customRule",
"alertTypeId":"siem.queryRule",
"schedule":{
"interval":"1m"
},

View file

@ -0,0 +1,118 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SearchHit } from '@elastic/elasticsearch/api/types';
import { Logger } from '@kbn/logging';
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { Moment } from 'moment';
import { SavedObject } from '../../../../../../../src/core/server';
import {
AlertInstanceContext,
AlertInstanceState,
AlertTypeParams,
AlertTypeState,
} from '../../../../../alerting/common';
import { AlertType } from '../../../../../alerting/server';
import { ListClient } from '../../../../../lists/server';
import { TechnicalRuleFieldMap } from '../../../../../rule_registry/common/assets/field_maps/technical_rule_field_map';
import { TypeOfFieldMap } from '../../../../../rule_registry/common/field_map';
import {
AlertTypeWithExecutor,
PersistenceServices,
RuleDataClient,
} from '../../../../../rule_registry/server';
import { BaseHit } from '../../../../common/detection_engine/types';
import { ConfigType } from '../../../config';
import { SetupPlugins } from '../../../plugin';
import { RuleParams } from '../schemas/rule_schemas';
import { BuildRuleMessage } from '../signals/rule_messages';
import { AlertAttributes, BulkCreate, WrapHits } from '../signals/types';
import { AlertsFieldMap, RulesFieldMap } from './field_maps';
export interface SecurityAlertTypeReturnValue<TState extends AlertTypeState> {
bulkCreateTimes: string[];
createdSignalsCount: number;
createdSignals: unknown[];
errors: string[];
lastLookbackDate?: Date | null;
searchAfterTimes: string[];
state: TState;
success: boolean;
warning: boolean;
warningMessages: string[];
}
type SimpleAlertType<
TState extends AlertTypeState,
TParams extends AlertTypeParams = {},
TAlertInstanceContext extends AlertInstanceContext = {}
> = AlertType<TParams, TParams, TState, AlertInstanceState, TAlertInstanceContext, string, string>;
export interface RunOpts<TParams extends RuleParams> {
buildRuleMessage: BuildRuleMessage;
bulkCreate: BulkCreate;
exceptionItems: ExceptionListItemSchema[];
listClient: ListClient;
rule: SavedObject<AlertAttributes<TParams>>;
searchAfterSize: number;
tuple: {
to: Moment;
from: Moment;
maxSignals: number;
};
wrapHits: WrapHits;
}
export type SecurityAlertTypeExecutor<
TState extends AlertTypeState,
TServices extends PersistenceServices<TAlertInstanceContext>,
TParams extends RuleParams,
TAlertInstanceContext extends AlertInstanceContext = {}
> = (
options: Parameters<SimpleAlertType<TState, TParams, TAlertInstanceContext>['executor']>[0] & {
runOpts: RunOpts<TParams>;
} & { services: TServices }
) => Promise<SecurityAlertTypeReturnValue<TState>>;
type SecurityAlertTypeWithExecutor<
TState extends AlertTypeState,
TServices extends PersistenceServices<TAlertInstanceContext>,
TParams extends RuleParams,
TAlertInstanceContext extends AlertInstanceContext = {}
> = Omit<
AlertType<TParams, TParams, TState, AlertInstanceState, TAlertInstanceContext, string, string>,
'executor'
> & {
executor: SecurityAlertTypeExecutor<TState, TServices, TParams, TAlertInstanceContext>;
};
export type CreateSecurityRuleTypeFactory = (options: {
indexAlias: string;
lists: SetupPlugins['lists'];
logger: Logger;
mergeStrategy: ConfigType['alertMergeStrategy'];
ruleDataClient: RuleDataClient;
}) => <
TParams extends RuleParams & { index: string[] | undefined },
TAlertInstanceContext extends AlertInstanceContext,
TServices extends PersistenceServices<TAlertInstanceContext>,
TState extends AlertTypeState
>(
type: SecurityAlertTypeWithExecutor<TState, TServices, TParams, TAlertInstanceContext>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => AlertTypeWithExecutor<TState, TParams, TAlertInstanceContext, any>;
export type RACAlertSignal = TypeOfFieldMap<AlertsFieldMap> & TypeOfFieldMap<RulesFieldMap>;
export type RACAlert = Exclude<
TypeOfFieldMap<TechnicalRuleFieldMap> & RACAlertSignal,
'@timestamp'
> & {
'@timestamp': string;
};
export type RACSourceHit = SearchHit<RACAlert>;
export type WrappedRACAlert = BaseHit<RACAlert>;

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
import { ExceptionListClient, ListClient, ListPluginSetup } from '../../../../../../lists/server';
export const getListClient = ({
lists,
spaceId,
updatedByUser,
esClient,
savedObjectClient,
}: {
lists: ListPluginSetup | undefined;
spaceId: string;
updatedByUser: string | null;
esClient: ElasticsearchClient;
savedObjectClient: SavedObjectsClientContract;
}): {
listClient: ListClient;
exceptionsClient: ExceptionListClient;
} => {
if (lists == null) {
throw new Error('lists plugin unavailable during rule execution');
}
const listClient = lists.getListClient(esClient, spaceId, updatedByUser ?? 'elastic');
const exceptionsClient = lists.getExceptionListClient(
savedObjectClient,
updatedByUser ?? 'elastic'
);
return { listClient, exceptionsClient };
};

View file

@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AlertTypeState } from '../../../../../../alerting/server';
import { SecurityAlertTypeReturnValue } from '../types';
export const createResultObject = <TState extends AlertTypeState>(state: TState) => {
const result: SecurityAlertTypeReturnValue<TState> = {
bulkCreateTimes: [],
createdSignalsCount: 0,
createdSignals: [],
errors: [],
lastLookbackDate: undefined,
searchAfterTimes: [],
state,
success: true,
warning: false,
warningMessages: [],
};
return result;
};

View file

@ -7,6 +7,7 @@
import { performance } from 'perf_hooks';
import { countBy, isEmpty, get } from 'lodash';
import { ElasticsearchClient, Logger } from 'kibana/server';
import { BuildRuleMessage } from './rule_messages';
import { RefreshTypes } from '../types';

View file

@ -59,6 +59,7 @@ export const queryExecutor = async ({
version,
index: ruleParams.index,
});
const esFilter = await getFilter({
type: ruleParams.type,
filters: ruleParams.filters,

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { SimpleHit, WrappedSignalHit } from './types';
import { WrappedRACAlert } from '../rule_types/types';
import { Ancestor, SimpleHit, WrappedSignalHit } from './types';
const isWrappedSignalHit = (
signals: SimpleHit[],
@ -14,6 +15,13 @@ const isWrappedSignalHit = (
return !isRuleRegistryEnabled;
};
const isWrappedRACAlert = (
signals: SimpleHit[],
isRuleRegistryEnabled: boolean
): signals is WrappedRACAlert[] => {
return isRuleRegistryEnabled;
};
export const filterDuplicateSignals = (
ruleId: string,
signals: SimpleHit[],
@ -23,8 +31,14 @@ export const filterDuplicateSignals = (
return signals.filter(
(doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId)
);
} else if (isWrappedRACAlert(signals, isRuleRegistryEnabled)) {
return signals.filter(
(doc) =>
!(doc._source['kibana.alert.ancestors'] as Ancestor[]).some(
(ancestor) => ancestor.rule === ruleId
)
);
} else {
// TODO: filter duplicate signals for RAC
return [];
return signals;
}
};

View file

@ -155,6 +155,7 @@ export const searchAfterAndBulkCreate = async ({
success: bulkSuccess,
errors: bulkErrors,
} = await bulkCreate(wrappedDocs);
toReturn = mergeReturns([
toReturn,
createSearchAfterReturnType({

View file

@ -401,12 +401,8 @@ export const signalRulesAlertType = ({
logger.info(
buildRuleMessage(
`[+] Finished indexing ${result.createdSignalsCount} ${
!isEmpty(result.totalToFromTuples)
? `signals searched between date ranges ${JSON.stringify(
result.totalToFromTuples,
null,
2
)}`
!isEmpty(tuples)
? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}`
: ''
}`
)

View file

@ -7,7 +7,7 @@
import type { estypes } from '@elastic/elasticsearch';
import { DslQuery, Filter } from '@kbn/es-query';
import moment, { Moment } from 'moment';
import moment from 'moment';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema';
@ -111,6 +111,8 @@ export interface SignalSource {
};
rule: {
id: string;
false_positives?: string[];
immutable?: boolean;
};
/** signal.depth was introduced in 7.10 and pre-7.10 signals do not have it. */
depth?: number;
@ -275,7 +277,7 @@ export type BulkCreate = <T>(docs: Array<BaseHit<T>>) => Promise<GenericBulkCrea
export type SimpleHit = BaseHit<{ '@timestamp': string }>;
export type WrapHits = (hits: Array<estypes.SearchHit<SignalSource>>) => SimpleHit[];
export type WrapHits = (hits: estypes.SearchHit[]) => SimpleHit[];
export type WrapSequences = (sequences: Array<EqlSequence<SignalSource>>) => SimpleHit[];
@ -314,11 +316,6 @@ export interface SearchAfterAndBulkCreateReturnType {
createdSignals: unknown[];
errors: string[];
warningMessages: string[];
totalToFromTuples?: Array<{
to: Moment | undefined;
from: Moment | undefined;
maxSignals: number;
}>;
}
export interface ThresholdAggregationBucket extends TermAggregationBucket {

View file

@ -5,7 +5,12 @@
* 2.0.
*/
import { SearchAfterAndBulkCreateParams, WrapHits, WrappedSignalHit } from './types';
import {
SearchAfterAndBulkCreateParams,
SignalSourceHit,
WrapHits,
WrappedSignalHit,
} from './types';
import { generateId } from './utils';
import { buildBulkBody } from './build_bulk_body';
import { filterDuplicateSignals } from './filter_duplicate_signals';
@ -29,7 +34,7 @@ export const wrapHitsFactory = ({
String(doc._version),
ruleSO.attributes.params.ruleId ?? ''
),
_source: buildBulkBody(ruleSO, doc, mergeStrategy),
_source: buildBulkBody(ruleSO, doc as SignalSourceHit, mergeStrategy),
},
]);

View file

@ -27,7 +27,6 @@ import {
PluginStartContract as AlertPluginStartContract,
} from '../../alerting/server';
import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map';
import { technicalRuleFieldMap } from '../../rule_registry/common/assets/field_maps/technical_rule_field_map';
import { PluginStartContract as CasesPluginStartContract } from '../../cases/server';
import {
@ -48,9 +47,7 @@ import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server';
import { ILicense, LicensingPluginStart } from '../../licensing/server';
import { FleetStartContract } from '../../fleet/server';
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
import { createQueryAlertType } from './lib/detection_engine/reference_rules/query';
import { createEqlAlertType } from './lib/detection_engine/reference_rules/eql';
import { createThresholdAlertType } from './lib/detection_engine/reference_rules/threshold';
import { createQueryAlertType } from './lib/detection_engine/rule_types';
import { initRoutes } from './routes';
import { isAlertExecutor } from './lib/detection_engine/signals/types';
import { signalRulesAlertType } from './lib/detection_engine/signals/signal_rule_alert_type';
@ -66,9 +63,7 @@ import {
SERVER_APP_ID,
SIGNALS_ID,
NOTIFICATIONS_ID,
REFERENCE_RULE_ALERT_TYPE_ID,
REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID,
CUSTOM_ALERT_TYPE_ID,
QUERY_ALERT_TYPE_ID,
DEFAULT_SPACE_ID,
} from '../common/constants';
import { registerEndpointRoutes } from './endpoint/routes/metadata';
@ -92,6 +87,8 @@ import { licenseService } from './lib/license';
import { PolicyWatcher } from './endpoint/lib/policy/license_watch';
import { parseExperimentalConfigValue } from '../common/experimental_features';
import { migrateArtifactsToFleet } from './endpoint/lib/artifacts/migrate_artifacts_to_fleet';
import { alertsFieldMap } from './lib/detection_engine/rule_types/field_maps/alerts';
import { rulesFieldMap } from './lib/detection_engine/rule_types/field_maps/rules';
import { RuleExecutionLogClient } from './lib/detection_engine/rule_execution_log/rule_execution_log_client';
import { getKibanaPrivilegesFeaturePrivileges } from './features';
import { EndpointMetadataService } from './endpoint/services/metadata';
@ -223,7 +220,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
settings: {
number_of_shards: 1,
},
mappings: { dynamic: false, ...mappingFromFieldMap(technicalRuleFieldMap) },
mappings: mappingFromFieldMap({ ...alertsFieldMap, ...rulesFieldMap }, false),
},
},
});
@ -247,21 +244,29 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
this.logger!.error(err);
});
const indexAlias = ruleDataService.getFullAssetName('security.alerts');
ruleDataClient = ruleDataService.getRuleDataClient(
SERVER_APP_ID,
ruleDataService.getFullAssetName('security.alerts'),
indexAlias,
() => initializeRuleDataTemplatesPromise
);
// Register reference rule types via rule-registry
this.setupPlugins.alerting.registerType(createQueryAlertType(ruleDataClient, this.logger));
this.setupPlugins.alerting.registerType(createEqlAlertType(ruleDataClient, this.logger));
// Register rule types via rule-registry
this.setupPlugins.alerting.registerType(
createThresholdAlertType(ruleDataClient, this.logger)
createQueryAlertType({
experimentalFeatures,
indexAlias,
lists: plugins.lists,
logger: this.logger,
mergeStrategy: this.config.alertMergeStrategy,
ruleDataClient,
version: this.context.env.packageInfo.version,
})
);
}
// TO DO We need to get the endpoint routes inside of initRoutes
// TODO We need to get the endpoint routes inside of initRoutes
initRoutes(
router,
config,
@ -277,15 +282,11 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
registerTrustedAppsRoutes(router, endpointContext);
registerActionRoutes(router, endpointContext);
const referenceRuleTypes = [
REFERENCE_RULE_ALERT_TYPE_ID,
REFERENCE_RULE_PERSISTENCE_ALERT_TYPE_ID,
CUSTOM_ALERT_TYPE_ID,
];
const racRuleTypes = [QUERY_ALERT_TYPE_ID];
const ruleTypes = [
SIGNALS_ID,
NOTIFICATIONS_ID,
...(isRuleRegistryEnabled ? referenceRuleTypes : []),
...(isRuleRegistryEnabled ? racRuleTypes : []),
];
plugins.features.registerKibanaFeature(

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { OWNER, RULE_ID, SPACE_IDS } from '@kbn/rule-data-utils/target/technical_field_names';
import { ALERT_OWNER, RULE_ID, SPACE_IDS } from '@kbn/rule-data-utils';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { from } from 'rxjs';
import {
@ -140,7 +140,7 @@ const timelineAlertsSearchStrategy = <T extends TimelineFactoryQueryTypes>({
type: AlertingAuthorizationFilterType.ESDSL,
// Not passing in values, these are the paths for these fields
fieldNames: {
consumer: OWNER,
consumer: ALERT_OWNER,
ruleTypeId: RULE_ID,
spaceIds: SPACE_IDS,
},

View file

@ -8,12 +8,12 @@
import React from 'react';
import moment from 'moment';
import { ALERT_END, ALERT_STATUS } from '@kbn/rule-data-utils';
import { AlertTypeInitializer } from '.';
import { getMonitorRouteFromMonitorId } from './common';
import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
import { DurationAnomalyTranslations } from '../../../common/translations';
import { AlertTypeInitializer } from '.';
import { getMonitorRouteFromMonitorId } from './common';
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
const { defaultActionMessage, description } = DurationAnomalyTranslations;
@ -39,8 +39,7 @@ export const initDurationAnomalyAlertType: AlertTypeInitializer = ({
reason: fields.reason,
link: getMonitorRouteFromMonitorId({
monitorId: fields['monitor.id']!,
dateRangeEnd:
fields['kibana.rac.alert.status'] === 'open' ? 'now' : fields['kibana.rac.alert.end']!,
dateRangeEnd: fields[ALERT_STATUS] === 'open' ? 'now' : fields[ALERT_END]!,
dateRangeStart: moment(new Date(fields['anomaly.start']!)).subtract('5', 'm').toISOString(),
}),
}),

View file

@ -8,15 +8,14 @@
import React from 'react';
import moment from 'moment';
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
import { ValidationResult } from '../../../../triggers_actions_ui/public';
import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
import { MonitorStatusTranslations } from '../../../common/translations';
import { getMonitorRouteFromMonitorId } from './common';
import { ALERT_END, ALERT_START, ALERT_STATUS } from '@kbn/rule-data-utils';
import { AlertTypeInitializer } from '.';
import { getMonitorRouteFromMonitorId } from './common';
import { MonitorStatusTranslations } from '../../../common/translations';
import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
import { ObservabilityRuleTypeModel } from '../../../../observability/public';
import { ValidationResult } from '../../../../triggers_actions_ui/public';
const { defaultActionMessage, description } = MonitorStatusTranslations;
@ -54,11 +53,8 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({
reason: fields.reason,
link: getMonitorRouteFromMonitorId({
monitorId: fields['monitor.id']!,
dateRangeEnd:
fields['kibana.rac.alert.status'] === 'open' ? 'now' : fields['kibana.rac.alert.end']!,
dateRangeStart: moment(new Date(fields['kibana.rac.alert.start']!))
.subtract('5', 'm')
.toISOString(),
dateRangeEnd: fields[ALERT_STATUS] === 'open' ? 'now' : fields[ALERT_END]!,
dateRangeStart: moment(new Date(fields[ALERT_START]!)).subtract('5', 'm').toISOString(),
filters: {
'observer.geo.name': [fields['observer.geo.name'][0]],
},

View file

@ -149,13 +149,13 @@ describe('status check alert', () => {
const alert = statusCheckAlertFactory(server, libs, plugins);
// @ts-ignore the executor can return `void`, but ours never does
const options = mockOptions();
const state: Record<string, any> = await alert.executor(options);
const state: Record<string, any> | void = await alert.executor(options);
const {
services: { alertWithLifecycle },
} = options;
expect(state).not.toBeUndefined();
expect(state?.isTriggered).toBe(false);
expect(state instanceof Object ? state.isTriggered : true).toBe(false);
expect(alertWithLifecycle).not.toHaveBeenCalled();
expect(mockGetter).toHaveBeenCalledTimes(1);
expect(mockGetter.mock.calls[0][0]).toEqual(

View file

@ -6,8 +6,9 @@
*/
import { UptimeCorePlugins, UptimeCoreSetup } from '../adapters';
import { UMServerLibs } from '../lib';
import { AlertTypeWithExecutor, LifecycleAlertService } from '../../../../rule_registry/server';
import { AlertInstanceContext } from '../../../../alerting/common';
import { AlertTypeWithExecutor } from '../../../../rule_registry/server';
import { AlertInstanceContext, AlertTypeState } from '../../../../alerting/common';
import { LifecycleAlertService } from '../../../../rule_registry/server';
/**
* Because all of our types are presumably going to list the `producer` as `'uptime'`,
@ -16,10 +17,15 @@ import { AlertInstanceContext } from '../../../../alerting/common';
* When we register all the alerts we can inject this field.
*/
export type DefaultUptimeAlertInstance<TActionGroupIds extends string> = AlertTypeWithExecutor<
Record<string, any>,
Record<string, any>,
AlertInstanceContext,
{
alertWithLifecycle: LifecycleAlertService<AlertInstanceContext, TActionGroupIds>;
alertWithLifecycle: LifecycleAlertService<
AlertTypeState,
AlertInstanceContext,
TActionGroupIds
>;
}
>;

View file

@ -50,7 +50,7 @@ export class Plugin implements PluginType {
settings: {
number_of_shards: 1,
},
mappings: mappingFromFieldMap(uptimeRuleFieldMap),
mappings: mappingFromFieldMap(uptimeRuleFieldMap, 'strict'),
},
},
});

View file

@ -6,9 +6,21 @@
*/
import expect from '@kbn/expect';
import {
ALERT_DURATION,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
ALERT_ID,
ALERT_OWNER,
ALERT_PRODUCER,
ALERT_START,
ALERT_STATUS,
ALERT_UUID,
EVENT_KIND,
} from '@kbn/rule-data-utils';
import { merge, omit } from 'lodash';
import { format } from 'url';
import { EVENT_KIND } from '@kbn/rule-data-utils/target/technical_field_names';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { registry } from '../../common/registry';
@ -338,12 +350,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
any
>;
const exclude = [
'@timestamp',
'kibana.rac.alert.start',
'kibana.rac.alert.uuid',
'rule.uuid',
];
const exclude = ['@timestamp', ALERT_START, ALERT_UUID, 'rule.uuid'];
const toCompare = omit(alertEvent, exclude);
@ -355,25 +362,25 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"event.kind": Array [
"signal",
],
"kibana.rac.alert.duration.us": Array [
"${ALERT_DURATION}": Array [
0,
],
"kibana.rac.alert.evaluation.threshold": Array [
"${ALERT_EVALUATION_THRESHOLD}": Array [
30,
],
"kibana.rac.alert.evaluation.value": Array [
"${ALERT_EVALUATION_VALUE}": Array [
50,
],
"kibana.rac.alert.id": Array [
"${ALERT_ID}": Array [
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
],
"kibana.rac.alert.owner": Array [
"${ALERT_OWNER}": Array [
"apm",
],
"kibana.rac.alert.producer": Array [
"${ALERT_PRODUCER}": Array [
"apm",
],
"kibana.rac.alert.status": Array [
"${ALERT_STATUS}": Array [
"open",
],
"kibana.space_ids": Array [
@ -431,25 +438,25 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"event.kind": Array [
"signal",
],
"kibana.rac.alert.duration.us": Array [
"${ALERT_DURATION}": Array [
0,
],
"kibana.rac.alert.evaluation.threshold": Array [
"${ALERT_EVALUATION_THRESHOLD}": Array [
30,
],
"kibana.rac.alert.evaluation.value": Array [
"${ALERT_EVALUATION_VALUE}": Array [
50,
],
"kibana.rac.alert.id": Array [
"${ALERT_ID}": Array [
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
],
"kibana.rac.alert.owner": Array [
"${ALERT_OWNER}": Array [
"apm",
],
"kibana.rac.alert.producer": Array [
"${ALERT_PRODUCER}": Array [
"apm",
],
"kibana.rac.alert.status": Array [
"${ALERT_STATUS}": Array [
"open",
],
"kibana.space_ids": Array [
@ -525,18 +532,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
any
>;
expect(recoveredAlertEvent['kibana.rac.alert.status']?.[0]).to.eql('closed');
expect(recoveredAlertEvent['kibana.rac.alert.duration.us']?.[0]).to.be.greaterThan(0);
expect(
new Date(recoveredAlertEvent['kibana.rac.alert.end']?.[0]).getTime()
).to.be.greaterThan(0);
expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('closed');
expect(recoveredAlertEvent[ALERT_DURATION]?.[0]).to.be.greaterThan(0);
expect(new Date(recoveredAlertEvent[ALERT_END]?.[0]).getTime()).to.be.greaterThan(0);
expectSnapshot(
omit(
recoveredAlertEvent,
exclude.concat(['kibana.rac.alert.duration.us', 'kibana.rac.alert.end'])
)
).toMatchInline(`
expectSnapshot(omit(recoveredAlertEvent, exclude.concat([ALERT_DURATION, ALERT_END])))
.toMatchInline(`
Object {
"event.action": Array [
"close",
@ -544,22 +545,22 @@ export default function ApiTest({ getService }: FtrProviderContext) {
"event.kind": Array [
"signal",
],
"kibana.rac.alert.evaluation.threshold": Array [
"${ALERT_EVALUATION_THRESHOLD}": Array [
30,
],
"kibana.rac.alert.evaluation.value": Array [
"${ALERT_EVALUATION_VALUE}": Array [
50,
],
"kibana.rac.alert.id": Array [
"${ALERT_ID}": Array [
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
],
"kibana.rac.alert.owner": Array [
"${ALERT_OWNER}": Array [
"apm",
],
"kibana.rac.alert.producer": Array [
"${ALERT_PRODUCER}": Array [
"apm",
],
"kibana.rac.alert.status": Array [
"${ALERT_STATUS}": Array [
"closed",
],
"kibana.space_ids": Array [
@ -610,7 +611,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(topAlertsAfterRecovery.length).to.be(1);
expect(topAlertsAfterRecovery[0]['kibana.rac.alert.status']?.[0]).to.be('closed');
expect(topAlertsAfterRecovery[0][ALERT_STATUS]?.[0]).to.be('closed');
});
});
});

View file

@ -8,8 +8,8 @@
"@timestamp": "2020-12-16T15:16:18.570Z",
"rule.id": "apm.error_rate",
"message": "hello world 1",
"kibana.rac.alert.owner": "apm",
"kibana.rac.alert.status": "open",
"kibana.alert.owner": "apm",
"kibana.alert.status": "open",
"kibana.space_ids": ["space1", "space2"]
}
}
@ -25,8 +25,8 @@
"@timestamp": "2020-12-16T15:16:18.570Z",
"rule.id": "apm.error_rate",
"message": "hello world 1",
"kibana.rac.alert.owner": "apm",
"kibana.rac.alert.status": "open",
"kibana.alert.owner": "apm",
"kibana.alert.status": "open",
"kibana.space_ids": ["space1"]
}
}
@ -42,8 +42,8 @@
"@timestamp": "2020-12-16T15:16:18.570Z",
"rule.id": "apm.error_rate",
"message": "hello world 1",
"kibana.rac.alert.owner": "apm",
"kibana.rac.alert.status": "open",
"kibana.alert.owner": "apm",
"kibana.alert.status": "open",
"kibana.space_ids": ["space2"]
}
}
@ -59,8 +59,8 @@
"@timestamp": "2020-12-16T15:16:18.570Z",
"rule.id": "siem.signals",
"message": "hello world security",
"kibana.rac.alert.owner": "siem",
"kibana.rac.alert.status": "open",
"kibana.alert.owner": "siem",
"kibana.alert.status": "open",
"kibana.space_ids": ["space1", "space2"]
}
}
@ -76,8 +76,8 @@
"@timestamp": "2020-12-16T15:16:18.570Z",
"rule.id": "siem.customRule",
"message": "hello world security",
"kibana.rac.alert.owner": "siem",
"kibana.rac.alert.status": "open",
"kibana.alert.owner": "siem",
"kibana.alert.status": "open",
"kibana.space_ids": ["space1", "space2"]
}
}

View file

@ -13,7 +13,7 @@
}
}
},
"kibana.rac.alert.owner": {
"kibana.alert.owner": {
"type": "keyword",
"ignore_above": 256
}
@ -37,7 +37,7 @@
}
}
},
"kibana.rac.alert.owner": {
"kibana.alert.owner": {
"type": "keyword",
"ignore_above": 256
}

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import expect from '@kbn/expect';
import { JsonObject } from '@kbn/common-utils';
import expect from '@kbn/expect';
import { ALERT_ID, ALERT_OWNER } from '@kbn/rule-data-utils';
import { User } from '../../../../rule_registry/common/lib/authentication/types';
import { TimelineEdges, TimelineNonEcsData } from '../../../../../plugins/timelines/common/';
@ -73,23 +74,17 @@ export default ({ getService }: FtrProviderContext) => {
field: '@timestamp',
},
{
field: 'kibana.rac.alert.owner',
field: ALERT_OWNER,
},
{
field: 'kibana.rac.alert.id',
field: ALERT_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: [
'@timestamp',
'message',
'kibana.rac.alert.owner',
'kibana.rac.alert.id',
'event.kind',
],
fieldRequested: ['@timestamp', 'message', ALERT_OWNER, ALERT_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
@ -154,10 +149,7 @@ export default ({ getService }: FtrProviderContext) => {
timeline.edges.every((hit: TimelineEdges) => {
const data: TimelineNonEcsData[] = hit.node.data;
return data.some(({ field, value }) => {
return (
field === 'kibana.rac.alert.owner' &&
featureIds.includes((value && value[0]) ?? '')
);
return field === ALERT_OWNER && featureIds.includes((value && value[0]) ?? '');
});
})
).to.equal(true);

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import expect from '@kbn/expect';
import { JsonObject } from '@kbn/common-utils';
import expect from '@kbn/expect';
import { ALERT_ID, ALERT_OWNER } from '@kbn/rule-data-utils';
import { User } from '../../../../rule_registry/common/lib/authentication/types';
import { TimelineEdges, TimelineNonEcsData } from '../../../../../plugins/timelines/common/';
@ -56,23 +57,17 @@ export default ({ getService }: FtrProviderContext) => {
field: '@timestamp',
},
{
field: 'kibana.rac.alert.owner',
field: ALERT_OWNER,
},
{
field: 'kibana.rac.alert.id',
field: ALERT_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: [
'@timestamp',
'message',
'kibana.rac.alert.owner',
'kibana.rac.alert.id',
'event.kind',
],
fieldRequested: ['@timestamp', 'message', ALERT_OWNER, ALERT_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
@ -136,10 +131,7 @@ export default ({ getService }: FtrProviderContext) => {
timeline.edges.every((hit: TimelineEdges) => {
const data: TimelineNonEcsData[] = hit.node.data;
return data.some(({ field, value }) => {
return (
field === 'kibana.rac.alert.owner' &&
featureIds.includes((value && value[0]) ?? '')
);
return field === ALERT_OWNER && featureIds.includes((value && value[0]) ?? '');
});
})
).to.equal(true);

View file

@ -6,6 +6,7 @@
*/
import { JsonObject } from '@kbn/common-utils';
import { ALERT_ID, ALERT_OWNER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
@ -39,23 +40,17 @@ export default ({ getService }: FtrProviderContext) => {
field: '@timestamp',
},
{
field: 'kibana.rac.alert.owner',
field: ALERT_OWNER,
},
{
field: 'kibana.rac.alert.id',
field: ALERT_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: [
'@timestamp',
'message',
'kibana.rac.alert.owner',
'kibana.rac.alert.id',
'event.kind',
],
fieldRequested: ['@timestamp', 'message', ALERT_OWNER, ALERT_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {

View file

@ -6,6 +6,7 @@
*/
import { JsonObject } from '@kbn/common-utils';
import { ALERT_ID, ALERT_OWNER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
@ -39,23 +40,17 @@ export default ({ getService }: FtrProviderContext) => {
field: '@timestamp',
},
{
field: 'kibana.rac.alert.owner',
field: ALERT_OWNER,
},
{
field: 'kibana.rac.alert.id',
field: ALERT_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: [
'@timestamp',
'message',
'kibana.rac.alert.owner',
'kibana.rac.alert.id',
'event.kind',
],
fieldRequested: ['@timestamp', 'message', ALERT_OWNER, ALERT_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import expect from '@kbn/expect';
import { JsonObject } from '@kbn/common-utils';
import expect from '@kbn/expect';
import { ALERT_ID, ALERT_OWNER } from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../rule_registry/common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../rule_registry/common/lib/authentication/spaces';
@ -34,23 +35,17 @@ export default ({ getService }: FtrProviderContext) => {
field: '@timestamp',
},
{
field: 'kibana.rac.alert.owner',
field: ALERT_OWNER,
},
{
field: 'kibana.rac.alert.id',
field: ALERT_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: [
'@timestamp',
'message',
'kibana.rac.alert.owner',
'kibana.rac.alert.id',
'event.kind',
],
fieldRequested: ['@timestamp', 'message', ALERT_OWNER, ALERT_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {