From 6a96906f26e3a765704bb9b4877771b5b0fbcfee Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 18 Sep 2023 17:20:59 -0700 Subject: [PATCH] [Security Solution] Improve rule execution logging (#166070) **Relates to:** https://github.com/elastic/kibana/pull/126063 ## Summary This PR extends rule event log's filter and improves log messages. ## Details We have Rule execution log feature hidden by a feature flag and disabled, it's shown on a rule details page when enabled. image The feature is close to a releasable state but some tasks had to be addressed to make it usable. This PR addresses the following tasks to make rule execution log feature releasable - Adds search bar to search for specific messages image - Adds a date range filter by default set to show logs for last 24 hours image - Improves error, warning and debug messages - Returns rule metrics in a message as a serialized JSON image - Adds `execution_id` to the response image ### Tasks to address later - [ ] Further improve logging messages. We have either error, warning or debug messages. In fact info or trace levels aren't used but it can give useful info. - [ ] Add an OpenAPI spec for the rule execution log endpoint --- .../model/execution_event.mock.ts | 20 +++-- .../rule_monitoring/model/execution_event.ts | 3 +- .../get_rule_execution_events_route.ts | 23 +++-- .../api/__mocks__/api_client.ts | 1 + .../rule_monitoring/api/api_client.test.ts | 55 +++++++++--- .../rule_monitoring/api/api_client.ts | 35 ++++++-- .../api/api_client_interface.ts | 15 ++++ .../event_message_filter.tsx | 35 ++++++++ .../event_message_filter/index.ts | 8 ++ .../event_message_filter/translations.ts | 22 +++++ .../execution_events_table.tsx | 22 ++++- .../use_execution_events.test.tsx | 1 + .../execution_events_table/use_filters.ts | 22 ++++- .../check_metadata_transforms_task.ts | 2 +- .../metadata/endpoint_metadata_service.ts | 2 +- .../handlers/validate_integration_config.ts | 10 +-- .../get_rule_execution_events_route.ts | 3 + .../logic/event_log/event_log_fields.ts | 1 + .../client_for_routes/client_interface.ts | 17 +++- .../event_log/event_log_reader.ts | 89 ++++++++++++++++--- .../create_security_rule_type_wrapper.ts | 23 ++--- .../factories/bulk_create_factory.ts | 10 +-- .../bulk_create_with_suppression.ts | 10 +-- .../create_single_field_match_enrichment.ts | 2 +- .../filter_events_against_list.test.ts | 2 +- .../filter_events_against_list.ts | 4 +- .../utils/search_after_bulk_create.ts | 65 +++++++++----- .../rule_types/utils/send_telemetry_events.ts | 2 +- .../rule_types/utils/single_search_after.ts | 4 +- .../rule_types/utils/utils.test.ts | 4 +- .../rule_types/utils/utils.ts | 8 +- .../lib/risk_engine/utils/create_index.ts | 2 +- .../group1/check_privileges.ts | 4 +- .../group1/create_rules.ts | 2 +- .../group10/get_rule_execution_results.ts | 2 +- 35 files changed, 398 insertions(+), 132 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts index 0cb49804b13a6..c8efaa8dd85b8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.mock.ts @@ -18,8 +18,9 @@ const getMessageEvent = (props: Partial = {}): RuleExecution timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, level: LogLevel.debug, + execution_id: 'execution-id-1', message: 'Some message', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event type: RuleExecutionEventType.message, @@ -31,8 +32,9 @@ const getRunningStatusChange = (props: Partial = {}): RuleEx // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "running"', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.info, @@ -47,8 +49,9 @@ const getPartialFailureStatusChange = ( // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "partial failure". Unknown error', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.warn, @@ -61,8 +64,9 @@ const getFailedStatusChange = (props: Partial = {}): RuleExe // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "failed". Unknown error', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.error, @@ -75,8 +79,9 @@ const getSucceededStatusChange = (props: Partial = {}): Rule // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule executed successfully', - // Overriden values + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.info, @@ -89,8 +94,9 @@ const getExecutionMetricsEvent = (props: Partial = {}): Rule // Default values timestamp: DEFAULT_TIMESTAMP, sequence: DEFAULT_SEQUENCE_NUMBER, - message: '', - // Overriden values + execution_id: 'execution-id-1', + message: JSON.stringify({ some_metric_ms: 10 }), + // Overridden values ...props, // Mandatory values for this type of event level: LogLevel.debug, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts index 3eaa2ca7efad0..64acfb01e2e2a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/model/execution_event.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; -import { enumeration, IsoDateString } from '@kbn/securitysolution-io-ts-types'; +import { enumeration, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import { enumFromString } from '../../../../utils/enum_from_string'; import { TLogLevel } from './log_level'; @@ -56,5 +56,6 @@ export const RuleExecutionEvent = t.type({ sequence: t.number, level: TLogLevel, type: TRuleExecutionEventType, + execution_id: NonEmptyString, message: t.string, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 9f5bd8d273efc..628e71cf51790 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import { defaultCsvArray, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; +import { defaultCsvArray, IsoDateString, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import { DefaultSortOrderDesc, PaginationResult } from '../../../model'; import { RuleExecutionEvent, TRuleExecutionEventType, TLogLevel } from '../../model'; @@ -32,13 +32,20 @@ export type GetRuleExecutionEventsRequestQuery = t.TypeOf< typeof GetRuleExecutionEventsRequestQuery >; export const GetRuleExecutionEventsRequestQuery = t.exact( - t.type({ - event_types: defaultCsvArray(TRuleExecutionEventType), - log_levels: defaultCsvArray(TLogLevel), - sort_order: DefaultSortOrderDesc, // defaults to 'desc' - page: DefaultPage, // defaults to 1 - per_page: DefaultPerPage, // defaults to 20 - }) + t.intersection([ + t.partial({ + search_term: NonEmptyString, + event_types: defaultCsvArray(TRuleExecutionEventType), + log_levels: defaultCsvArray(TLogLevel), + date_start: IsoDateString, + date_end: IsoDateString, + }), + t.type({ + sort_order: DefaultSortOrderDesc, // defaults to 'desc' + page: DefaultPage, // defaults to 1 + per_page: DefaultPerPage, // defaults to 20 + }), + ]) ); /** diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts index 18328e5b9b901..b5b8f201f0ff3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/__mocks__/api_client.ts @@ -32,6 +32,7 @@ export const api: jest.Mocked = { sequence: 0, level: LogLevel.info, type: RuleExecutionEventType['status-change'], + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts index 9fac84959f7dc..d1317e2f74252 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.test.ts @@ -72,14 +72,51 @@ describe('Rule Monitoring API Client', () => { ); }); - it('calls API correctly with filter and pagination options', async () => { + const ISO_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + + it.each([ + [ + 'search term filter', + { searchTerm: 'something to search' }, + { search_term: 'something to search' }, + ], + [ + 'event types filter', + { eventTypes: [RuleExecutionEventType.message] }, + { event_types: 'message' }, + ], + [ + 'log level filter', + { logLevels: [LogLevel.warn, LogLevel.error] }, + { log_levels: 'warn,error' }, + ], + [ + 'start date filter in relative format', + { dateRange: { start: 'now-1d/d' } }, + { date_start: expect.stringMatching(ISO_PATTERN) }, + ], + [ + 'end date filter', + { dateRange: { end: 'now-3d/d' } }, + { date_end: expect.stringMatching(ISO_PATTERN) }, + ], + [ + 'date range filter in relative format', + { dateRange: { start: new Date().toISOString(), end: new Date().toISOString() } }, + { + date_start: expect.stringMatching(ISO_PATTERN), + date_end: expect.stringMatching(ISO_PATTERN), + }, + ], + [ + 'pagination', + { sortOrder: 'asc', page: 42, perPage: 146 } as const, + { sort_order: 'asc', page: 42, per_page: 146 }, + ], + ])('calls API correctly with %s', async (_, params, expectedParams) => { await api.fetchRuleExecutionEvents({ ruleId: '42', - eventTypes: [RuleExecutionEventType.message], - logLevels: [LogLevel.warn, LogLevel.error], - sortOrder: 'asc', - page: 42, - perPage: 146, + ...params, }); expect(fetchMock).toHaveBeenCalledWith( @@ -87,11 +124,7 @@ describe('Rule Monitoring API Client', () => { expect.objectContaining({ method: 'GET', query: { - event_types: 'message', - log_levels: 'warn,error', - sort_order: 'asc', - page: 42, - per_page: 146, + ...expectedParams, }, }) ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts index d5d34447d2bf4..bd1bf80a0f4c3 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { omitBy, isUndefined } from 'lodash'; import dateMath from '@kbn/datemath'; import { KibanaServices } from '../../../common/lib/kibana'; @@ -36,20 +37,38 @@ export const api: IRuleMonitoringApiClient = { fetchRuleExecutionEvents: ( args: FetchRuleExecutionEventsArgs ): Promise => { - const { ruleId, eventTypes, logLevels, sortOrder, page, perPage, signal } = args; + const { + ruleId, + searchTerm, + eventTypes, + logLevels, + dateRange, + sortOrder, + page, + perPage, + signal, + } = args; const url = getRuleExecutionEventsUrl(ruleId); + const startDate = dateMath.parse(dateRange?.start ?? '')?.toISOString(); + const endDate = dateMath.parse(dateRange?.end ?? '', { roundUp: true })?.toISOString(); return http().fetch(url, { method: 'GET', version: '1', - query: { - event_types: eventTypes?.join(','), - log_levels: logLevels?.join(','), - sort_order: sortOrder, - page, - per_page: perPage, - }, + query: omitBy( + { + search_term: searchTerm?.length ? searchTerm : undefined, + event_types: eventTypes?.length ? eventTypes.join(',') : undefined, + log_levels: logLevels?.length ? logLevels.join(',') : undefined, + date_start: startDate, + date_end: endDate, + sort_order: sortOrder, + page, + per_page: perPage, + }, + isUndefined + ), signal, }); }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts index 4ded970a28e4d..a51059a16ef42 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/api/api_client_interface.ts @@ -46,12 +46,22 @@ export interface RuleMonitoringApiCallArgs { signal?: AbortSignal; } +export interface DateRange { + start?: string; + end?: string; +} + export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs { /** * Saved Object ID of the rule (`rule.id`, not static `rule.rule_id`). */ ruleId: string; + /** + * Filter by event message. If set, result will include events matching the search term. + */ + searchTerm?: string; + /** * Filter by event type. If set, result will include only events matching any of these. */ @@ -62,6 +72,11 @@ export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs */ logLevels?: LogLevel[]; + /** + * Filter by date range. If set, result will include only events recorded in the specified date range. + */ + dateRange?: DateRange; + /** * What order to sort by (e.g. `asc` or `desc`). */ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx new file mode 100644 index 0000000000000..b8d45ed5fb1a3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/event_message_filter.tsx @@ -0,0 +1,35 @@ +/* + * 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 { ChangeEvent } from 'react'; +import React, { useCallback } from 'react'; +import { EuiFieldSearch } from '@elastic/eui'; +import * as i18n from './translations'; + +interface EventMessageFilterProps { + value: string; + onChange: (value: string) => void; +} + +export function EventMessageFilter({ value, onChange }: EventMessageFilterProps): JSX.Element { + const handleChange = useCallback( + (e: ChangeEvent) => onChange(e.target.value), + [onChange] + ); + + return ( + + ); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/index.ts new file mode 100644 index 0000000000000..2b07bf7cf86a8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/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 * from './event_message_filter'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts new file mode 100644 index 0000000000000..f82c812d8208c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/event_message_filter/translations.ts @@ -0,0 +1,22 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const SEARCH_BY_EVENT_MESSAGE_ARIA_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.eventLog.searchAriaLabel', + { + defaultMessage: 'Search by event message', + } +); + +export const SEARCH_BY_EVENT_MESSAGE_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.eventLog.searchPlaceholder', + { + defaultMessage: 'Search by event message', + } +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx index 27f500c26dac6..f05636f9aced1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/execution_events_table.tsx @@ -7,7 +7,13 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import type { CriteriaWithPagination } from '@elastic/eui'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { + EuiBasicTable, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSuperDatePicker, +} from '@elastic/eui'; import type { RuleExecutionEvent } from '../../../../../common/api/detection_engine/rule_monitoring'; @@ -22,6 +28,7 @@ import { usePagination } from '../basic/tables/use_pagination'; import { useColumns } from './use_columns'; import { useExpandableRows } from '../basic/tables/use_expandable_rows'; import { useExecutionEvents } from './use_execution_events'; +import { EventMessageFilter } from './event_message_filter'; import * as i18n from './translations'; @@ -56,8 +63,10 @@ const ExecutionEventsTableComponent: React.FC = ({ ru const executionEvents = useExecutionEvents({ ruleId, + searchTerm: filters.state.searchTerm, eventTypes: filters.state.eventTypes, logLevels: filters.state.logLevels, + dateRange: filters.state.dateRange, sortOrder: sorting.state.sort.direction, page: pagination.state.pageNumber, perPage: pagination.state.pageSize, @@ -86,6 +95,9 @@ const ExecutionEventsTableComponent: React.FC = ({ ru + + + @@ -95,6 +107,14 @@ const ExecutionEventsTableComponent: React.FC = ({ ru onChange={filters.setEventTypes} /> + + + {/* Table with items */} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx index b701b3bc48f74..866e0e44b6c77 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_execution_events.test.tsx @@ -87,6 +87,7 @@ describe('useExecutionEvents', () => { sequence: 0, level: LogLevel.info, type: RuleExecutionEventType['status-change'], + execution_id: 'execution-id-1', message: 'Rule changed status to "succeeded". Rule execution completed without errors', }, ], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts index 36fa393467d5e..3079ff19db160 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_monitoring/components/execution_events_table/use_filters.ts @@ -11,15 +11,29 @@ import type { LogLevel, RuleExecutionEventType, } from '../../../../../common/api/detection_engine/rule_monitoring'; +import type { DateRange } from '../../api'; export const useFilters = () => { + const [searchTerm, setSearchTerm] = useState(''); const [logLevels, setLogLevels] = useState([]); const [eventTypes, setEventTypes] = useState([]); - - const state = useMemo(() => ({ logLevels, eventTypes }), [logLevels, eventTypes]); + const [dateRange, setDateRange] = useState({ + start: 'now-24h', + end: 'now', + }); + const state = useMemo( + () => ({ searchTerm, logLevels, eventTypes, dateRange }), + [searchTerm, logLevels, eventTypes, dateRange] + ); return useMemo( - () => ({ state, setLogLevels, setEventTypes }), - [state, setLogLevels, setEventTypes] + () => ({ + state, + setSearchTerm, + setLogLevels, + setEventTypes, + setDateRange, + }), + [state, setSearchTerm, setLogLevels, setEventTypes, setDateRange] ); }; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts index 518780bbbf8a6..ca7a73a272192 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts @@ -196,7 +196,7 @@ export class CheckMetadataTransformsTask { if (attempts > MAX_ATTEMPTS) { this.logger.warn( - `transform ${transform.id} has failed to restart ${attempts} times. stopping auto restart attempts.` + `Transform ${transform.id} has failed to restart ${attempts} times. stopping auto restart attempts.` ); return { attempts, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts index 748e2c1058036..918d29c21f95f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.ts @@ -236,7 +236,7 @@ export class EndpointMetadataService { fleetAgent = await this.getFleetAgent(fleetServices.agent, fleetAgentId); } catch (error) { if (error instanceof FleetAgentNotFoundError) { - this.logger?.warn(`agent with id ${fleetAgentId} not found`); + this.logger?.warn(`Agent with id ${fleetAgentId} not found`); } else { throw error; } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts index 711b46babbc54..d572b15bb4d2d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/validate_integration_config.ts @@ -32,8 +32,8 @@ const validateEndpointIntegrationConfig = ( logger: Logger ): void => { if (!config?.endpointConfig?.preset) { - logger.warn('missing endpointConfig preset'); - throwError('invalid endpointConfig preset'); + logger.warn('Missing endpointConfig preset'); + throwError('Invalid endpointConfig preset'); } if ( ![ @@ -43,8 +43,8 @@ const validateEndpointIntegrationConfig = ( ENDPOINT_CONFIG_PRESET_DATA_COLLECTION, ].includes(config.endpointConfig.preset) ) { - logger.warn(`invalid endpointConfig preset: ${config.endpointConfig.preset}`); - throwError('invalid endpointConfig preset'); + logger.warn(`Invalid endpointConfig preset: ${config.endpointConfig.preset}`); + throwError('Invalid endpointConfig preset'); } }; const validateCloudIntegrationConfig = (config: PolicyCreateCloudConfig, logger: Logger): void => { @@ -56,7 +56,7 @@ const validateCloudIntegrationConfig = (config: PolicyCreateCloudConfig, logger: } if (typeof config.eventFilters?.nonInteractiveSession !== 'boolean') { logger.warn( - `missing or invalid value for eventFilters nonInteractiveSession: ${config.eventFilters?.nonInteractiveSession}` + `Missing or invalid value for eventFilters nonInteractiveSession: ${config.eventFilters?.nonInteractiveSession}` ); throwError('invalid value for eventFilters nonInteractiveSession'); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 10c8cc065b996..1049cbb5c89e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -54,8 +54,11 @@ export const getRuleExecutionEventsRoute = (router: SecuritySolutionPluginRouter const executionLog = ctx.securitySolution.getRuleExecutionLog(); const executionEventsResponse = await executionLog.getExecutionEvents({ ruleId: params.ruleId, + searchTerm: query.search_term, eventTypes: query.event_types, logLevels: query.log_levels, + dateStart: query.date_start, + dateEnd: query.date_end, sortOrder: query.sort_order, page: query.page, perPage: query.per_page, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts index 4e66e93f54507..06e631dece638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/event_log/event_log_fields.ts @@ -10,6 +10,7 @@ export const TIMESTAMP = `@timestamp` as const; +export const MESSAGE = 'message' as const; export const EVENT_PROVIDER = 'event.provider' as const; export const EVENT_ACTION = 'event.action' as const; export const EVENT_SEQUENCE = 'event.sequence' as const; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts index 436ddd91bd595..6f3c867ee3ed4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/client_for_routes/client_interface.ts @@ -39,11 +39,20 @@ export interface GetExecutionEventsArgs { /** Saved object id of the rule (`rule.id`). */ ruleId: RuleObjectId; - /** Include events of the specified types. If empty, all types of events will be included. */ - eventTypes: RuleExecutionEventType[]; + /** Include events of matching the search term. If omitted, all events will be included. */ + searchTerm?: string; - /** Include events having these log levels. If empty, events of all levels will be included. */ - logLevels: LogLevel[]; + /** Include events of the specified types. If omitted, all types of events will be included. */ + eventTypes?: RuleExecutionEventType[]; + + /** Include events having these log levels. If omitted, events of all levels will be included. */ + logLevels?: LogLevel[]; + + /** Include events recorded starting from the specified moment. If omitted, all events will be included. */ + dateStart?: string; + + /** Include events recorded till the specified moment. If omitted, all events will be included. */ + dateEnd?: string; /** What order to sort by (e.g. `asc` or `desc`). */ sortOrder: SortOrder; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts index 03871248ff5be..fae8b6cfe9f5c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/event_log/event_log_reader.ts @@ -9,6 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IEventLogClient, IValidatedEvent } from '@kbn/event-log-plugin/server'; import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules'; +import { prepareKQLStringParam } from '../../../../../../../common/utils/kql'; import type { GetRuleExecutionEventsResponse, GetRuleExecutionResultsResponse, @@ -53,20 +54,30 @@ export const createEventLogReader = (eventLog: IEventLogClient): IEventLogReader async getExecutionEvents( args: GetExecutionEventsArgs ): Promise { - const { ruleId, eventTypes, logLevels, sortOrder, page, perPage } = args; + const { + ruleId, + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, + sortOrder, + page, + perPage, + } = args; const soType = RULE_SAVED_OBJECT_TYPE; const soIds = [ruleId]; - // TODO: include Framework events - const kqlFilter = kqlAnd([ - `${f.EVENT_PROVIDER}:${RULE_EXECUTION_LOG_PROVIDER}`, - eventTypes.length > 0 ? `${f.EVENT_ACTION}:(${kqlOr(eventTypes)})` : '', - logLevels.length > 0 ? `${f.LOG_LEVEL}:(${kqlOr(logLevels)})` : '', - ]); - const findResult = await withSecuritySpan('findEventsBySavedObjectIds', () => { return eventLog.findEventsBySavedObjectIds(soType, soIds, { - filter: kqlFilter, + // TODO: include Framework events + filter: buildEventLogKqlFilter({ + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, + }), sort: [ { sort_field: f.TIMESTAMP, sort_order: sortOrder }, { sort_field: f.EVENT_SEQUENCE, sort_order: sortOrder }, @@ -173,9 +184,10 @@ const normalizeEvent = (rawEvent: IValidatedEvent): RuleExecutionEvent => { const sequence = normalizeEventSequence(rawEvent); const level = normalizeLogLevel(rawEvent); const type = normalizeEventType(rawEvent); + const executionId = normalizeExecutionId(rawEvent); const message = normalizeEventMessage(rawEvent, type); - return { timestamp, sequence, level, type, message }; + return { timestamp, sequence, level, type, message, execution_id: executionId }; }; type RawEvent = NonNullable; @@ -230,9 +242,64 @@ const normalizeEventMessage = (event: RawEvent, type: RuleExecutionEventType): s } if (type === RuleExecutionEventType['execution-metrics']) { - return ''; + invariant( + event.kibana?.alert?.rule?.execution?.metrics, + 'Required "kibana.alert.rule.execution.metrics" field is not found' + ); + + return JSON.stringify(event.kibana.alert.rule.execution.metrics); } assertUnreachable(type); return ''; }; + +const normalizeExecutionId = (event: RawEvent): string => { + invariant( + event.kibana?.alert?.rule?.execution?.uuid, + 'Required "kibana.alert.rule.execution.uuid" field is not found' + ); + + return event.kibana.alert.rule.execution.uuid; +}; + +const buildEventLogKqlFilter = ({ + searchTerm, + eventTypes, + logLevels, + dateStart, + dateEnd, +}: Pick< + GetExecutionEventsArgs, + 'searchTerm' | 'eventTypes' | 'logLevels' | 'dateStart' | 'dateEnd' +>) => { + const filters = [`${f.EVENT_PROVIDER}:${RULE_EXECUTION_LOG_PROVIDER}`]; + + if (searchTerm?.length) { + filters.push(`${f.MESSAGE}:${prepareKQLStringParam(searchTerm)}`); + } + + if (eventTypes?.length) { + filters.push(`${f.EVENT_ACTION}:(${kqlOr(eventTypes)})`); + } + + if (logLevels?.length) { + filters.push(`${f.LOG_LEVEL}:(${kqlOr(logLevels)})`); + } + + const dateRangeFilter: string[] = []; + + if (dateStart) { + dateRangeFilter.push(`${f.TIMESTAMP} >= ${prepareKQLStringParam(dateStart)}`); + } + + if (dateEnd) { + dateRangeFilter.push(`${f.TIMESTAMP} <= ${prepareKQLStringParam(dateEnd)}`); + } + + if (dateRangeFilter.length) { + filters.push(kqlAnd(dateRangeFilter)); + } + + return kqlAnd(filters); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 8480b1de57b0e..c3fb701458238 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -175,8 +175,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const refresh = actions.length ? 'wait_for' : false; - ruleExecutionLogger.debug('[+] Starting Signal Rule execution'); - ruleExecutionLogger.debug(`interval: ${interval}`); + ruleExecutionLogger.debug(`Starting Security Rule execution (interval: ${interval})`); await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus.running, @@ -455,11 +454,15 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const createdSignalsCount = result.createdSignals.length; if (result.success) { - ruleExecutionLogger.debug('[+] Signal Rule execution completed.'); + ruleExecutionLogger.debug('Security Rule execution completed'); ruleExecutionLogger.debug( - `[+] Finished indexing ${createdSignalsCount} signals into ${ruleDataClient.indexNameWithNamespace( + `Finished indexing ${createdSignalsCount} alerts into ${ruleDataClient.indexNameWithNamespace( spaceId - )}` + )} ${ + !isEmpty(tuples) + ? `searched between date ranges ${JSON.stringify(tuples, null, 2)}` + : '' + }` ); if (!hasError && !wroteWarningStatus && !result.warning) { @@ -473,18 +476,10 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = }, }); } - - ruleExecutionLogger.debug( - `[+] Finished indexing ${createdSignalsCount} ${ - !isEmpty(tuples) - ? `signals searched between date ranges ${JSON.stringify(tuples, null, 2)}` - : '' - }` - ); } else { await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus.failed, - message: `Bulk Indexing of signals failed: ${truncateList(result.errors).join()}`, + message: `Bulk Indexing of alerts failed: ${truncateList(result.errors).join()}`, metrics: { searchDurations: result.searchAfterTimes, indexingDurations: result.bulkCreateTimes, 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 index a1d19931032f5..603ff74cf2704 100644 --- 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 @@ -69,7 +69,7 @@ export const bulkCreateFactory = const enrichedAlerts = await enrichAlerts(alerts, params, experimentalFeatures); return enrichedAlerts; } catch (error) { - ruleExecutionLogger.error(`Enrichments failed ${error}`); + ruleExecutionLogger.error(`Alerts enrichment failed: ${error}`); throw error; } finally { enrichmentsTimeFinish = performance.now(); @@ -90,13 +90,11 @@ export const bulkCreateFactory = const end = performance.now(); - ruleExecutionLogger.debug( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ); + ruleExecutionLogger.debug(`Alerts bulk process took ${makeFloatString(end - start)} ms`); if (!isEmpty(errors)) { - ruleExecutionLogger.debug( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errors)}` + ruleExecutionLogger.warn( + `Alerts bulk process finished with errors: ${JSON.stringify(errors)}` ); return { errors: Object.keys(errors), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts index 110f9848c6d83..231387f9c004a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/alert_suppression/bulk_create_with_suppression.ts @@ -74,7 +74,7 @@ export const bulkCreateWithSuppression = async < const enrichedAlerts = await enrichAlerts(alerts, params); return enrichedAlerts; } catch (error) { - ruleExecutionLogger.error(`Enrichments failed ${error}`); + ruleExecutionLogger.error(`Alerts enrichment failed: ${error}`); throw error; } finally { enrichmentsTimeFinish = performance.now(); @@ -94,14 +94,10 @@ export const bulkCreateWithSuppression = async < const end = performance.now(); - ruleExecutionLogger.debug( - `individual bulk process time took: ${makeFloatString(end - start)} milliseconds` - ); + ruleExecutionLogger.debug(`Alerts bulk process took ${makeFloatString(end - start)} ms`); if (!isEmpty(errors)) { - ruleExecutionLogger.debug( - `[-] bulkResponse had errors with responses of: ${JSON.stringify(errors)}` - ); + ruleExecutionLogger.warn(`Alerts bulk process finished with errors: ${JSON.stringify(errors)}`); return { errors: Object.keys(errors), success: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts index 5971c7685a443..982de01b8bae7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/enrichments/create_single_field_match_enrichment.ts @@ -89,7 +89,7 @@ export const createSingleFieldMatchEnrichment: CreateFieldsMatchEnrichment = asy ); return eventsMapById; } catch (error) { - logger.error(`Enrichment ${name}: throw error ${error}`); + logger.error(`Enrichment ${name} failed: ${error}`); return {}; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts index a50b33a0eee34..d5c566383ebba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.test.ts @@ -62,7 +62,7 @@ describe('filterEventsAgainstList', () => { expect(included.length).toEqual(4); expect(excluded.length).toEqual(0); expect(ruleExecutionLogger.debug.mock.calls[0][0]).toContain( - 'no exception items of type list found - returning original search result' + 'No exception items of type list found - return unfiltered events' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts index 1093742d76d6e..8a8f7d34aa3a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/large_list_filters/filter_events_against_list.ts @@ -47,9 +47,7 @@ export const filterEventsAgainstList = async ({ ); if (!atLeastOneLargeValueList) { - ruleExecutionLogger.debug( - 'no exception items of type list found - returning original search result' - ); + ruleExecutionLogger.debug('No exception items of type list found - return unfiltered events'); return [events, []]; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts index c64bb1cc463a2..8e084bd8e35dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create.ts @@ -49,13 +49,18 @@ export const searchAfterAndBulkCreate = async ({ }: SearchAfterAndBulkCreateParams): Promise => { return withSecuritySpan('searchAfterAndBulkCreate', async () => { let toReturn = createSearchAfterReturnType(); + let searchingIteration = 0; // sortId tells us where to start our next consecutive search_after query let sortIds: estypes.SortResults | undefined; let hasSortId = true; // default to true so we execute the search on initial run if (tuple == null || tuple.to == null || tuple.from == null) { - ruleExecutionLogger.error(`[-] malformed date tuple`); + ruleExecutionLogger.error( + `missing run options fields: ${!tuple.to ? '"tuple.to"' : ''}, ${ + !tuple.from ? '"tuple.from"' : '' + }` + ); return createSearchAfterReturnType({ success: false, errors: ['malformed date tuple'], @@ -63,9 +68,14 @@ export const searchAfterAndBulkCreate = async ({ } while (toReturn.createdSignalsCount <= tuple.maxSignals) { + const cycleNum = `cycle ${searchingIteration++}`; try { let mergedSearchResults = createSearchResultReturnType(); - ruleExecutionLogger.debug(`sortIds: ${sortIds}`); + ruleExecutionLogger.debug( + `[${cycleNum}] Searching events${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + } in index pattern "${inputIndexPattern}"` + ); if (hasSortId) { const { searchResult, searchDuration, searchErrors } = await singleSearchAfter({ @@ -97,9 +107,29 @@ export const searchAfterAndBulkCreate = async ({ }), ]); + // determine if there are any candidate signals to be processed + const totalHits = getTotalHitsValue(mergedSearchResults.hits.total); const lastSortIds = getSafeSortIds( searchResult.hits.hits[searchResult.hits.hits.length - 1]?.sort ); + + if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { + ruleExecutionLogger.debug( + `[${cycleNum}] Found 0 events ${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + }` + ); + break; + } else { + ruleExecutionLogger.debug( + `[${cycleNum}] Found ${ + mergedSearchResults.hits.hits.length + } of total ${totalHits} events${ + sortIds ? ` after cursor ${JSON.stringify(sortIds)}` : '' + }, last cursor ${JSON.stringify(lastSortIds)}` + ); + } + if (lastSortIds != null && lastSortIds.length !== 0) { sortIds = lastSortIds; hasSortId = true; @@ -108,22 +138,6 @@ export const searchAfterAndBulkCreate = async ({ } } - // determine if there are any candidate signals to be processed - const totalHits = getTotalHitsValue(mergedSearchResults.hits.total); - ruleExecutionLogger.debug(`totalHits: ${totalHits}`); - ruleExecutionLogger.debug( - `searchResult.hit.hits.length: ${mergedSearchResults.hits.hits.length}` - ); - - if (totalHits === 0 || mergedSearchResults.hits.hits.length === 0) { - ruleExecutionLogger.debug( - `${ - totalHits === 0 ? 'totalHits' : 'searchResult.hits.hits.length' - } was 0, exiting early` - ); - break; - } - // filter out the search results that match with the values found in the list. // the resulting set are signals to be indexed, given they are not duplicates // of signals already present in the signals index. @@ -158,9 +172,9 @@ export const searchAfterAndBulkCreate = async ({ addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); - ruleExecutionLogger.debug(`created ${bulkCreateResult.createdItemsCount} signals`); - ruleExecutionLogger.debug(`signalsCreatedCount: ${toReturn.createdSignalsCount}`); - ruleExecutionLogger.debug(`enrichedEvents.hits.hits: ${enrichedEvents.length}`); + ruleExecutionLogger.debug( + `[${cycleNum}] Created ${bulkCreateResult.createdItemsCount} alerts from ${enrichedEvents.length} events` + ); sendAlertTelemetryEvents( enrichedEvents, @@ -171,11 +185,14 @@ export const searchAfterAndBulkCreate = async ({ } if (!hasSortId) { - ruleExecutionLogger.debug('ran out of sort ids to sort on'); + ruleExecutionLogger.debug(`[${cycleNum}] Unable to fetch last event cursor`); break; } } catch (exc: unknown) { - ruleExecutionLogger.error(`[-] search_after_bulk_create threw an error ${exc}`); + ruleExecutionLogger.error( + 'Unable to extract/process events or create alerts', + JSON.stringify(exc) + ); return mergeReturns([ toReturn, createSearchAfterReturnType({ @@ -185,7 +202,7 @@ export const searchAfterAndBulkCreate = async ({ ]); } } - ruleExecutionLogger.debug(`[+] completed bulk index of ${toReturn.createdSignalsCount}`); + ruleExecutionLogger.debug(`Completed bulk indexing of ${toReturn.createdSignalsCount} alert`); return toReturn; }); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts index a4687fa953dfe..713428ca08557 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/send_telemetry_events.ts @@ -72,6 +72,6 @@ export function sendAlertTelemetryEvents( try { eventsTelemetry.queueTelemetryEvents(selectedEvents); } catch (exc) { - ruleExecutionLogger.error(`[-] queing telemetry events failed ${exc}`); + ruleExecutionLogger.error(`Queuing telemetry events failed: ${exc}`); } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts index ba91454b3aa02..c10403848474f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/single_search_after.ts @@ -104,14 +104,12 @@ export const singleSearchAfter = async < searchErrors, }; } catch (exc) { - ruleExecutionLogger.error(`[-] nextSearchAfter threw an error ${exc}`); + ruleExecutionLogger.error(`Searching events operation failed: ${exc}`); if ( exc.message.includes(`No mapping found for [${primaryTimestamp}] in order to sort on`) || (secondaryTimestamp && exc.message.includes(`No mapping found for [${secondaryTimestamp}] in order to sort on`)) ) { - ruleExecutionLogger.error(`[-] failure reason: ${exc.message}`); - const searchRes: SignalSearchResponse = { took: 0, timed_out: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts index d507977de11c8..0a18cd40b3a61 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.test.ts @@ -734,7 +734,7 @@ describe('utils', () => { expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({ newStatus: RuleExecutionStatus['partial failure'], message: - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled. If you have recently enrolled agents enabled with Endpoint Security through Fleet, this warning should stop once an alert is sent from an agent.', + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled. If you have recently enrolled agents enabled with Endpoint Security through Fleet, this warning should stop once an alert is sent from an agent.', }); }); @@ -768,7 +768,7 @@ describe('utils', () => { expect(ruleExecutionLogger.logStatusChange).toHaveBeenCalledWith({ newStatus: RuleExecutionStatus['partial failure'], message: - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.', + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["logs-endpoint.alerts-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.', }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts index 17d3e0a4876f3..be19c354cbe1a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/utils.ts @@ -91,7 +91,7 @@ export const hasReadIndexPrivileges = async (args: { const indexesString = JSON.stringify(indexesWithNoReadPrivileges); await ruleExecutionLogger.logStatusChange({ newStatus: RuleExecutionStatus['partial failure'], - message: `This rule may not have the required read privileges to the following indices/index patterns: ${indexesString}`, + message: `This rule may not have the required read privileges to the following index patterns: ${indexesString}`, }); return true; } @@ -112,7 +112,7 @@ export const hasTimestampFields = async (args: { const { ruleName } = ruleExecutionLogger.context; if (isEmpty(timestampFieldCapsResponse.body.indices)) { - const errorString = `This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ${JSON.stringify( + const errorString = `This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ${JSON.stringify( inputIndices )} was found. This warning will continue to appear until a matching index is created or this rule is disabled. ${ ruleName === 'Endpoint Security' @@ -432,7 +432,9 @@ export const getRuleRangeTuples = ({ const intervalDuration = parseInterval(interval); if (intervalDuration == null) { ruleExecutionLogger.error( - 'Failed to compute gap between rule runs: could not parse rule interval' + `Failed to compute gap between rule runs: could not parse rule interval "${JSON.stringify( + interval + )}"` ); return { tuples, remainingGap: moment.duration(0) }; } diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts index b7fa301869951..b1aefedc90cef 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/create_index.ts @@ -25,7 +25,7 @@ export const createIndex = async ({ index: options.index, }); if (isIndexExist) { - logger.info('${options.index} already exist'); + logger.info(`${options.index} already exist`); return; } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts index 5fb1f0d87a585..3a016fe68618d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(body?.execution_summary?.last_execution.message).to.eql( - `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` + `This rule may not have the required read privileges to the following index patterns: ["${index[0]}"]` ); await deleteUserAndRole(getService, ROLES.detections_admin); @@ -121,7 +121,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(body?.execution_summary?.last_execution.message).to.eql( - `This rule may not have the required read privileges to the following indices/index patterns: ["${index[0]}"]` + `This rule may not have the required read privileges to the following index patterns: ["${index[0]}"]` ); await deleteUserAndRole(getService, ROLES.detections_admin); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index d8fc2ec1439be..7df1ae742640f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -165,7 +165,7 @@ export default ({ getService }: FtrProviderContext) => { // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe expect(rule?.execution_summary?.last_execution.status).to.eql('partial failure'); expect(rule?.execution_summary?.last_execution.message).to.eql( - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["does-not-exist-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.' + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["does-not-exist-*"] was found. This warning will continue to appear until a matching index is created or this rule is disabled.' ); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts index b7cca9fd005df..acb8043ab3a25 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/get_rule_execution_results.ts @@ -126,7 +126,7 @@ export default ({ getService }: FtrProviderContext) => { expect(response.body.events[0].security_status).to.eql('partial failure'); expect( response.body.events[0].security_message.startsWith( - 'This rule is attempting to query data from Elasticsearch indices listed in the "Index pattern" section of the rule definition, however no index matching: ["no-name-index"] was found.' + 'This rule is attempting to query data from Elasticsearch indices listed in the "Index patterns" section of the rule definition, however no index matching: ["no-name-index"] was found.' ) ).to.eql(true); });