mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[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 commit87aff9c810
. * Revert "whoops" This reverts commita7771bd392
. * 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:
parent
bcb16c1b86
commit
8f9086b4c2
94 changed files with 2547 additions and 1346 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ export const ecsComponentTemplate: ClusterPutComponentTemplateBody = {
|
|||
},
|
||||
mappings: merge(
|
||||
{},
|
||||
mappingFromFieldMap(ecsFieldMap),
|
||||
mappingFromFieldMap(technicalRuleFieldMap)
|
||||
mappingFromFieldMap(ecsFieldMap, 'strict'),
|
||||
mappingFromFieldMap(technicalRuleFieldMap, 'strict')
|
||||
),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,6 +14,6 @@ export const technicalComponentTemplate: ClusterPutComponentTemplateBody = {
|
|||
settings: {
|
||||
number_of_shards: 1,
|
||||
},
|
||||
mappings: mappingFromFieldMap(technicalRuleFieldMap),
|
||||
mappings: mappingFromFieldMap(technicalRuleFieldMap, 'strict'),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: {},
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {},
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<
|
||||
|
|
|
@ -349,7 +349,7 @@ const createDefaultAlertExecutorOptions = <
|
|||
actions: [],
|
||||
enabled: true,
|
||||
consumer: 'CONSUMER',
|
||||
producer: 'PRODUCER',
|
||||
producer: 'ALERT_PRODUCER',
|
||||
schedule: { interval: '1m' },
|
||||
throttle: null,
|
||||
createdAt,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>;
|
|
@ -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 }
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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} />;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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',
|
||||
}),
|
||||
])
|
||||
);
|
||||
*/
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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"
|
||||
}'
|
||||
|
||||
|
|
@ -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"
|
||||
}'
|
||||
|
||||
|
|
@ -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',
|
||||
}),
|
||||
])
|
||||
);
|
||||
*/
|
||||
});
|
||||
});
|
|
@ -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(),
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
|
@ -28,7 +28,7 @@ export const bootstrapRuleExecutionLog = async (
|
|||
settings: {
|
||||
number_of_shards: 1,
|
||||
},
|
||||
mappings: mappingFromFieldMap(ruleExecutionFieldMap),
|
||||
mappings: mappingFromFieldMap(ruleExecutionFieldMap, 'strict'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
|
@ -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,
|
||||
},
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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(' ');
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -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';
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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(),
|
||||
};
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
|
@ -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 };
|
|
@ -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;
|
|
@ -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';
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 };
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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"
|
||||
},
|
|
@ -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>;
|
|
@ -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 };
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -59,6 +59,7 @@ export const queryExecutor = async ({
|
|||
version,
|
||||
index: ruleParams.index,
|
||||
});
|
||||
|
||||
const esFilter = await getFilter({
|
||||
type: ruleParams.type,
|
||||
filters: ruleParams.filters,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -155,6 +155,7 @@ export const searchAfterAndBulkCreate = async ({
|
|||
success: bulkSuccess,
|
||||
errors: bulkErrors,
|
||||
} = await bulkCreate(wrappedDocs);
|
||||
|
||||
toReturn = mergeReturns([
|
||||
toReturn,
|
||||
createSearchAfterReturnType({
|
||||
|
|
|
@ -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)}`
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -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]],
|
||||
},
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
>;
|
||||
}
|
||||
>;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export class Plugin implements PluginType {
|
|||
settings: {
|
||||
number_of_shards: 1,
|
||||
},
|
||||
mappings: mappingFromFieldMap(uptimeRuleFieldMap),
|
||||
mappings: mappingFromFieldMap(uptimeRuleFieldMap, 'strict'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue