From 3b2769372eb884fdb865cd0e0a3989ca888357cc Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Tue, 3 Aug 2021 12:48:07 -0400 Subject: [PATCH] [RAC][Security Solution] Add base Security Rule Type (#105096) * injects bulkCreate and wrapHits to individual rule executors * WIP create_security_rule_type_factory based on Marshall's work in #d3076ca54526ea0e61a9a99e1c1bce854806977e * removes ruleStatusService from old rule executors, fixes executor unit tests * fixes rebase * Rename reference_rules to rule_types * Fix type errors * Fix type errors in base security rule factory * Additional improvements to types and interfaces * More type alignment * Fix remaining type errors in query rule * Add validation / inject lists plugin * Formatting * Improvements to typing * Static typing on executors * cleanup * Hook up params for query/threshold rules... includes exceptionsList and daterange tuple * Scaffolding for wrapHits and bulkCreate * Add error handling / status reporting * Fixup alert type state * Begin threshold * Begin work on threshold state * Organize rule types * Export base security rule types * Fixup lifecycle static typing * WrapHits / bulk changes * Field mappings (partial) * whoops * Remove redundant params * More flexibile implementation of bulkCreateFactory * Add mappings * Finish query rule * Revert "Remove redundant params" This reverts commit 87aff9c81041e9328ee47ed7ec413750ee722b65. * Revert "whoops" This reverts commit a7771bd3920cb2dd156325cf61a77e48acc84074. * 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 Co-authored-by: Ece Ozalp Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- api_docs/observability.json | 6 +- api_docs/rule_registry.json | 12 +- .../src/technical_field_names.ts | 199 ++++++++--- .../helper/get_alert_annotations.test.tsx | 32 +- .../latency_chart/latency_chart.stories.tsx | 62 ++-- x-pack/plugins/apm/server/plugin.ts | 29 +- .../alerts_flyout/alerts_flyout.stories.tsx | 4 +- .../public/pages/alerts/example_data.ts | 57 +-- x-pack/plugins/rule_registry/README.md | 32 +- .../ecs_component_template.ts | 4 +- .../technical_component_template.ts | 2 +- .../common/assets/field_maps/ecs_field_map.ts | 5 + .../field_maps/technical_rule_field_map.ts | 275 ++++++++++++--- .../common/mapping_from_field_map.ts | 7 +- .../alerts_client/classes/alertsclient.md | 4 +- .../server/alert_data_client/alerts_client.ts | 10 +- .../alert_data_client/tests/get.test.ts | 31 +- .../alert_data_client/tests/update.test.ts | 27 +- .../server/event_log/event_schema/schema.ts | 6 +- .../event_log/log/event_log_resolver.ts | 2 +- .../event_log/log/event_logger_template.ts | 4 +- .../event_log/log/event_query_builder.ts | 2 +- .../log/utils/mapping_from_field_map.ts | 7 +- x-pack/plugins/rule_registry/server/index.ts | 9 +- .../server/routes/get_alert_by_id.test.ts | 25 +- x-pack/plugins/rule_registry/server/types.ts | 29 +- .../utils/create_lifecycle_executor.test.ts | 2 +- .../server/utils/create_lifecycle_executor.ts | 8 +- .../utils/create_lifecycle_rule_type.test.ts | 57 +-- .../create_lifecycle_rule_type_factory.ts | 21 +- .../create_persistence_rule_type_factory.ts | 104 ++---- .../server/utils/get_rule_executor_data.ts | 6 +- .../server/utils/persistence_types.ts | 50 +++ .../utils/with_rule_data_client_factory.ts | 5 +- .../security_solution/common/constants.ts | 2 +- .../alerts_table/default_config.tsx | 29 +- .../examples/observablity_alerts/columns.ts | 6 +- .../render_cell_value.test.tsx | 6 +- .../observablity_alerts/render_cell_value.tsx | 9 +- .../schedule_notification_actions.ts | 2 +- .../reference_rules/eql.test.ts | 90 ----- .../detection_engine/reference_rules/eql.ts | 122 ------- .../detection_engine/reference_rules/query.ts | 89 ----- .../scripts/create_reference_rule_eql.sh | 34 -- .../create_reference_rule_threshold.sh | 37 -- .../reference_rules/threshold.test.ts | 132 ------- .../reference_rules/threshold.ts | 207 ----------- .../rule_execution_log_bootstrapper.ts | 2 +- .../with_rule_execution_log.ts | 11 +- .../__mocks__/eql.ts | 0 .../rule_types/__mocks__/rule.ts | 52 +++ .../__mocks__/rule_type.ts | 47 ++- .../__mocks__/threshold.ts | 0 .../create_security_rule_type_factory.ts | 324 ++++++++++++++++++ .../factories/build_rule_message_factory.ts | 28 ++ .../factories/bulk_create_factory.ts | 105 ++++++ .../rule_types/factories/index.ts | 10 + .../rule_types/factories/utils/build_alert.ts | 147 ++++++++ .../factories/utils/build_bulk_body.ts | 40 +++ .../factories/utils/filter_source.ts | 24 ++ .../rule_types/factories/wrap_hits_factory.ts | 36 ++ .../rule_types/field_maps/alerts.ts | 298 ++++++++++++++++ .../rule_types/field_maps/index.ts | 10 + .../rule_types/field_maps/rules.ts | 136 ++++++++ .../lib/detection_engine/rule_types/index.ts | 8 + .../{reference_rules => rule_types}/ml.ts | 0 .../query/create_query_alert_type.test.ts} | 67 ++-- .../query/create_query_alert_type.ts | 111 ++++++ .../scripts/create_rule_query.sh} | 25 +- .../lib/detection_engine/rule_types/types.ts | 118 +++++++ .../rule_types/utils/get_list_client.ts | 38 ++ .../rule_types/utils/index.ts | 25 ++ .../signals/bulk_create_factory.ts | 1 + .../signals/executors/query.ts | 1 + .../signals/filter_duplicate_signals.ts | 20 +- .../signals/search_after_bulk_create.ts | 1 + .../signals/signal_rule_alert_type.ts | 8 +- .../lib/detection_engine/signals/types.ts | 11 +- .../signals/wrap_hits_factory.ts | 9 +- .../security_solution/server/plugin.ts | 41 +-- .../server/search_strategy/timeline/index.ts | 4 +- .../lib/alert_types/duration_anomaly.tsx | 11 +- .../public/lib/alert_types/monitor_status.tsx | 20 +- .../server/lib/alerts/status_check.test.ts | 4 +- .../plugins/uptime/server/lib/alerts/types.ts | 12 +- x-pack/plugins/uptime/server/plugin.ts | 2 +- .../tests/alerts/rule_registry.ts | 79 ++--- .../rule_registry/alerts/data.json | 20 +- .../rule_registry/alerts/mappings.json | 4 +- .../security_and_spaces/tests/basic/events.ts | 20 +- .../security_and_spaces/tests/trial/events.ts | 20 +- .../security_only/tests/basic/events.ts | 13 +- .../security_only/tests/trial/events.ts | 13 +- .../test/timeline/spaces_only/tests/events.ts | 15 +- 94 files changed, 2546 insertions(+), 1345 deletions(-) create mode 100644 x-pack/plugins/rule_registry/server/utils/persistence_types.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh delete mode 100755 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules => rule_types}/__mocks__/eql.ts (100%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules => rule_types}/__mocks__/rule_type.ts (62%) rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules => rule_types}/__mocks__/threshold.ts (100%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules => rule_types}/ml.ts (100%) rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules/query.test.ts => rule_types/query/create_query_alert_type.test.ts} (55%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts rename x-pack/plugins/security_solution/server/lib/detection_engine/{reference_rules/scripts/create_reference_rule_query.sh => rule_types/scripts/create_rule_query.sh} (55%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_list_client.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts diff --git a/api_docs/observability.json b/api_docs/observability.json index 1daf96c7a551f..2a0528878114b 100644 --- a/api_docs/observability.json +++ b/api_docs/observability.json @@ -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; 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; 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; 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; 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; 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; 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, diff --git a/api_docs/rule_registry.json b/api_docs/rule_registry.json index 24a8be2d2650f..74480af0eb2f8 100644 --- a/api_docs/rule_registry.json +++ b/api_docs/rule_registry.json @@ -525,7 +525,7 @@ "section": "def-server.AlertExecutorOptions", "text": "AlertExecutorOptions" }, - ") => { \"rule.id\": string; \"rule.uuid\": string; \"rule.category\": string; \"rule.name\": string; tags: string[]; \"kibana.rac.alert.producer\": string; }" + ") => { \"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 & Partial>, \"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", ", \"getState\" | \"replaceState\" | \"scheduleActions\" | \"scheduleActionsWithSubGroup\">" ], @@ -758,7 +758,7 @@ "signature": [ "{ id: string; fields: Record & Partial>, \"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, diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 54a6fa26664e3..c55be6cfe8ff6 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -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, + TIMESTAMP, + ALERT_ACTION_GROUP, + ALERT_DURATION, + ALERT_END, + ALERT_EVALUATION_THRESHOLD, + ALERT_EVALUATION_VALUE, ALERT_ID, - ALERT_UUID, + 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_END, - ALERT_DURATION, + 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; diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx index b2d2f360a5fd4..01a8293163106 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx @@ -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', diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index 1439e59877ea4..71d517ad53871 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -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'], }, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index d28e43d9cb976..50dd09a6366e8 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -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' + ), }, }, }); diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx index 8aae408b1f94b..a752b8d89a1bd 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_flyout/alerts_flyout.stories.tsx @@ -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 ( \>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> +▸ `Private` **fetchAlert**(`__namedParameters`): `Promise`\>, ``"kibana.alert.owner"`` \| ``"rule.id"``\> & { `kibana.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Parameters @@ -108,7 +108,7 @@ ___ #### Returns -`Promise`\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> +`Promise`\>, ``"kibana.alert.owner"`` \| ``"rule.id"``\> & { `kibana.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Defined in diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index 598818a7a69c3..91282edf3778a 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -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 = Omit & { [K in Props]-?: NonNullable }; -type AlertType = NonNullableProps; +type AlertType = NonNullableProps; 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, }); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index 518b75637cf34..51789a09234e2 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -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", } diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index e24bae96975df..47a86c718d8de 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -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'], }, }, ], diff --git a/x-pack/plugins/rule_registry/server/event_log/event_schema/schema.ts b/x-pack/plugins/rule_registry/server/event_log/event_schema/schema.ts index 9b5d94918a83f..6ab82e7027efc 100644 --- a/x-pack/plugins/rule_registry/server/event_log/event_schema/schema.ts +++ b/x-pack/plugins/rule_registry/server/event_log/event_schema/schema.ts @@ -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 { + public static getBase(): EventSchema { return baseSchema; } public static extendBase( fields: TMap - ): EventSchema { + ): EventSchema { const extensionSchema = createSchema(fields); return this.combine(baseSchema, extensionSchema); } diff --git a/x-pack/plugins/rule_registry/server/event_log/log/event_log_resolver.ts b/x-pack/plugins/rule_registry/server/event_log/log/event_log_resolver.ts index 8440f55432304..6f1d4aba9f6e5 100644 --- a/x-pack/plugins/rule_registry/server/event_log/log/event_log_resolver.ts +++ b/x-pack/plugins/rule_registry/server/event_log/log/event_log_resolver.ts @@ -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 }; } diff --git a/x-pack/plugins/rule_registry/server/event_log/log/event_logger_template.ts b/x-pack/plugins/rule_registry/server/event_log/log/event_logger_template.ts index 3872a5c744269..57d2dabb47bfa 100644 --- a/x-pack/plugins/rule_registry/server/event_log/log/event_logger_template.ts +++ b/x-pack/plugins/rule_registry/server/event_log/log/event_logger_template.ts @@ -42,8 +42,8 @@ export class EventLoggerTemplate implements IEventLoggerTemplate 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 { diff --git a/x-pack/plugins/rule_registry/server/event_log/log/event_query_builder.ts b/x-pack/plugins/rule_registry/server/event_log/log/event_query_builder.ts index bf9aca74f7800..9fafaa8ed1b76 100644 --- a/x-pack/plugins/rule_registry/server/event_log/log/event_query_builder.ts +++ b/x-pack/plugins/rule_registry/server/event_log/log/event_query_builder.ts @@ -83,7 +83,7 @@ export class EventQueryBuilder implements IEventQueryBuilder { if (this.loggerName) { result.push({ - term: { 'kibana.rac.event_log.logger_name': this.loggerName }, + term: { 'kibana.event_log.logger_name': this.loggerName }, }); } diff --git a/x-pack/plugins/rule_registry/server/event_log/log/utils/mapping_from_field_map.ts b/x-pack/plugins/rule_registry/server/event_log/log/utils/mapping_from_field_map.ts index fd5dc3ae02288..02c759cc328d2 100644 --- a/x-pack/plugins/rule_registry/server/event_log/log/utils/mapping_from_field_map.ts +++ b/x-pack/plugins/rule_registry/server/event_log/log/utils/mapping_from_field_map.ts @@ -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: {}, }; diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts index e6656420af05d..af086abadbb72 100644 --- a/x-pack/plugins/rule_registry/server/index.ts +++ b/x-pack/plugins/rule_registry/server/index.ts @@ -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); diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts index 0de1e6c585a17..d89eb305545e8 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts @@ -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', () => { diff --git a/x-pack/plugins/rule_registry/server/types.ts b/x-pack/plugins/rule_registry/server/types.ts index 4b63acd5b01ab..c524a5412e13d 100644 --- a/x-pack/plugins/rule_registry/server/types.ts +++ b/x-pack/plugins/rule_registry/server/types.ts @@ -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; export type AlertTypeExecutor< + TState extends AlertTypeState, TParams extends AlertTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {}, TServices extends Record = {} > = ( - options: Parameters['executor']>[0] & { + options: Parameters['executor']>[0] & { services: TServices; } -) => Promise; +) => Promise; export type AlertTypeWithExecutor< + TState extends AlertTypeState = {}, TParams extends AlertTypeParams = {}, TAlertInstanceContext extends AlertInstanceContext = {}, TServices extends Record = {} > = Omit< - AlertType< - TParams, - TParams, - AlertTypeState, - AlertInstanceState, - TAlertInstanceContext, - string, - string - >, + AlertType, 'executor' > & { - executor: AlertTypeExecutor; + executor: AlertTypeExecutor; }; export type AlertExecutorOptionsWithExtraServices< diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index a036f42739998..80b75b8c74732 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -349,7 +349,7 @@ const createDefaultAlertExecutorOptions = < actions: [], enabled: true, consumer: 'CONSUMER', - producer: 'PRODUCER', + producer: 'ALERT_PRODUCER', schedule: { interval: '1m' }, throttle: null, createdAt, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 046b5bdddf6d8..50ac8afb945b4 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -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]; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 11bb48a7440a7..c1358da97e95a 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -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; 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'); }); }); }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts index 783077a1f68ab..21e95fbefe4e2 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts @@ -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, - TActionGroupIds extends string = string -> = (alert: { - id: string; - fields: Record; -}) => AlertInstance; +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 } + TServices extends { + alertWithLifecycle: LifecycleAlertService, TAlertInstanceContext, string>; + } >( - type: AlertTypeWithExecutor -): AlertTypeWithExecutor => { + type: AlertTypeWithExecutor, TParams, TAlertInstanceContext, TServices> +): AlertTypeWithExecutor, TParams, TAlertInstanceContext, any> => { const createBoundLifecycleExecutor = createLifecycleExecutor(logger, ruleDataClient); const executor = createBoundLifecycleExecutor< TParams, diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts index 9f4a6ce2e022c..50e5b224f01d8 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_factory.ts @@ -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> = ( - alerts: Array> -) => Array>; - -type PersistenceAlertQueryService = ( - query: ESSearchRequest -) => Promise>>; - -type CreatePersistenceRuleTypeFactory = (options: { - ruleDataClient: RuleDataClient; - logger: Logger; -}) => < - TParams extends AlertTypeParams, - TAlertInstanceContext extends AlertInstanceContext, - TServices extends { - alertWithPersistence: PersistenceAlertService; - findAlerts: PersistenceAlertQueryService; - } ->( - type: AlertTypeWithExecutor -) => AlertTypeWithExecutor; +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> = []; - 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; }, }; diff --git a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts b/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts index 144c0dafa3786..866eb5f882fe0 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_rule_executor_data.ts @@ -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 = ( + alerts: Array<{ + id: string; + fields: Record; + }>, + refresh: boolean | 'wait_for' +) => Promise>; + +export type PersistenceAlertQueryService = ( + query: ESSearchRequest +) => Promise>>; +export interface PersistenceServices { + alertWithPersistence: PersistenceAlertService; +} + +export type CreatePersistenceRuleTypeFactory = (options: { + ruleDataClient: RuleDataClient; + logger: Logger; +}) => < + TState extends AlertTypeState, + TParams extends AlertTypeParams, + TServices extends PersistenceServices, + TAlertInstanceContext extends AlertInstanceContext = {} +>( + type: AlertTypeWithExecutor +) => AlertTypeWithExecutor; diff --git a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts index 02ff6b10f74cf..7943c3ad4f35a 100644 --- a/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts +++ b/x-pack/plugins/rule_registry/server/utils/with_rule_data_client_factory.ts @@ -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 = {} >( type: AlertTypeWithExecutor< + TState, TParams, TAlertInstanceContext, TServices & { ruleDataClient: RuleDataClient } > ): AlertTypeWithExecutor< + TState, TParams, TAlertInstanceContext, TServices & { ruleDataClient: RuleDataClient } diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index ad877ec69945d..47e3b5b3ea364 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -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`; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 9a142f6cba247..7ff6f82d40bdc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -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', }; diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts index 5e1bf4d90fb46..ae9285f85501b 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/columns.ts @@ -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, }, { diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx index d99fecb6bdadf..a4826445b23cf 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.test.tsx @@ -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( - + ); @@ -67,7 +69,7 @@ describe('RenderCellValue', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx index 4eb885d4c9aea..684680ea2e852 100644 --- a/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx +++ b/x-pack/plugins/security_solution/public/detections/configurations/examples/observablity_alerts/render_cell_value.tsx @@ -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 ( ); - case 'kibana.rac.alert.duration.us': + case ALERT_DURATION: return {moment().fromNow(true)}; case 'signal.rule.severity': return ; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts index bfb96e97edf11..147c54cd6eb8a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -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 { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts deleted file mode 100644 index da5c89a3102a1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.test.ts +++ /dev/null @@ -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', - }), - ]) - ); - */ - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts deleted file mode 100644 index b98bd9b3551c3..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/eql.ts +++ /dev/null @@ -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; - - 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(), - }; - }, - }); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts deleted file mode 100644 index 40c1246733056..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.ts +++ /dev/null @@ -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); - } - }, - }); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh deleted file mode 100755 index 25e247a08ef46..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_eql.sh +++ /dev/null @@ -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" -}' - - diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh deleted file mode 100755 index 8b486b165c34b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_threshold.sh +++ /dev/null @@ -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" -}' - - diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts deleted file mode 100644 index 36e53b8154e70..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.test.ts +++ /dev/null @@ -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', - }), - ]) - ); - */ - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts deleted file mode 100644 index fa291ef3139cd..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/threshold.ts +++ /dev/null @@ -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(), - }; - }, - }); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_execution_log_bootstrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_execution_log_bootstrapper.ts index 50a9b45485741..9b13fbb1d21d6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_execution_log_bootstrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/rule_registry_log_client/rule_execution_log_bootstrapper.ts @@ -28,7 +28,7 @@ export const bootstrapRuleExecutionLog = async ( settings: { number_of_shards: 1, }, - mappings: mappingFromFieldMap(ruleExecutionFieldMap), + mappings: mappingFromFieldMap(ruleExecutionFieldMap, 'strict'), }, }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/with_rule_execution_log.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/with_rule_execution_log.ts index 7cd0b2b345438..95598ea943d8f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/with_rule_execution_log.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/with_rule_execution_log.ts @@ -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 -) => AlertTypeWithExecutor; + type: AlertTypeWithExecutor +) => AlertTypeWithExecutor; export const withRuleExecutionLogFactory: WithRuleExecutionLog = ({ logger, ruleDataService }) => ( type diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/eql.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/eql.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts new file mode 100644 index 0000000000000..25e687050cf88 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts @@ -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, +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts similarity index 62% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index 6c0670d1dbb2c..60dcbb3a4e77e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -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); + 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, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/__mocks__/threshold.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts new file mode 100644 index 0000000000000..71d922ed543c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_factory.ts @@ -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, void>( + async (error: Error) => logger.error(buildRuleMessage(error.message)), + async (status: Promise) => (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> + ); + } + } 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; + }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts new file mode 100644 index 0000000000000..09d2cb559fe69 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/build_rule_message_factory.ts @@ -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(' '); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts new file mode 100644 index 0000000000000..c600e187bc8f1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/bulk_create_factory.ts @@ -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 { + success: boolean; + bulkCreateDuration: string; + createdItemsCount: number; + createdItems: Array; + errors: string[]; +} + +export const bulkCreateFactory = ( + logger: Logger, + alertWithPersistence: PersistenceAlertService, + buildRuleMessage: BuildRuleMessage, + refreshForBulkCreate: RefreshTypes +) => async (wrappedDocs: Array>): Promise> => { + 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, + }; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/index.ts new file mode 100644 index 0000000000000..76263745ba7ae --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts new file mode 100644 index 0000000000000..fbd033a7f4ec4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -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, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts new file mode 100644 index 0000000000000..8c868ece26ceb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts @@ -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, + 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(), + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts new file mode 100644 index 0000000000000..2f1ebf545c6c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/filter_source.ts @@ -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 => { + const event = buildEventTypeSignal(doc); + + const docSource = doc._source ?? {}; + const { threshold_result: thresholdResult, ...filteredSource } = docSource || { + threshold_result: null, + }; + + return { + ...filteredSource, + event, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts new file mode 100644 index 0000000000000..620e599e7a499 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -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); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts new file mode 100644 index 0000000000000..244905329f8ca --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts @@ -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; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts new file mode 100644 index 0000000000000..0f19e2b3d3a91 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts new file mode 100644 index 0000000000000..21405672fdf7f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts @@ -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; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts new file mode 100644 index 0000000000000..1b1fe48be00e0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/index.ts @@ -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'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/ml.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts similarity index 55% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index e8c45e9ab7056..0127477e4800a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/query.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -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(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts new file mode 100644 index 0000000000000..3321597d8268f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -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({ + 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 }; + }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_query.sh similarity index 55% rename from x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_query.sh index b1d614e98ccae..aa632afc52095 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/reference_rules/scripts/create_reference_rule_query.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/scripts/create_rule_query.sh @@ -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" }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts new file mode 100644 index 0000000000000..dc4cbba680b25 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -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 { + 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; + +export interface RunOpts { + buildRuleMessage: BuildRuleMessage; + bulkCreate: BulkCreate; + exceptionItems: ExceptionListItemSchema[]; + listClient: ListClient; + rule: SavedObject>; + searchAfterSize: number; + tuple: { + to: Moment; + from: Moment; + maxSignals: number; + }; + wrapHits: WrapHits; +} + +export type SecurityAlertTypeExecutor< + TState extends AlertTypeState, + TServices extends PersistenceServices, + TParams extends RuleParams, + TAlertInstanceContext extends AlertInstanceContext = {} +> = ( + options: Parameters['executor']>[0] & { + runOpts: RunOpts; + } & { services: TServices } +) => Promise>; + +type SecurityAlertTypeWithExecutor< + TState extends AlertTypeState, + TServices extends PersistenceServices, + TParams extends RuleParams, + TAlertInstanceContext extends AlertInstanceContext = {} +> = Omit< + AlertType, + 'executor' +> & { + executor: SecurityAlertTypeExecutor; +}; + +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, + TState extends AlertTypeState +>( + type: SecurityAlertTypeWithExecutor + // eslint-disable-next-line @typescript-eslint/no-explicit-any +) => AlertTypeWithExecutor; + +export type RACAlertSignal = TypeOfFieldMap & TypeOfFieldMap; +export type RACAlert = Exclude< + TypeOfFieldMap & RACAlertSignal, + '@timestamp' +> & { + '@timestamp': string; +}; + +export type RACSourceHit = SearchHit; +export type WrappedRACAlert = BaseHit; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_list_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_list_client.ts new file mode 100644 index 0000000000000..83a473799068a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_list_client.ts @@ -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 }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts new file mode 100644 index 0000000000000..03cdcc6ef5c52 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts @@ -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 = (state: TState) => { + const result: SecurityAlertTypeReturnValue = { + bulkCreateTimes: [], + createdSignalsCount: 0, + createdSignals: [], + errors: [], + lastLookbackDate: undefined, + searchAfterTimes: [], + state, + success: true, + warning: false, + warningMessages: [], + }; + return result; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts index f518ac2386d0b..80d1ebf800191 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_factory.ts @@ -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'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index 454cb464506a9..f27680315d194 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -59,6 +59,7 @@ export const queryExecutor = async ({ version, index: ruleParams.index, }); + const esFilter = await getFilter({ type: ruleParams.type, filters: ruleParams.filters, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts index 0b9859fad7688..fb562a2d11f0a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts @@ -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; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 75cffb598d186..7b5b61577cf32 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -155,6 +155,7 @@ export const searchAfterAndBulkCreate = async ({ success: bulkSuccess, errors: bulkErrors, } = await bulkCreate(wrappedDocs); + toReturn = mergeReturns([ toReturn, createSearchAfterReturnType({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index d524757b7c144..d1cb7194f86ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -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)}` : '' }` ) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index dbc6848335893..4ad734c3bf7d9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -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 = (docs: Array>) => Promise; -export type WrapHits = (hits: Array>) => SimpleHit[]; +export type WrapHits = (hits: estypes.SearchHit[]) => SimpleHit[]; export type WrapSequences = (sequences: Array>) => 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 { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts index b28c46aae8f82..5cef740e17895 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts @@ -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), }, ]); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index dd6081b6c9127..96171154f6007 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -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 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({ 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, }, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx index f14c1a4a9fdde..68036ea047866 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx @@ -8,12 +8,12 @@ import React from 'react'; import moment from 'moment'; -import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; -import { DurationAnomalyTranslations } from '../../../common/translations'; -import { AlertTypeInitializer } from '.'; +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 { 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(), }), }), diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index a87ba4aedbb2a..7bd50fce52f3a 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -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]], }, diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts index dbb199a2e07d8..13c0c48eb28f8 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.test.ts @@ -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 = await alert.executor(options); + const state: Record | 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( diff --git a/x-pack/plugins/uptime/server/lib/alerts/types.ts b/x-pack/plugins/uptime/server/lib/alerts/types.ts index 28f9eba7ab381..f4ac2f354d814 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/types.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/types.ts @@ -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 = AlertTypeWithExecutor< + Record, Record, AlertInstanceContext, { - alertWithLifecycle: LifecycleAlertService; + alertWithLifecycle: LifecycleAlertService< + AlertTypeState, + AlertInstanceContext, + TActionGroupIds + >; } >; diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts index 5ef5e17d4e33a..d4bc9ca1b4d5d 100644 --- a/x-pack/plugins/uptime/server/plugin.ts +++ b/x-pack/plugins/uptime/server/plugin.ts @@ -50,7 +50,7 @@ export class Plugin implements PluginType { settings: { number_of_shards: 1, }, - mappings: mappingFromFieldMap(uptimeRuleFieldMap), + mappings: mappingFromFieldMap(uptimeRuleFieldMap, 'strict'), }, }, }); diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index 0677acd500c1f..8072064b2b1bf 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -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'); }); }); }); diff --git a/x-pack/test/functional/es_archives/rule_registry/alerts/data.json b/x-pack/test/functional/es_archives/rule_registry/alerts/data.json index 6d7f78292ddac..c8c01883197f5 100644 --- a/x-pack/test/functional/es_archives/rule_registry/alerts/data.json +++ b/x-pack/test/functional/es_archives/rule_registry/alerts/data.json @@ -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"] } } diff --git a/x-pack/test/functional/es_archives/rule_registry/alerts/mappings.json b/x-pack/test/functional/es_archives/rule_registry/alerts/mappings.json index 4cb178d979982..74d50ca402e45 100644 --- a/x-pack/test/functional/es_archives/rule_registry/alerts/mappings.json +++ b/x-pack/test/functional/es_archives/rule_registry/alerts/mappings.json @@ -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 } diff --git a/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts b/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts index f4b7efc096e33..79f5768b9a3ba 100644 --- a/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts +++ b/x-pack/test/timeline/security_and_spaces/tests/basic/events.ts @@ -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); diff --git a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts index 075c413e1f2dc..2ebb534c4a451 100644 --- a/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts +++ b/x-pack/test/timeline/security_and_spaces/tests/trial/events.ts @@ -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); diff --git a/x-pack/test/timeline/security_only/tests/basic/events.ts b/x-pack/test/timeline/security_only/tests/basic/events.ts index 0f7ddc0c05825..fc8ee95dedfc4 100644 --- a/x-pack/test/timeline/security_only/tests/basic/events.ts +++ b/x-pack/test/timeline/security_only/tests/basic/events.ts @@ -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: { diff --git a/x-pack/test/timeline/security_only/tests/trial/events.ts b/x-pack/test/timeline/security_only/tests/trial/events.ts index 0f7ddc0c05825..fc8ee95dedfc4 100644 --- a/x-pack/test/timeline/security_only/tests/trial/events.ts +++ b/x-pack/test/timeline/security_only/tests/trial/events.ts @@ -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: { diff --git a/x-pack/test/timeline/spaces_only/tests/events.ts b/x-pack/test/timeline/spaces_only/tests/events.ts index 71ea077406462..829d46905b6d1 100644 --- a/x-pack/test/timeline/spaces_only/tests/events.ts +++ b/x-pack/test/timeline/spaces_only/tests/events.ts @@ -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: {