diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 411bb2a4457e6..2772c3de51065 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -255,7 +255,7 @@ export const DETECTION_ENGINE_RULES_PREVIEW_INDEX_URL = */ export const INTERNAL_DETECTION_ENGINE_URL = '/internal/detection_engine' as const; export const INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL = - `${INTERNAL_DETECTION_ENGINE_URL}/rules/_find_statuses` as const; + `${INTERNAL_DETECTION_ENGINE_URL}/rules/_find_status` as const; export const TIMELINE_RESOLVE_URL = '/api/timeline/resolve' as const; export const TIMELINE_URL = '/api/timeline' as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts index 1437a8230b67a..d489ad562f304 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_statuses_schema.ts @@ -16,3 +16,13 @@ export const findRulesStatusesSchema = t.exact( export type FindRulesStatusesSchema = t.TypeOf; export type FindRulesStatusesSchemaDecoded = FindRulesStatusesSchema; + +export const findRuleStatusSchema = t.exact( + t.type({ + ruleId: t.string, + }) +); + +export type FindRuleStatusSchema = t.TypeOf; + +export type FindRuleStatusSchemaDecoded = FindRuleStatusSchema; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts index e812f5d10e6e9..0045f69968b2a 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts @@ -669,8 +669,8 @@ describe('Detections Rules API', () => { test('check parameter url, query', async () => { await getRuleStatusById({ id: 'mySuperRuleId', signal: abortCtrl.signal }); - expect(fetchMock).toHaveBeenCalledWith('/internal/detection_engine/rules/_find_statuses', { - body: '{"ids":["mySuperRuleId"]}', + expect(fetchMock).toHaveBeenCalledWith('/internal/detection_engine/rules/_find_status', { + body: '{"ruleId":"mySuperRuleId"}', method: 'POST', signal: abortCtrl.signal, }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts index de9d8841c52bf..5f9ad7fdd2bf5 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts @@ -375,7 +375,7 @@ export const getRuleStatusById = async ({ }): Promise => KibanaServices.get().http.fetch(INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, { method: 'POST', - body: JSON.stringify({ ids: [id] }), + body: JSON.stringify({ ruleId: id }), signal, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index fa0bac82da08b..3c1a49c640863 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -237,7 +237,7 @@ export const internalRuleStatusRequest = () => requestMock.create({ method: 'post', path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, - body: { ids: ['04128c15-0d1b-4716-a4c5-46997ac7f3bd'] }, + body: { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }, }); export const getImportRulesRequest = (hapiStream?: HapiReadableStream) => diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts index f1ad41dfd4368..285b839cacb9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.test.ts @@ -13,7 +13,7 @@ import { getRuleExecutionStatusFailed, } from '../__mocks__/request_responses'; import { serverMock, requestContextMock, requestMock } from '../__mocks__'; -import { internalFindRuleStatusRoute } from './internal_find_rule_status_route'; +import { findRuleStatusInternalRoute } from './find_rule_status_internal_route'; import { RuleStatusResponse } from '../../rules/types'; import { AlertExecutionStatusErrorReasons } from '../../../../../../alerting/common'; import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; @@ -39,7 +39,7 @@ describe.each([ getAlertMock(isRuleRegistryEnabled, getQueryRuleParams()) ); - internalFindRuleStatusRoute(server.router); + findRuleStatusInternalRoute(server.router); }); describe('status codes with actionClient and alertClient', () => { @@ -86,7 +86,7 @@ describe.each([ }); const request = internalRuleStatusRequest(); - const ruleId = request.body.ids[0]; + const { ruleId } = request.body; const response = await server.inject(request, context); const responseBody: RuleStatusResponse = response.body; @@ -107,7 +107,9 @@ describe.each([ }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith('Invalid value "undefined" supplied to "ids"'); + expect(result.badRequest).toHaveBeenCalledWith( + 'Invalid value "undefined" supplied to "ruleId"' + ); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts similarity index 75% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts index 8cccf4502eeb9..6d9b371a9370c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/internal_find_rule_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_status_internal_route.ts @@ -11,25 +11,29 @@ import type { SecuritySolutionPluginRouter } from '../../../../types'; import { INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL } from '../../../../../common/constants'; import { buildSiemResponse, mergeStatuses, getFailingRules } from '../utils'; import { - findRulesStatusesSchema, - FindRulesStatusesSchemaDecoded, + findRuleStatusSchema, + FindRuleStatusSchemaDecoded, } from '../../../../../common/detection_engine/schemas/request/find_rule_statuses_schema'; import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; /** - * Given a list of rule ids, return the current status and - * last five errors for each associated rule. + * Returns the current execution status and metrics + last five failed statuses of a given rule. + * Accepts a rule id. + * + * NOTE: This endpoint is a raw implementation of an endpoint for reading rule execution + * status and logs for a given rule (e.g. for use on the Rule Details page). It will be reworked. + * See the plan in https://github.com/elastic/kibana/pull/115574 * * @param router - * @returns RuleStatusResponse + * @returns RuleStatusResponse containing data only for the given rule (normally it contains data for N rules). */ -export const internalFindRuleStatusRoute = (router: SecuritySolutionPluginRouter) => { +export const findRuleStatusInternalRoute = (router: SecuritySolutionPluginRouter) => { router.post( { path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, validate: { - body: buildRouteValidation( - findRulesStatusesSchema + body: buildRouteValidation( + findRuleStatusSchema ), }, options: { @@ -37,7 +41,8 @@ export const internalFindRuleStatusRoute = (router: SecuritySolutionPluginRouter }, }, async (context, request, response) => { - const { body } = request; + const { ruleId } = request.body; + const siemResponse = buildSiemResponse(response); const rulesClient = context.alerting?.getRulesClient(); @@ -45,8 +50,6 @@ export const internalFindRuleStatusRoute = (router: SecuritySolutionPluginRouter return siemResponse.error({ statusCode: 404 }); } - const ruleId = body.ids[0]; - try { const ruleStatusClient = context.securitySolution.getExecutionLogClient(); const spaceId = context.securitySolution.getSpaceId(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index a1668df42eada..af4f8ddbb9ec8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -17,11 +17,16 @@ import { import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters'; /** - * Given a list of rule ids, return the current status and - * last five errors for each associated rule. + * Returns the current execution status and metrics for N rules. + * Accepts an array of rule ids. + * + * NOTE: This endpoint is used on the Rule Management page and will be reworked. + * See the plan in https://github.com/elastic/kibana/pull/115574 * * @param router - * @returns RuleStatusResponse + * @returns RuleStatusResponse containing data for N requested rules. + * RuleStatusResponse[ruleId].failures is always an empty array, because + * we don't need failure history of every rule when we render tables with rules. */ export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) => { router.post( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts index 4fa8c98c9de9f..6ce9d3d1c26ee 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/event_log_adapter/event_log_client.ts @@ -13,6 +13,7 @@ import { SAVED_OBJECT_REL_PRIMARY, } from '../../../../../../event_log/server'; import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { invariant } from '../../../../../common/utils/invariant'; import { IRuleStatusSOAttributes } from '../../rules/types'; import { LogStatusChangeArgs } from '../types'; import { @@ -23,6 +24,8 @@ import { const spaceIdToNamespace = SavedObjectsUtils.namespaceStringToId; +const now = () => new Date().toISOString(); + const statusSeverityDict: Record = { [RuleExecutionStatus.succeeded]: 0, [RuleExecutionStatus['going to run']]: 10, @@ -104,19 +107,23 @@ export class EventLogClient implements IExecLogEventLogClient { }); return findResult.data.map((event) => { - const statusDate = event?.['@timestamp'] ?? new Date().toISOString(); - const status = event?.kibana?.alert?.rule?.execution?.status as + invariant(event, 'Event not found'); + invariant(event['@timestamp'], 'Required "@timestamp" field is not found'); + + const statusDate = event['@timestamp']; + const status = event.kibana?.alert?.rule?.execution?.status as | RuleExecutionStatus | undefined; - const message = event?.message ?? ''; + const isStatusFailed = status === RuleExecutionStatus.failed; + const message = event.message ?? ''; return { statusDate, status, - lastFailureAt: status === RuleExecutionStatus.failed ? statusDate : undefined, - lastFailureMessage: status === RuleExecutionStatus.failed ? message : undefined, - lastSuccessAt: status !== RuleExecutionStatus.failed ? statusDate : undefined, - lastSuccessMessage: status !== RuleExecutionStatus.failed ? message : undefined, + lastFailureAt: isStatusFailed ? statusDate : undefined, + lastFailureMessage: isStatusFailed ? message : undefined, + lastSuccessAt: !isStatusFailed ? statusDate : undefined, + lastSuccessMessage: !isStatusFailed ? message : undefined, lastLookBackDate: undefined, gap: undefined, bulkCreateTimeDurations: undefined, @@ -133,6 +140,7 @@ export class EventLogClient implements IExecLogEventLogClient { spaceId, }: LogExecutionMetricsArgs) { this.eventLogger.logEvent({ + '@timestamp': now(), rule: { id: ruleId, name: ruleName, @@ -177,6 +185,8 @@ export class EventLogClient implements IExecLogEventLogClient { spaceId, }: LogStatusChangeArgs) { this.eventLogger.logEvent({ + '@timestamp': now(), + message, rule: { id: ruleId, name: ruleName, @@ -187,7 +197,6 @@ export class EventLogClient implements IExecLogEventLogClient { action: RuleExecutionLogAction['status-change'], sequence: this.sequence++, }, - message, kibana: { alert: { rule: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts index 2d0be1cb4aab8..53b50bb8fe638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_execution_log/saved_objects_adapter/saved_objects_adapter.ts @@ -84,7 +84,7 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { ): Promise { const result = await this.findRuleStatusSavedObjects(args.ruleId, 1); const currentStatusSavedObject = result[0]; - return currentStatusSavedObject ? currentStatusSavedObject.attributes : undefined; + return currentStatusSavedObject?.attributes; } public async getCurrentStatusBulk( @@ -92,11 +92,7 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient { ): Promise { const { ruleIds } = args; const result = await this.ruleStatusClient.findBulk(ruleIds, 1); - - return mapValues(result, (value) => { - const arrayOfAttributes = value ?? []; - return arrayOfAttributes[0]; - }); + return mapValues(result, (attributes = []) => attributes[0]); } public async deleteCurrentStatus(ruleId: string): Promise { diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index addd3fbd5bd0a..20fbf44e77a48 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -36,7 +36,7 @@ import { performBulkActionRoute } from '../lib/detection_engine/routes/rules/per import { importRulesRoute } from '../lib/detection_engine/routes/rules/import_rules_route'; import { exportRulesRoute } from '../lib/detection_engine/routes/rules/export_rules_route'; import { findRulesStatusesRoute } from '../lib/detection_engine/routes/rules/find_rules_status_route'; -import { internalFindRuleStatusRoute } from '../lib/detection_engine/routes/rules/internal_find_rule_status_route'; +import { findRuleStatusInternalRoute } from '../lib/detection_engine/routes/rules/find_rule_status_internal_route'; import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; import { createTimelinesRoute, @@ -123,7 +123,7 @@ export const initRoutes = ( persistPinnedEventRoute(router, config, security); findRulesStatusesRoute(router); - internalFindRuleStatusRoute(router); + findRuleStatusInternalRoute(router); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status