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 0cb49804b13a695..c8efaa8dd85b8c3 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 3eaa2ca7efad085..64acfb01e2e2a59 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 9f5bd8d273efca6..628e71cf51790bb 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 18328e5b9b90180..b5b8f201f0ff3c6 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 9fac84959f7dcec..d1317e2f7425201 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 d5d34447d2bf4b0..bd1bf80a0f4c32c 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 4ded970a28e4d1f..a51059a16ef4299 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 000000000000000..b8d45ed5fb1a339 --- /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 000000000000000..2b07bf7cf86a8c2 --- /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 000000000000000..f82c812d8208c10 --- /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 27f500c26dac68d..f05636f9aced131 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 b701b3bc48f74b4..866e0e44b6c7789 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 36fa393467d5e82..3079ff19db16051 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 518780bbbf8a66a..ca7a73a2721924a 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 748e2c105803671..918d29c21f95fcf 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 711b46babbc54f0..d572b15bb4d2df9 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 10c8cc065b99656..1049cbb5c89e1ac 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 4e66e93f5450738..06e631dece638ae 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 436ddd91bd59509..6f3c867ee3ed47f 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 03871248ff5be9a..fae8b6cfe9f5c26 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 8480b1de57b0e62..c3fb701458238da 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 a1d19931032f544..603ff74cf27043f 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 110f9848c6d83df..231387f9c004a64 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 5971c7685a4430a..982de01b8bae786 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 a50b33a0eee34a7..d5c566383ebba00 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 1093742d76d6e6d..8a8f7d34aa3a885 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 c64bb1cc463a242..8e084bd8e35dc77 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 a4687fa953dfe24..713428ca08557e6 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 ba91454b3aa02d1..c10403848474fe6 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 d507977de11c89e..0a18cd40b3a6105 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 17d3e0a4876f39a..be19c354cbe1a3f 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 b7fa30186995120..b1aefedc90cef8b 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 5fb1f0d87a5858c..3a016fe68618d83 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 d8fc2ec1439bec8..7df1ae742640fd8 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 b7cca9fd005df1f..acb8043ab3a2503 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); }); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts index 95647649259dca5..5295b033fc7cf4f 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/fleet.ts @@ -25,13 +25,21 @@ const deleteAgentPolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/agent_policies', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { response.body.items.forEach((item: { id: string }) => { rootRequest({ method: 'POST', url: `api/fleet/agent_policies/delete`, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, body: { agentPolicyId: item.id, }, @@ -44,12 +52,20 @@ const deletePackagePolicies = () => { return rootRequest<{ items: Array<{ id: string }> }>({ method: 'GET', url: 'api/fleet/package_policies', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { rootRequest({ method: 'POST', url: `api/fleet/package_policies/delete`, - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, body: { packagePolicyIds: response.body.items.map((item: { id: string }) => item.id), }, @@ -61,7 +77,11 @@ const deletePackages = () => { return rootRequest<{ items: Array<{ status: string; name: string; version: string }> }>({ method: 'GET', url: 'api/fleet/epm/packages', - headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution' }, + headers: { + 'kbn-xsrf': 'cypress-creds', + 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', + }, }).then((response) => { response.body.items.forEach((item) => { if (item.status === 'installed') { @@ -71,6 +91,7 @@ const deletePackages = () => { headers: { 'kbn-xsrf': 'cypress-creds', 'x-elastic-internal-origin': 'security-solution', + 'elastic-api-version': '2023-10-31', }, }); } diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts b/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts index cb0c5cc9c36ba88..d653c6f8505ba98 100644 --- a/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts +++ b/x-pack/test_serverless/functional/test_suites/common/examples/discover_customization_examples/customizations.ts @@ -12,7 +12,7 @@ const TEST_START_TIME = 'Sep 19, 2015 @ 06:31:44.000'; const TEST_END_TIME = 'Sep 23, 2015 @ 18:31:44.000'; export default ({ getService, getPageObjects }: FtrProviderContext) => { - const PageObjects = getPageObjects(['common', 'timePicker', 'header']); + const PageObjects = getPageObjects(['common', 'timePicker', 'header', 'svlCommonPage']); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); @@ -20,10 +20,10 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => { const dataGrid = getService('dataGrid'); const defaultSettings = { defaultIndex: 'logstash-*' }; - // Flaky in serverless tests (before hook) - // Failing: See https://github.com/elastic/kibana/issues/165396 - describe.skip('Customizations', () => { + describe('Customizations', () => { before(async () => { + // TODO: Serverless tests require login first + await PageObjects.svlCommonPage.login(); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');