mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Detections] Reading last 5 failures from Event Log v1 - raw implementation (#115574) (#116947)
**Ticket:** https://github.com/elastic/kibana/issues/106469, https://github.com/elastic/kibana/issues/101013 ## Summary TL;DR: New internal endpoint for reading data from Event Log (raw version), legacy status SO under the hood. With this PR we now read the Failure History (last 5 failures) on the Rule Details page from Event Log. We continue getting the Current Status from the legacy `siem-detection-engine-rule-status` saved objects. Rule Management page also gets data from the legacy saved objects. - [x] Deprecate existing methods for reading data in `IRuleExecutionLogClient`: `.find()` and `.findBulk()` - [x] Introduce new methods for reading data in IRuleExecutionLogClient: - for reading last N execution events for 1 rule from event log - for reading current status and metrics for 1 rule from legacy status SOs - for reading current statuses and metrics for N rules from legacy status SOs - [x] New methods should return data in the legacy status SO format. - [x] Update all the existing endpoints that depend on `IRuleExecutionLogClient` to use the new methods. - [x] Implement a new internal endpoint for fetching current status of the rule execution and execution events from Event Log for a given rule. - [x] The API of the new endpoint should be the same as `rules/_find_statuses` to minimise changes in the app. - [x] Use the new endpoint on the Rule Details page. ## Near-term plan for technical implementation of the Rule Execution Log (https://github.com/elastic/kibana/issues/101013) **Stage 1. Reading last 5 failures from Event Log v1 - raw implementation** - ✔️ done in this PR TL;DR: New internal endpoint for reading data from Event Log (raw version), legacy status SO under the hood. - Deprecate existing methods for reading data in `IRuleExecutionLogClient`: `.find()` and `.findBulk()` - Introduce new methods for reading data in IRuleExecutionLogClient: - for reading last N execution events for 1 rule from event log - for reading current status and metrics for 1 rule from legacy status SOs - for reading current statuses and metrics for N rules from legacy status SOs - New methods should return data in the legacy status SO format. - Update all the existing endpoints that depend on `IRuleExecutionLogClient` to use the new methods. - Implement a new internal endpoint for fetching current status of the rule execution and execution events from Event Log for a given rule. - The API of the new endpoint should be the same as `rules/_find_statuses` to minimise changes in the app. - Use the new endpoint on the Rule Details page. **Stage 2: Reading last 5 failures from Event Log v2 - clean implementation** TL;DR: Clean HTTP API, legacy Rule Status SO under the hood. 🚨🚨🚨 Possible breaking changes in Detections API 🚨🚨🚨 - Design a new data model for the Current Rule Execution Info (the TO-BE new SO type and later the TO-BE data in the rule object itself). - Design a new data model for the Rule Execution Event (read model to be used on the Rule Details page) - Think over changes in `IRuleExecutionLogClient` to support the new data model. - Think over changes in all the endpoints that return any data related to rule monitoring (statuses, metrics, etc). Make sure to check our docs to identify what's documented there regarding rule monitoring. - Update `IRuleExecutionLogClient` to return data in the new format. - Update all the endpoints (including the raw new one) to return data in the new format. - Update Rule Details page to consume data in the new format. - Update Rule Management page to consume data in the new format. **Stage 3: Reading last 5 failures from Event Log v3 - new SO** TL;DR: Clean HTTP API, new Rule Execution Info SO under the hood. - Implement a new SO type for storing the current rule execution info. Relation type: 1 rule - 1 current execution info. - Swap the legacy SO with the new SO in the implementation of `IRuleExecutionLogClient`. **Stage 4: Cleanup and misc** - Revisit the problem of deterministic ordering ([comment](https://github.com/elastic/kibana/pull/115574#discussion_r735803087)) - Remove rule execution log's glue code: adapters, feature switch. - Remove the legacy rule status SO. - Mark the legacy rule status SO as deleted in Kibana Core. - Encapsulate the current space id in the instance of IRuleExecutionLogClient. Remove it from parameters of its methods. - Introduce a Rule Execution Logger scoped to a rule instance. For use in rule executors. - Add test coverage. ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Georgii Gorbachev <georgii.gorbachev@elastic.co>
This commit is contained in:
parent
fe35e2372d
commit
f241f62aa7
43 changed files with 699 additions and 393 deletions
|
@ -250,6 +250,13 @@ export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/pre
|
|||
export const DETECTION_ENGINE_RULES_PREVIEW_INDEX_URL =
|
||||
`${DETECTION_ENGINE_RULES_PREVIEW}/index` as const;
|
||||
|
||||
/**
|
||||
* Internal detection engine routes
|
||||
*/
|
||||
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_status` as const;
|
||||
|
||||
export const TIMELINE_RESOLVE_URL = '/api/timeline/resolve' as const;
|
||||
export const TIMELINE_URL = '/api/timeline' as const;
|
||||
export const TIMELINES_URL = '/api/timelines' as const;
|
||||
|
|
|
@ -16,3 +16,13 @@ export const findRulesStatusesSchema = t.exact(
|
|||
export type FindRulesStatusesSchema = t.TypeOf<typeof findRulesStatusesSchema>;
|
||||
|
||||
export type FindRulesStatusesSchemaDecoded = FindRulesStatusesSchema;
|
||||
|
||||
export const findRuleStatusSchema = t.exact(
|
||||
t.type({
|
||||
ruleId: t.string,
|
||||
})
|
||||
);
|
||||
|
||||
export type FindRuleStatusSchema = t.TypeOf<typeof findRuleStatusSchema>;
|
||||
|
||||
export type FindRuleStatusSchemaDecoded = FindRuleStatusSchema;
|
||||
|
|
|
@ -669,8 +669,8 @@ describe('Detections Rules API', () => {
|
|||
|
||||
test('check parameter url, query', async () => {
|
||||
await getRuleStatusById({ id: 'mySuperRuleId', signal: abortCtrl.signal });
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/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,
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
DETECTION_ENGINE_TAGS_URL,
|
||||
DETECTION_ENGINE_RULES_BULK_ACTION,
|
||||
DETECTION_ENGINE_RULES_PREVIEW,
|
||||
INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL,
|
||||
} from '../../../../../common/constants';
|
||||
import {
|
||||
UpdateRulesProps,
|
||||
|
@ -372,9 +373,9 @@ export const getRuleStatusById = async ({
|
|||
id: string;
|
||||
signal: AbortSignal;
|
||||
}): Promise<RuleStatusResponse> =>
|
||||
KibanaServices.get().http.fetch<RuleStatusResponse>(DETECTION_ENGINE_RULES_STATUS_URL, {
|
||||
KibanaServices.get().http.fetch<RuleStatusResponse>(INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ids: [id] }),
|
||||
body: JSON.stringify({ ruleId: id }),
|
||||
signal,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
|
||||
import { ruleTypeMappings } from '@kbn/securitysolution-rules';
|
||||
|
||||
import { SavedObjectsFindResponse, SavedObjectsFindResult } from 'kibana/server';
|
||||
import { SavedObjectsFindResponse } from 'src/core/server';
|
||||
|
||||
import { ActionResult } from '../../../../../../actions/server';
|
||||
import {
|
||||
|
@ -23,6 +23,7 @@ import {
|
|||
DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL,
|
||||
DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL,
|
||||
DETECTION_ENGINE_RULES_BULK_ACTION,
|
||||
INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL,
|
||||
} from '../../../../../common/constants';
|
||||
import {
|
||||
RuleAlertType,
|
||||
|
@ -42,7 +43,7 @@ import { SanitizedAlert, ResolvedSanitizedRule } from '../../../../../../alertin
|
|||
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
|
||||
import { getPerformBulkActionSchemaMock } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock';
|
||||
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||
import { FindBulkExecutionLogResponse } from '../../rule_execution_log/types';
|
||||
import { GetCurrentStatusBulkResult } from '../../rule_execution_log/types';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import type { LegacyRuleNotificationAlertType } from '../../notifications/legacy_types';
|
||||
|
||||
|
@ -232,6 +233,13 @@ export const ruleStatusRequest = () =>
|
|||
body: { ids: ['04128c15-0d1b-4716-a4c5-46997ac7f3bd'] },
|
||||
});
|
||||
|
||||
export const internalRuleStatusRequest = () =>
|
||||
requestMock.create({
|
||||
method: 'post',
|
||||
path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL,
|
||||
body: { ruleId: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' },
|
||||
});
|
||||
|
||||
export const getImportRulesRequest = (hapiStream?: HapiReadableStream) =>
|
||||
requestMock.create({
|
||||
method: 'post',
|
||||
|
@ -475,94 +483,64 @@ export const getEmptySavedObjectsResponse =
|
|||
saved_objects: [],
|
||||
});
|
||||
|
||||
export const getRuleExecutionStatuses = (): Array<
|
||||
SavedObjectsFindResult<IRuleStatusSOAttributes>
|
||||
> => [
|
||||
{
|
||||
type: 'my-type',
|
||||
id: 'e0b86950-4e9f-11ea-bdbd-07b56aa159b3',
|
||||
attributes: {
|
||||
statusDate: '2020-02-18T15:26:49.783Z',
|
||||
status: RuleExecutionStatus.succeeded,
|
||||
lastFailureAt: undefined,
|
||||
lastSuccessAt: '2020-02-18T15:26:49.783Z',
|
||||
lastFailureMessage: undefined,
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
score: 1,
|
||||
references: [
|
||||
{
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bc',
|
||||
type: 'alert',
|
||||
name: 'alert_0',
|
||||
},
|
||||
],
|
||||
updated_at: '2020-02-18T15:26:51.333Z',
|
||||
version: 'WzQ2LDFd',
|
||||
},
|
||||
{
|
||||
type: 'my-type',
|
||||
id: '91246bd0-5261-11ea-9650-33b954270f67',
|
||||
attributes: {
|
||||
statusDate: '2020-02-18T15:15:58.806Z',
|
||||
status: RuleExecutionStatus.failed,
|
||||
lastFailureAt: '2020-02-18T15:15:58.806Z',
|
||||
lastSuccessAt: '2020-02-13T20:31:59.855Z',
|
||||
lastFailureMessage:
|
||||
'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.',
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
score: 1,
|
||||
references: [
|
||||
{
|
||||
id: '1ea5a820-4da1-4e82-92a1-2b43a7bece08',
|
||||
type: 'alert',
|
||||
name: 'alert_0',
|
||||
},
|
||||
],
|
||||
updated_at: '2020-02-18T15:15:58.860Z',
|
||||
version: 'WzMyLDFd',
|
||||
},
|
||||
export const getRuleExecutionStatusSucceeded = (): IRuleStatusSOAttributes => ({
|
||||
statusDate: '2020-02-18T15:26:49.783Z',
|
||||
status: RuleExecutionStatus.succeeded,
|
||||
lastFailureAt: undefined,
|
||||
lastSuccessAt: '2020-02-18T15:26:49.783Z',
|
||||
lastFailureMessage: undefined,
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
});
|
||||
|
||||
export const getRuleExecutionStatusFailed = (): IRuleStatusSOAttributes => ({
|
||||
statusDate: '2020-02-18T15:15:58.806Z',
|
||||
status: RuleExecutionStatus.failed,
|
||||
lastFailureAt: '2020-02-18T15:15:58.806Z',
|
||||
lastSuccessAt: '2020-02-13T20:31:59.855Z',
|
||||
lastFailureMessage:
|
||||
'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.',
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
});
|
||||
|
||||
export const getRuleExecutionStatuses = (): IRuleStatusSOAttributes[] => [
|
||||
getRuleExecutionStatusSucceeded(),
|
||||
getRuleExecutionStatusFailed(),
|
||||
];
|
||||
|
||||
export const getFindBulkResultStatus = (): FindBulkExecutionLogResponse => ({
|
||||
'04128c15-0d1b-4716-a4c5-46997ac7f3bd': [
|
||||
{
|
||||
statusDate: '2020-02-18T15:26:49.783Z',
|
||||
status: RuleExecutionStatus.succeeded,
|
||||
lastFailureAt: undefined,
|
||||
lastSuccessAt: '2020-02-18T15:26:49.783Z',
|
||||
lastFailureMessage: undefined,
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
],
|
||||
'1ea5a820-4da1-4e82-92a1-2b43a7bece08': [
|
||||
{
|
||||
statusDate: '2020-02-18T15:15:58.806Z',
|
||||
status: RuleExecutionStatus.failed,
|
||||
lastFailureAt: '2020-02-18T15:15:58.806Z',
|
||||
lastSuccessAt: '2020-02-13T20:31:59.855Z',
|
||||
lastFailureMessage:
|
||||
'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.',
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
],
|
||||
export const getFindBulkResultStatus = (): GetCurrentStatusBulkResult => ({
|
||||
'04128c15-0d1b-4716-a4c5-46997ac7f3bd': {
|
||||
statusDate: '2020-02-18T15:26:49.783Z',
|
||||
status: RuleExecutionStatus.succeeded,
|
||||
lastFailureAt: undefined,
|
||||
lastSuccessAt: '2020-02-18T15:26:49.783Z',
|
||||
lastFailureMessage: undefined,
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
'1ea5a820-4da1-4e82-92a1-2b43a7bece08': {
|
||||
statusDate: '2020-02-18T15:15:58.806Z',
|
||||
status: RuleExecutionStatus.failed,
|
||||
lastFailureAt: '2020-02-18T15:15:58.806Z',
|
||||
lastSuccessAt: '2020-02-13T20:31:59.855Z',
|
||||
lastFailureMessage:
|
||||
'Signal rule name: "Query with a rule id Number 1", id: "1ea5a820-4da1-4e82-92a1-2b43a7bece08", rule_id: "query-rule-id-1" has a time gap of 5 days (412682928ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.',
|
||||
lastSuccessMessage: 'succeeded',
|
||||
lastLookBackDate: new Date('2020-02-18T15:14:58.806Z').toISOString(),
|
||||
gap: '500.32',
|
||||
searchAfterTimeDurations: ['200.00'],
|
||||
bulkCreateTimeDurations: ['800.43'],
|
||||
},
|
||||
});
|
||||
|
||||
export const getBasicEmptySearchResponse = (): estypes.SearchResponse<unknown> => ({
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
getEmptyFindResult,
|
||||
getAlertMock,
|
||||
getCreateRequest,
|
||||
getRuleExecutionStatuses,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
getFindResultWithSingleHit,
|
||||
createMlRuleRequest,
|
||||
getBasicEmptySearchResponse,
|
||||
|
@ -43,7 +43,9 @@ describe.each([
|
|||
clients.rulesClient.create.mockResolvedValue(
|
||||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
); // creation succeeds
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses()); // needed to transform: ;
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
|
||||
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
|
||||
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
|
||||
|
|
|
@ -106,14 +106,13 @@ export const createRulesRoute = (
|
|||
await rulesClient.muteAll({ id: createdRule.id });
|
||||
}
|
||||
|
||||
const ruleStatuses = await context.securitySolution.getExecutionLogClient().find({
|
||||
logsCount: 1,
|
||||
const ruleStatus = await context.securitySolution.getExecutionLogClient().getCurrentStatus({
|
||||
ruleId: createdRule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
const [validated, errors] = newTransformValidate(
|
||||
createdRule,
|
||||
ruleStatuses[0],
|
||||
ruleStatus,
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
if (errors != null) {
|
||||
|
|
|
@ -80,21 +80,19 @@ export const deleteRulesBulkRoute = (
|
|||
return getIdBulkError({ id, ruleId });
|
||||
}
|
||||
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 6,
|
||||
const ruleStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
await deleteRules({
|
||||
ruleId: rule.id,
|
||||
rulesClient,
|
||||
ruleStatusClient,
|
||||
ruleStatuses,
|
||||
id: rule.id,
|
||||
});
|
||||
return transformValidateBulkError(
|
||||
idOrRuleIdOrUnknown,
|
||||
rule,
|
||||
ruleStatuses,
|
||||
ruleStatus,
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
} catch (err) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
getDeleteRequest,
|
||||
getFindResultWithSingleHit,
|
||||
getDeleteRequestById,
|
||||
getRuleExecutionStatuses,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
getEmptySavedObjectsResponse,
|
||||
} from '../__mocks__/request_responses';
|
||||
import { requestContextMock, serverMock, requestMock } from '../__mocks__';
|
||||
|
@ -32,7 +32,9 @@ describe.each([
|
|||
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled));
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses());
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
|
||||
deleteRulesRoute(server.router, isRuleRegistryEnabled);
|
||||
});
|
||||
|
|
|
@ -62,18 +62,16 @@ export const deleteRulesRoute = (
|
|||
});
|
||||
}
|
||||
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 6,
|
||||
const currentStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
await deleteRules({
|
||||
ruleId: rule.id,
|
||||
rulesClient,
|
||||
ruleStatusClient,
|
||||
ruleStatuses,
|
||||
id: rule.id,
|
||||
});
|
||||
const transformed = transform(rule, ruleStatuses[0], isRuleRegistryEnabled);
|
||||
const transformed = transform(rule, currentStatus, isRuleRegistryEnabled);
|
||||
if (transformed == null) {
|
||||
return siemResponse.error({ statusCode: 500, body: 'failed to transform alert' });
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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 { INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL } from '../../../../../common/constants';
|
||||
import {
|
||||
internalRuleStatusRequest,
|
||||
getAlertMock,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
getRuleExecutionStatusFailed,
|
||||
} from '../__mocks__/request_responses';
|
||||
import { serverMock, requestContextMock, requestMock } from '../__mocks__';
|
||||
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';
|
||||
|
||||
describe.each([
|
||||
['Legacy', false],
|
||||
['RAC', true],
|
||||
])(`${INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL} - %s`, (_, isRuleRegistryEnabled) => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
|
||||
beforeEach(async () => {
|
||||
server = serverMock.create();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
clients.ruleExecutionLogClient.getLastFailures.mockResolvedValue([
|
||||
getRuleExecutionStatusFailed(),
|
||||
]);
|
||||
clients.rulesClient.get.mockResolvedValue(
|
||||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
);
|
||||
|
||||
findRuleStatusInternalRoute(server.router);
|
||||
});
|
||||
|
||||
describe('status codes with actionClient and alertClient', () => {
|
||||
test('returns 200 when finding a single rule status with a valid rulesClient', async () => {
|
||||
const response = await server.inject(internalRuleStatusRequest(), context);
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
test('returns 404 if alertClient is not available on the route', async () => {
|
||||
context.alerting.getRulesClient = jest.fn();
|
||||
const response = await server.inject(internalRuleStatusRequest(), context);
|
||||
expect(response.status).toEqual(404);
|
||||
expect(response.body).toEqual({ message: 'Not Found', status_code: 404 });
|
||||
});
|
||||
|
||||
test('catch error when status search throws error', async () => {
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockImplementation(async () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await server.inject(internalRuleStatusRequest(), context);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Test error',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns success if rule status client writes an error status', async () => {
|
||||
// 0. task manager tried to run the rule but couldn't, so the alerting framework
|
||||
// wrote an error to the executionStatus.
|
||||
const failingExecutionRule = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
|
||||
failingExecutionRule.executionStatus = {
|
||||
status: 'error',
|
||||
lastExecutionDate: failingExecutionRule.executionStatus.lastExecutionDate,
|
||||
error: {
|
||||
reason: AlertExecutionStatusErrorReasons.Read,
|
||||
message: 'oops',
|
||||
},
|
||||
};
|
||||
|
||||
// 1. getFailingRules api found a rule where the executionStatus was 'error'
|
||||
clients.rulesClient.get.mockResolvedValue({
|
||||
...failingExecutionRule,
|
||||
});
|
||||
|
||||
const request = internalRuleStatusRequest();
|
||||
const { ruleId } = request.body;
|
||||
|
||||
const response = await server.inject(request, context);
|
||||
const responseBody: RuleStatusResponse = response.body;
|
||||
const ruleStatus = responseBody[ruleId].current_status;
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(ruleStatus?.status).toEqual('failed');
|
||||
expect(ruleStatus?.last_failure_message).toEqual('Reason: read Message: oops');
|
||||
});
|
||||
});
|
||||
|
||||
describe('request validation', () => {
|
||||
test('disallows singular id query param', async () => {
|
||||
const request = requestMock.create({
|
||||
method: 'post',
|
||||
path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL,
|
||||
body: { id: ['someId'] },
|
||||
});
|
||||
const result = server.validate(request);
|
||||
|
||||
expect(result.badRequest).toHaveBeenCalledWith(
|
||||
'Invalid value "undefined" supplied to "ruleId"'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
import { INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL } from '../../../../../common/constants';
|
||||
import { buildSiemResponse, mergeStatuses, getFailingRules } from '../utils';
|
||||
import {
|
||||
findRuleStatusSchema,
|
||||
FindRuleStatusSchemaDecoded,
|
||||
} from '../../../../../common/detection_engine/schemas/request/find_rule_statuses_schema';
|
||||
import { mergeAlertWithSidecarStatus } from '../../schemas/rule_converters';
|
||||
|
||||
/**
|
||||
* 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 containing data only for the given rule (normally it contains data for N rules).
|
||||
*/
|
||||
export const findRuleStatusInternalRoute = (router: SecuritySolutionPluginRouter) => {
|
||||
router.post(
|
||||
{
|
||||
path: INTERNAL_DETECTION_ENGINE_RULE_STATUS_URL,
|
||||
validate: {
|
||||
body: buildRouteValidation<typeof findRuleStatusSchema, FindRuleStatusSchemaDecoded>(
|
||||
findRuleStatusSchema
|
||||
),
|
||||
},
|
||||
options: {
|
||||
tags: ['access:securitySolution'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { ruleId } = request.body;
|
||||
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const rulesClient = context.alerting?.getRulesClient();
|
||||
|
||||
if (!rulesClient) {
|
||||
return siemResponse.error({ statusCode: 404 });
|
||||
}
|
||||
|
||||
try {
|
||||
const ruleStatusClient = context.securitySolution.getExecutionLogClient();
|
||||
const spaceId = context.securitySolution.getSpaceId();
|
||||
|
||||
const [currentStatus, lastFailures, failingRules] = await Promise.all([
|
||||
ruleStatusClient.getCurrentStatus({ ruleId, spaceId }),
|
||||
ruleStatusClient.getLastFailures({ ruleId, spaceId }),
|
||||
getFailingRules([ruleId], rulesClient),
|
||||
]);
|
||||
|
||||
const failingRule = failingRules[ruleId];
|
||||
let statuses = {};
|
||||
|
||||
if (currentStatus != null) {
|
||||
const finalCurrentStatus =
|
||||
failingRule != null
|
||||
? mergeAlertWithSidecarStatus(failingRule, currentStatus)
|
||||
: currentStatus;
|
||||
|
||||
statuses = mergeStatuses(ruleId, [finalCurrentStatus, ...lastFailures], statuses);
|
||||
}
|
||||
|
||||
return response.ok({ body: statuses });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -36,7 +36,9 @@ describe.each([
|
|||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
);
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus());
|
||||
clients.ruleExecutionLogClient.getCurrentStatusBulk.mockResolvedValue(
|
||||
getFindBulkResultStatus()
|
||||
);
|
||||
|
||||
findRulesRoute(server.router, logger, isRuleRegistryEnabled);
|
||||
});
|
||||
|
|
|
@ -68,15 +68,14 @@ export const findRulesRoute = (
|
|||
});
|
||||
const alertIds = rules.data.map((rule) => rule.id);
|
||||
|
||||
const [ruleStatuses, ruleActions] = await Promise.all([
|
||||
execLogClient.findBulk({
|
||||
const [currentStatusesByRuleId, ruleActions] = await Promise.all([
|
||||
execLogClient.getCurrentStatusBulk({
|
||||
ruleIds: alertIds,
|
||||
logsCount: 1,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
}),
|
||||
legacyGetBulkRuleActionsSavedObject({ alertIds, savedObjectsClient, logger }),
|
||||
]);
|
||||
const transformed = transformFindAlerts(rules, ruleStatuses, ruleActions);
|
||||
const transformed = transformFindAlerts(rules, currentStatusesByRuleId, ruleActions);
|
||||
if (transformed == null) {
|
||||
return siemResponse.error({ statusCode: 500, body: 'Internal error transforming' });
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,9 @@ describe.each([
|
|||
beforeEach(async () => {
|
||||
server = serverMock.create();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
clients.ruleExecutionLogClient.findBulk.mockResolvedValue(getFindBulkResultStatus()); // successful status search
|
||||
clients.ruleExecutionLogClient.getCurrentStatusBulk.mockResolvedValue(
|
||||
getFindBulkResultStatus()
|
||||
); // successful status search
|
||||
clients.rulesClient.get.mockResolvedValue(
|
||||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
);
|
||||
|
@ -48,7 +50,7 @@ describe.each([
|
|||
});
|
||||
|
||||
test('catch error when status search throws error', async () => {
|
||||
clients.ruleExecutionLogClient.findBulk.mockImplementation(async () => {
|
||||
clients.ruleExecutionLogClient.getCurrentStatusBulk.mockImplementation(async () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await server.inject(ruleStatusRequest(), context);
|
||||
|
|
|
@ -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(
|
||||
|
@ -48,31 +53,30 @@ export const findRulesStatusesRoute = (router: SecuritySolutionPluginRouter) =>
|
|||
const ids = body.ids;
|
||||
try {
|
||||
const ruleStatusClient = context.securitySolution.getExecutionLogClient();
|
||||
const [statusesById, failingRules] = await Promise.all([
|
||||
ruleStatusClient.findBulk({
|
||||
const [currentStatusesByRuleId, failingRules] = await Promise.all([
|
||||
ruleStatusClient.getCurrentStatusBulk({
|
||||
ruleIds: ids,
|
||||
logsCount: 6,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
}),
|
||||
getFailingRules(ids, rulesClient),
|
||||
]);
|
||||
|
||||
const statuses = ids.reduce((acc, id) => {
|
||||
const lastFiveErrorsForId = statusesById[id];
|
||||
const currentStatus = currentStatusesByRuleId[id];
|
||||
const failingRule = failingRules[id];
|
||||
|
||||
if (lastFiveErrorsForId == null || lastFiveErrorsForId.length === 0) {
|
||||
if (currentStatus == null) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const failingRule = failingRules[id];
|
||||
const finalCurrentStatus =
|
||||
failingRule != null
|
||||
? mergeAlertWithSidecarStatus(failingRule, currentStatus)
|
||||
: currentStatus;
|
||||
|
||||
if (failingRule != null) {
|
||||
const currentStatus = mergeAlertWithSidecarStatus(failingRule, lastFiveErrorsForId[0]);
|
||||
const updatedLastFiveErrorsSO = [currentStatus, ...lastFiveErrorsForId.slice(1)];
|
||||
return mergeStatuses(id, updatedLastFiveErrorsSO, acc);
|
||||
}
|
||||
return mergeStatuses(id, [...lastFiveErrorsForId], acc);
|
||||
return mergeStatuses(id, [finalCurrentStatus], acc);
|
||||
}, {});
|
||||
|
||||
return response.ok({ body: statuses });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
|
|
|
@ -194,12 +194,11 @@ export const patchRulesBulkRoute = (
|
|||
exceptionsList,
|
||||
});
|
||||
if (rule != null && rule.enabled != null && rule.name != null) {
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
const ruleStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled);
|
||||
return transformValidateBulkError(rule.id, rule, ruleStatus, isRuleRegistryEnabled);
|
||||
} else {
|
||||
return getIdBulkError({ id, ruleId });
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../mach
|
|||
import { buildMlAuthz } from '../../../machine_learning/authz';
|
||||
import {
|
||||
getEmptyFindResult,
|
||||
getRuleExecutionStatuses,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
getAlertMock,
|
||||
getPatchRequest,
|
||||
getFindResultWithSingleHit,
|
||||
|
@ -46,8 +46,15 @@ describe.each([
|
|||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
); // successful update
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform
|
||||
clients.savedObjectsClient.create.mockResolvedValue(getRuleExecutionStatuses()[0]); // successful transform
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue(getRuleExecutionStatuses());
|
||||
clients.savedObjectsClient.create.mockResolvedValue({
|
||||
type: 'my-type',
|
||||
id: 'e0b86950-4e9f-11ea-bdbd-07b56aa159b3',
|
||||
attributes: getRuleExecutionStatusSucceeded(),
|
||||
references: [],
|
||||
}); // successful transform
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
|
||||
patchRulesRoute(server.router, ml, isRuleRegistryEnabled);
|
||||
});
|
||||
|
|
|
@ -195,17 +195,12 @@ export const patchRulesRoute = (
|
|||
exceptionsList,
|
||||
});
|
||||
if (rule != null && rule.enabled != null && rule.name != null) {
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
const ruleStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
|
||||
const [validated, errors] = transformValidate(
|
||||
rule,
|
||||
ruleStatuses[0],
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
const [validated, errors] = transformValidate(rule, ruleStatus, isRuleRegistryEnabled);
|
||||
if (errors != null) {
|
||||
return siemResponse.error({ statusCode: 500, body: errors });
|
||||
} else {
|
||||
|
|
|
@ -109,16 +109,10 @@ export const performBulkActionRoute = (
|
|||
case BulkAction.delete:
|
||||
await Promise.all(
|
||||
rules.data.map(async (rule) => {
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 6,
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
await deleteRules({
|
||||
ruleId: rule.id,
|
||||
rulesClient,
|
||||
ruleStatusClient,
|
||||
ruleStatuses,
|
||||
id: rule.id,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
getFindResultWithSingleHit,
|
||||
nonRuleFindResult,
|
||||
getEmptySavedObjectsResponse,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
resolveAlertMock,
|
||||
} from '../__mocks__/request_responses';
|
||||
import { requestMock, requestContextMock, serverMock } from '../__mocks__';
|
||||
|
@ -37,7 +38,9 @@ describe.each([
|
|||
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit(isRuleRegistryEnabled)); // rule exists
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // successful transform
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue([]);
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
|
||||
clients.rulesClient.resolve.mockResolvedValue({
|
||||
...resolveAlertMock(isRuleRegistryEnabled, {
|
||||
|
|
|
@ -70,12 +70,10 @@ export const readRulesRoute = (
|
|||
ruleAlertId: rule.id,
|
||||
logger,
|
||||
});
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
const currentStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
const [currentStatus] = ruleStatuses;
|
||||
if (currentStatus != null && rule.executionStatus.status === 'error') {
|
||||
currentStatus.attributes.lastFailureMessage = `Reason: ${rule.executionStatus.error?.reason} Message: ${rule.executionStatus.error?.message}`;
|
||||
currentStatus.attributes.lastFailureAt =
|
||||
|
|
|
@ -95,12 +95,11 @@ export const updateRulesBulkRoute = (
|
|||
isRuleRegistryEnabled,
|
||||
});
|
||||
if (rule != null) {
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
const ruleStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
return transformValidateBulkError(rule.id, rule, ruleStatuses, isRuleRegistryEnabled);
|
||||
return transformValidateBulkError(rule.id, rule, ruleStatus, isRuleRegistryEnabled);
|
||||
} else {
|
||||
return getIdBulkError({ id: payloadRule.id, ruleId: payloadRule.rule_id });
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
getAlertMock,
|
||||
getUpdateRequest,
|
||||
getFindResultWithSingleHit,
|
||||
getRuleExecutionStatusSucceeded,
|
||||
nonRuleFindResult,
|
||||
typicalMlRulePayload,
|
||||
} from '../__mocks__/request_responses';
|
||||
|
@ -43,8 +44,11 @@ describe.each([
|
|||
clients.rulesClient.update.mockResolvedValue(
|
||||
getAlertMock(isRuleRegistryEnabled, getQueryRuleParams())
|
||||
); // successful update
|
||||
clients.ruleExecutionLogClient.find.mockResolvedValue([]); // successful transform: ;
|
||||
clients.ruleExecutionLogClient.getCurrentStatus.mockResolvedValue(
|
||||
getRuleExecutionStatusSucceeded()
|
||||
);
|
||||
clients.appClient.getSignalsIndex.mockReturnValue('.siem-signals-test-index');
|
||||
|
||||
updateRulesRoute(server.router, ml, isRuleRegistryEnabled);
|
||||
});
|
||||
|
||||
|
|
|
@ -86,16 +86,11 @@ export const updateRulesRoute = (
|
|||
});
|
||||
|
||||
if (rule != null) {
|
||||
const ruleStatuses = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
const ruleStatus = await ruleStatusClient.getCurrentStatus({
|
||||
ruleId: rule.id,
|
||||
spaceId: context.securitySolution.getSpaceId(),
|
||||
});
|
||||
const [validated, errors] = transformValidate(
|
||||
rule,
|
||||
ruleStatuses[0],
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
const [validated, errors] = transformValidate(rule, ruleStatus, isRuleRegistryEnabled);
|
||||
if (errors != null) {
|
||||
return siemResponse.error({ statusCode: 500, body: errors });
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { countBy } from 'lodash/fp';
|
||||
import { SavedObject } from 'kibana/server';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema';
|
||||
|
@ -18,8 +17,7 @@ import { INTERNAL_IDENTIFIER } from '../../../../../common/constants';
|
|||
import {
|
||||
RuleAlertType,
|
||||
isAlertType,
|
||||
IRuleSavedAttributesSavedObjectAttributes,
|
||||
isRuleStatusSavedObjectType,
|
||||
isRuleStatusSavedObjectAttributes,
|
||||
IRuleStatusSOAttributes,
|
||||
} from '../../rules/types';
|
||||
import { createBulkErrorObject, BulkError, OutputError } from '../utils';
|
||||
|
@ -98,10 +96,10 @@ export const transformTags = (tags: string[]): string[] => {
|
|||
// those on the export
|
||||
export const transformAlertToRule = (
|
||||
alert: SanitizedAlert<RuleParams>,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
|
||||
ruleStatus?: IRuleStatusSOAttributes,
|
||||
legacyRuleActions?: LegacyRulesActionsSavedObject | null
|
||||
): Partial<RulesSchema> => {
|
||||
return internalRuleToAPIResponse(alert, ruleStatus?.attributes, legacyRuleActions);
|
||||
return internalRuleToAPIResponse(alert, ruleStatus, legacyRuleActions);
|
||||
};
|
||||
|
||||
export const transformAlertsToRules = (
|
||||
|
@ -113,7 +111,7 @@ export const transformAlertsToRules = (
|
|||
|
||||
export const transformFindAlerts = (
|
||||
findResults: FindResult<RuleParams>,
|
||||
ruleStatuses: { [key: string]: IRuleStatusSOAttributes[] | undefined },
|
||||
currentStatusesByRuleId: { [key: string]: IRuleStatusSOAttributes | undefined },
|
||||
legacyRuleActions: Record<string, LegacyRulesActionsSavedObject | undefined>
|
||||
): {
|
||||
page: number;
|
||||
|
@ -126,8 +124,7 @@ export const transformFindAlerts = (
|
|||
perPage: findResults.perPage,
|
||||
total: findResults.total,
|
||||
data: findResults.data.map((alert) => {
|
||||
const statuses = ruleStatuses[alert.id];
|
||||
const status = statuses ? statuses[0] : undefined;
|
||||
const status = currentStatusesByRuleId[alert.id];
|
||||
return internalRuleToAPIResponse(alert, status, legacyRuleActions[alert.id]);
|
||||
}),
|
||||
};
|
||||
|
@ -135,14 +132,14 @@ export const transformFindAlerts = (
|
|||
|
||||
export const transform = (
|
||||
alert: PartialAlert<RuleParams>,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
|
||||
ruleStatus?: IRuleStatusSOAttributes,
|
||||
isRuleRegistryEnabled?: boolean,
|
||||
legacyRuleActions?: LegacyRulesActionsSavedObject | null
|
||||
): Partial<RulesSchema> | null => {
|
||||
if (isAlertType(isRuleRegistryEnabled ?? false, alert)) {
|
||||
return transformAlertToRule(
|
||||
alert,
|
||||
isRuleStatusSavedObjectType(ruleStatus) ? ruleStatus : undefined,
|
||||
isRuleStatusSavedObjectAttributes(ruleStatus) ? ruleStatus : undefined,
|
||||
legacyRuleActions
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { transformValidate, transformValidateBulkError } from './validate';
|
||||
import { BulkError } from '../utils';
|
||||
import { RulesSchema } from '../../../../../common/detection_engine/schemas/response';
|
||||
import { getAlertMock, getRuleExecutionStatuses } from '../__mocks__/request_responses';
|
||||
import { getAlertMock, getRuleExecutionStatusSucceeded } from '../__mocks__/request_responses';
|
||||
import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock';
|
||||
import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock';
|
||||
import { getQueryRuleParams } from '../../schemas/rule_schemas.mock';
|
||||
|
@ -121,12 +121,12 @@ describe.each([
|
|||
});
|
||||
|
||||
test('it should do a validation correctly of a rule id with ruleStatus passed in', () => {
|
||||
const ruleStatuses = getRuleExecutionStatuses();
|
||||
const ruleStatus = getRuleExecutionStatusSucceeded();
|
||||
const ruleAlert = getAlertMock(isRuleRegistryEnabled, getQueryRuleParams());
|
||||
const validatedOrError = transformValidateBulkError(
|
||||
'rule-1',
|
||||
ruleAlert,
|
||||
ruleStatuses,
|
||||
ruleStatus,
|
||||
isRuleRegistryEnabled
|
||||
);
|
||||
const expected: RulesSchema = {
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObject, SavedObjectsFindResult } from 'kibana/server';
|
||||
|
||||
import { validateNonExact } from '@kbn/securitysolution-io-ts-utils';
|
||||
import {
|
||||
FullResponseSchema,
|
||||
|
@ -19,9 +17,8 @@ import {
|
|||
import { PartialAlert } from '../../../../../../alerting/server';
|
||||
import {
|
||||
isAlertType,
|
||||
IRuleSavedAttributesSavedObjectAttributes,
|
||||
IRuleStatusSOAttributes,
|
||||
isRuleStatusSavedObjectType,
|
||||
isRuleStatusSavedObjectAttributes,
|
||||
} from '../../rules/types';
|
||||
import { createBulkErrorObject, BulkError } from '../utils';
|
||||
import { transform, transformAlertToRule } from './utils';
|
||||
|
@ -31,7 +28,7 @@ import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rul
|
|||
|
||||
export const transformValidate = (
|
||||
alert: PartialAlert<RuleParams>,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
|
||||
ruleStatus?: IRuleStatusSOAttributes,
|
||||
isRuleRegistryEnabled?: boolean,
|
||||
legacyRuleActions?: LegacyRulesActionsSavedObject | null
|
||||
): [RulesSchema | null, string | null] => {
|
||||
|
@ -45,7 +42,7 @@ export const transformValidate = (
|
|||
|
||||
export const newTransformValidate = (
|
||||
alert: PartialAlert<RuleParams>,
|
||||
ruleStatus?: SavedObject<IRuleSavedAttributesSavedObjectAttributes>,
|
||||
ruleStatus?: IRuleStatusSOAttributes,
|
||||
isRuleRegistryEnabled?: boolean,
|
||||
legacyRuleActions?: LegacyRulesActionsSavedObject | null
|
||||
): [FullResponseSchema | null, string | null] => {
|
||||
|
@ -60,12 +57,12 @@ export const newTransformValidate = (
|
|||
export const transformValidateBulkError = (
|
||||
ruleId: string,
|
||||
alert: PartialAlert<RuleParams>,
|
||||
ruleStatus?: Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>,
|
||||
ruleStatus?: IRuleStatusSOAttributes,
|
||||
isRuleRegistryEnabled?: boolean
|
||||
): RulesSchema | BulkError => {
|
||||
if (isAlertType(isRuleRegistryEnabled ?? false, alert)) {
|
||||
if (ruleStatus && ruleStatus?.length > 0 && isRuleStatusSavedObjectType(ruleStatus[0])) {
|
||||
const transformed = transformAlertToRule(alert, ruleStatus[0]);
|
||||
if (ruleStatus && isRuleStatusSavedObjectAttributes(ruleStatus)) {
|
||||
const transformed = transformAlertToRule(alert, ruleStatus);
|
||||
const [validated, errors] = validateNonExact(transformed, rulesSchema);
|
||||
if (errors != null || validated == null) {
|
||||
return createBulkErrorObject({
|
||||
|
|
|
@ -11,8 +11,13 @@ export const ruleExecutionLogClientMock = {
|
|||
create: (): jest.Mocked<IRuleExecutionLogClient> => ({
|
||||
find: jest.fn(),
|
||||
findBulk: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
|
||||
getLastFailures: jest.fn(),
|
||||
getCurrentStatus: jest.fn(),
|
||||
getCurrentStatusBulk: jest.fn(),
|
||||
|
||||
deleteCurrentStatus: jest.fn(),
|
||||
|
||||
logStatusChange: jest.fn(),
|
||||
logExecutionMetrics: jest.fn(),
|
||||
}),
|
||||
|
|
|
@ -7,18 +7,25 @@
|
|||
|
||||
import { sum } from 'lodash';
|
||||
import { SavedObjectsClientContract } from '../../../../../../../../src/core/server';
|
||||
import { IEventLogService } from '../../../../../../event_log/server';
|
||||
import { IEventLogClient, IEventLogService } from '../../../../../../event_log/server';
|
||||
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||
import { IRuleStatusSOAttributes } from '../../rules/types';
|
||||
import { SavedObjectsAdapter } from '../saved_objects_adapter/saved_objects_adapter';
|
||||
import {
|
||||
FindBulkExecutionLogArgs,
|
||||
FindExecutionLogArgs,
|
||||
GetCurrentStatusArgs,
|
||||
GetCurrentStatusBulkArgs,
|
||||
GetCurrentStatusBulkResult,
|
||||
GetLastFailuresArgs,
|
||||
IRuleExecutionLogClient,
|
||||
LogExecutionMetricsArgs,
|
||||
LogStatusChangeArgs,
|
||||
UpdateExecutionLogArgs,
|
||||
} from '../types';
|
||||
import { EventLogClient } from './event_log_client';
|
||||
|
||||
const MAX_LAST_FAILURES = 5;
|
||||
|
||||
export class EventLogAdapter implements IRuleExecutionLogClient {
|
||||
private eventLogClient: EventLogClient;
|
||||
/**
|
||||
|
@ -28,38 +35,46 @@ export class EventLogAdapter implements IRuleExecutionLogClient {
|
|||
*/
|
||||
private savedObjectsAdapter: IRuleExecutionLogClient;
|
||||
|
||||
constructor(eventLogService: IEventLogService, savedObjectsClient: SavedObjectsClientContract) {
|
||||
this.eventLogClient = new EventLogClient(eventLogService);
|
||||
constructor(
|
||||
eventLogService: IEventLogService,
|
||||
eventLogClient: IEventLogClient | undefined,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
) {
|
||||
this.eventLogClient = new EventLogClient(eventLogService, eventLogClient);
|
||||
this.savedObjectsAdapter = new SavedObjectsAdapter(savedObjectsClient);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public async find(args: FindExecutionLogArgs) {
|
||||
return this.savedObjectsAdapter.find(args);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public async findBulk(args: FindBulkExecutionLogArgs) {
|
||||
return this.savedObjectsAdapter.findBulk(args);
|
||||
}
|
||||
|
||||
public async update(args: UpdateExecutionLogArgs) {
|
||||
const { attributes, spaceId, ruleId, ruleName, ruleType } = args;
|
||||
|
||||
await this.savedObjectsAdapter.update(args);
|
||||
|
||||
// EventLog execution events are immutable, so we just log a status change istead of updating previous
|
||||
if (attributes.status) {
|
||||
this.eventLogClient.logStatusChange({
|
||||
ruleName,
|
||||
ruleType,
|
||||
ruleId,
|
||||
newStatus: attributes.status,
|
||||
spaceId,
|
||||
});
|
||||
}
|
||||
public getLastFailures(args: GetLastFailuresArgs): Promise<IRuleStatusSOAttributes[]> {
|
||||
const { ruleId } = args;
|
||||
return this.eventLogClient.getLastStatusChanges({
|
||||
ruleId,
|
||||
count: MAX_LAST_FAILURES,
|
||||
includeStatuses: [RuleExecutionStatus.failed],
|
||||
});
|
||||
}
|
||||
|
||||
public async delete(id: string) {
|
||||
await this.savedObjectsAdapter.delete(id);
|
||||
public getCurrentStatus(
|
||||
args: GetCurrentStatusArgs
|
||||
): Promise<IRuleStatusSOAttributes | undefined> {
|
||||
return this.savedObjectsAdapter.getCurrentStatus(args);
|
||||
}
|
||||
|
||||
public getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise<GetCurrentStatusBulkResult> {
|
||||
return this.savedObjectsAdapter.getCurrentStatusBulk(args);
|
||||
}
|
||||
|
||||
public async deleteCurrentStatus(ruleId: string): Promise<void> {
|
||||
await this.savedObjectsAdapter.deleteCurrentStatus(ruleId);
|
||||
|
||||
// EventLog execution events are immutable, nothing to do here
|
||||
}
|
||||
|
|
|
@ -7,11 +7,14 @@
|
|||
|
||||
import { SavedObjectsUtils } from '../../../../../../../../src/core/server';
|
||||
import {
|
||||
IEventLogClient,
|
||||
IEventLogger,
|
||||
IEventLogService,
|
||||
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 {
|
||||
RuleExecutionLogAction,
|
||||
|
@ -21,6 +24,8 @@ import {
|
|||
|
||||
const spaceIdToNamespace = SavedObjectsUtils.namespaceStringToId;
|
||||
|
||||
const now = () => new Date().toISOString();
|
||||
|
||||
const statusSeverityDict: Record<RuleExecutionStatus, number> = {
|
||||
[RuleExecutionStatus.succeeded]: 0,
|
||||
[RuleExecutionStatus['going to run']]: 10,
|
||||
|
@ -29,13 +34,6 @@ const statusSeverityDict: Record<RuleExecutionStatus, number> = {
|
|||
[RuleExecutionStatus.failed]: 30,
|
||||
};
|
||||
|
||||
interface FindExecutionLogArgs {
|
||||
ruleIds: string[];
|
||||
spaceId: string;
|
||||
logsCount?: number;
|
||||
statuses?: RuleExecutionStatus[];
|
||||
}
|
||||
|
||||
interface LogExecutionMetricsArgs {
|
||||
ruleId: string;
|
||||
ruleName: string;
|
||||
|
@ -50,24 +48,88 @@ interface EventLogExecutionMetrics {
|
|||
executionGapDuration?: number;
|
||||
}
|
||||
|
||||
interface GetLastStatusChangesArgs {
|
||||
ruleId: string;
|
||||
count: number;
|
||||
includeStatuses?: RuleExecutionStatus[];
|
||||
}
|
||||
|
||||
interface IExecLogEventLogClient {
|
||||
find: (args: FindExecutionLogArgs) => Promise<{}>;
|
||||
getLastStatusChanges(args: GetLastStatusChangesArgs): Promise<IRuleStatusSOAttributes[]>;
|
||||
logStatusChange: (args: LogStatusChangeArgs) => void;
|
||||
logExecutionMetrics: (args: LogExecutionMetricsArgs) => void;
|
||||
}
|
||||
|
||||
export class EventLogClient implements IExecLogEventLogClient {
|
||||
private readonly eventLogClient: IEventLogClient | undefined;
|
||||
private readonly eventLogger: IEventLogger;
|
||||
private sequence = 0;
|
||||
private eventLogger: IEventLogger;
|
||||
|
||||
constructor(eventLogService: IEventLogService) {
|
||||
constructor(eventLogService: IEventLogService, eventLogClient: IEventLogClient | undefined) {
|
||||
this.eventLogClient = eventLogClient;
|
||||
this.eventLogger = eventLogService.getLogger({
|
||||
event: { provider: RULE_EXECUTION_LOG_PROVIDER },
|
||||
});
|
||||
}
|
||||
|
||||
public async find({ ruleIds, spaceId, statuses, logsCount = 1 }: FindExecutionLogArgs) {
|
||||
return {}; // TODO implement
|
||||
public async getLastStatusChanges(
|
||||
args: GetLastStatusChangesArgs
|
||||
): Promise<IRuleStatusSOAttributes[]> {
|
||||
if (!this.eventLogClient) {
|
||||
throw new Error('Querying Event Log from a rule executor is not supported at this moment');
|
||||
}
|
||||
|
||||
const soType = ALERT_SAVED_OBJECT_TYPE;
|
||||
const soIds = [args.ruleId];
|
||||
const count = args.count;
|
||||
const includeStatuses = (args.includeStatuses ?? []).map((status) => `"${status}"`);
|
||||
|
||||
const filterBy: string[] = [
|
||||
`event.provider: ${RULE_EXECUTION_LOG_PROVIDER}`,
|
||||
'event.kind: event',
|
||||
`event.action: ${RuleExecutionLogAction['status-change']}`,
|
||||
includeStatuses.length > 0
|
||||
? `kibana.alert.rule.execution.status:${includeStatuses.join(' ')}`
|
||||
: '',
|
||||
];
|
||||
|
||||
const kqlFilter = filterBy
|
||||
.filter(Boolean)
|
||||
.map((item) => `(${item})`)
|
||||
.join(' and ');
|
||||
|
||||
const findResult = await this.eventLogClient.findEventsBySavedObjectIds(soType, soIds, {
|
||||
page: 1,
|
||||
per_page: count,
|
||||
sort_field: '@timestamp',
|
||||
sort_order: 'desc',
|
||||
filter: kqlFilter,
|
||||
});
|
||||
|
||||
return findResult.data.map((event) => {
|
||||
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 isStatusFailed = status === RuleExecutionStatus.failed;
|
||||
const message = event.message ?? '';
|
||||
|
||||
return {
|
||||
statusDate,
|
||||
status,
|
||||
lastFailureAt: isStatusFailed ? statusDate : undefined,
|
||||
lastFailureMessage: isStatusFailed ? message : undefined,
|
||||
lastSuccessAt: !isStatusFailed ? statusDate : undefined,
|
||||
lastSuccessMessage: !isStatusFailed ? message : undefined,
|
||||
lastLookBackDate: undefined,
|
||||
gap: undefined,
|
||||
bulkCreateTimeDurations: undefined,
|
||||
searchAfterTimeDurations: undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public logExecutionMetrics({
|
||||
|
@ -78,6 +140,7 @@ export class EventLogClient implements IExecLogEventLogClient {
|
|||
spaceId,
|
||||
}: LogExecutionMetricsArgs) {
|
||||
this.eventLogger.logEvent({
|
||||
'@timestamp': now(),
|
||||
rule: {
|
||||
id: ruleId,
|
||||
name: ruleName,
|
||||
|
@ -122,6 +185,8 @@ export class EventLogClient implements IExecLogEventLogClient {
|
|||
spaceId,
|
||||
}: LogStatusChangeArgs) {
|
||||
this.eventLogger.logEvent({
|
||||
'@timestamp': now(),
|
||||
message,
|
||||
rule: {
|
||||
id: ruleId,
|
||||
name: ruleName,
|
||||
|
@ -132,7 +197,6 @@ export class EventLogClient implements IExecLogEventLogClient {
|
|||
action: RuleExecutionLogAction['status-change'],
|
||||
sequence: this.sequence++,
|
||||
},
|
||||
message,
|
||||
kibana: {
|
||||
alert: {
|
||||
rule: {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from '../../../../../../../src/core/server';
|
||||
import { IEventLogService } from '../../../../../event_log/server';
|
||||
import { IEventLogClient, IEventLogService } from '../../../../../event_log/server';
|
||||
import { IRuleStatusSOAttributes } from '../rules/types';
|
||||
import { EventLogAdapter } from './event_log_adapter/event_log_adapter';
|
||||
import { SavedObjectsAdapter } from './saved_objects_adapter/saved_objects_adapter';
|
||||
import {
|
||||
|
@ -15,58 +16,63 @@ import {
|
|||
FindExecutionLogArgs,
|
||||
IRuleExecutionLogClient,
|
||||
LogStatusChangeArgs,
|
||||
UpdateExecutionLogArgs,
|
||||
UnderlyingLogClient,
|
||||
GetLastFailuresArgs,
|
||||
GetCurrentStatusArgs,
|
||||
GetCurrentStatusBulkArgs,
|
||||
GetCurrentStatusBulkResult,
|
||||
} from './types';
|
||||
import { truncateMessage } from './utils/normalization';
|
||||
|
||||
export interface RuleExecutionLogClientArgs {
|
||||
interface ConstructorParams {
|
||||
underlyingClient: UnderlyingLogClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
eventLogService: IEventLogService;
|
||||
underlyingClient: UnderlyingLogClient;
|
||||
eventLogClient?: IEventLogClient;
|
||||
}
|
||||
|
||||
export class RuleExecutionLogClient implements IRuleExecutionLogClient {
|
||||
private client: IRuleExecutionLogClient;
|
||||
|
||||
constructor({
|
||||
savedObjectsClient,
|
||||
eventLogService,
|
||||
underlyingClient,
|
||||
}: RuleExecutionLogClientArgs) {
|
||||
constructor(params: ConstructorParams) {
|
||||
const { underlyingClient, eventLogService, eventLogClient, savedObjectsClient } = params;
|
||||
|
||||
switch (underlyingClient) {
|
||||
case UnderlyingLogClient.savedObjects:
|
||||
this.client = new SavedObjectsAdapter(savedObjectsClient);
|
||||
break;
|
||||
case UnderlyingLogClient.eventLog:
|
||||
this.client = new EventLogAdapter(eventLogService, savedObjectsClient);
|
||||
this.client = new EventLogAdapter(eventLogService, eventLogClient, savedObjectsClient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public find(args: FindExecutionLogArgs) {
|
||||
return this.client.find(args);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public findBulk(args: FindBulkExecutionLogArgs) {
|
||||
return this.client.findBulk(args);
|
||||
}
|
||||
|
||||
public async update(args: UpdateExecutionLogArgs) {
|
||||
const { lastFailureMessage, lastSuccessMessage, ...restAttributes } = args.attributes;
|
||||
|
||||
return this.client.update({
|
||||
...args,
|
||||
attributes: {
|
||||
lastFailureMessage: truncateMessage(lastFailureMessage),
|
||||
lastSuccessMessage: truncateMessage(lastSuccessMessage),
|
||||
...restAttributes,
|
||||
},
|
||||
});
|
||||
public getLastFailures(args: GetLastFailuresArgs): Promise<IRuleStatusSOAttributes[]> {
|
||||
return this.client.getLastFailures(args);
|
||||
}
|
||||
|
||||
public async delete(id: string) {
|
||||
return this.client.delete(id);
|
||||
public getCurrentStatus(
|
||||
args: GetCurrentStatusArgs
|
||||
): Promise<IRuleStatusSOAttributes | undefined> {
|
||||
return this.client.getCurrentStatus(args);
|
||||
}
|
||||
|
||||
public getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise<GetCurrentStatusBulkResult> {
|
||||
return this.client.getCurrentStatusBulk(args);
|
||||
}
|
||||
|
||||
public deleteCurrentStatus(ruleId: string): Promise<void> {
|
||||
return this.client.deleteCurrentStatus(ruleId);
|
||||
}
|
||||
|
||||
public async logExecutionMetrics(args: LogExecutionMetricsArgs) {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { mapValues } from 'lodash';
|
||||
import { SavedObject, SavedObjectReference } from 'src/core/server';
|
||||
import { SavedObjectsClientContract } from '../../../../../../../../src/core/server';
|
||||
import { RuleExecutionStatus } from '../../../../../common/detection_engine/schemas/common/schemas';
|
||||
|
@ -23,7 +24,10 @@ import {
|
|||
IRuleExecutionLogClient,
|
||||
ExecutionMetrics,
|
||||
LogStatusChangeArgs,
|
||||
UpdateExecutionLogArgs,
|
||||
GetLastFailuresArgs,
|
||||
GetCurrentStatusArgs,
|
||||
GetCurrentStatusBulkArgs,
|
||||
GetCurrentStatusBulkResult,
|
||||
} from '../types';
|
||||
import { assertUnreachable } from '../../../../../common';
|
||||
|
||||
|
@ -48,26 +52,52 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient {
|
|||
this.ruleStatusClient = ruleStatusSavedObjectsClientFactory(savedObjectsClient);
|
||||
}
|
||||
|
||||
public find({ ruleId, logsCount = 1 }: FindExecutionLogArgs) {
|
||||
private findRuleStatusSavedObjects(ruleId: string, count: number) {
|
||||
return this.ruleStatusClient.find({
|
||||
perPage: logsCount,
|
||||
perPage: count,
|
||||
sortField: 'statusDate',
|
||||
sortOrder: 'desc',
|
||||
ruleId,
|
||||
});
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public find({ ruleId, logsCount = 1 }: FindExecutionLogArgs) {
|
||||
return this.findRuleStatusSavedObjects(ruleId, logsCount);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
public findBulk({ ruleIds, logsCount = 1 }: FindBulkExecutionLogArgs) {
|
||||
return this.ruleStatusClient.findBulk(ruleIds, logsCount);
|
||||
}
|
||||
|
||||
public async update({ id, attributes, ruleId }: UpdateExecutionLogArgs) {
|
||||
const references: SavedObjectReference[] = [legacyGetRuleReference(ruleId)];
|
||||
await this.ruleStatusClient.update(id, attributes, { references });
|
||||
public async getLastFailures(args: GetLastFailuresArgs): Promise<IRuleStatusSOAttributes[]> {
|
||||
const result = await this.findRuleStatusSavedObjects(args.ruleId, MAX_RULE_STATUSES);
|
||||
|
||||
// The first status is always the current one followed by 5 last failures.
|
||||
// We skip the current status and return only the failures.
|
||||
return result.map((so) => so.attributes).slice(1);
|
||||
}
|
||||
|
||||
public async delete(id: string) {
|
||||
await this.ruleStatusClient.delete(id);
|
||||
public async getCurrentStatus(
|
||||
args: GetCurrentStatusArgs
|
||||
): Promise<IRuleStatusSOAttributes | undefined> {
|
||||
const result = await this.findRuleStatusSavedObjects(args.ruleId, 1);
|
||||
const currentStatusSavedObject = result[0];
|
||||
return currentStatusSavedObject?.attributes;
|
||||
}
|
||||
|
||||
public async getCurrentStatusBulk(
|
||||
args: GetCurrentStatusBulkArgs
|
||||
): Promise<GetCurrentStatusBulkResult> {
|
||||
const { ruleIds } = args;
|
||||
const result = await this.ruleStatusClient.findBulk(ruleIds, 1);
|
||||
return mapValues(result, (attributes = []) => attributes[0]);
|
||||
}
|
||||
|
||||
public async deleteCurrentStatus(ruleId: string): Promise<void> {
|
||||
const statusSavedObjects = await this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES);
|
||||
await Promise.all(statusSavedObjects.map((so) => this.ruleStatusClient.delete(so.id)));
|
||||
}
|
||||
|
||||
public async logExecutionMetrics({ ruleId, metrics }: LogExecutionMetricsArgs) {
|
||||
|
@ -109,16 +139,12 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient {
|
|||
private getOrCreateRuleStatuses = async (
|
||||
ruleId: string
|
||||
): Promise<Array<SavedObject<IRuleStatusSOAttributes>>> => {
|
||||
const ruleStatuses = await this.find({
|
||||
spaceId: '', // spaceId is a required argument but it's not used by savedObjectsClient, any string would work here
|
||||
ruleId,
|
||||
logsCount: MAX_RULE_STATUSES,
|
||||
});
|
||||
if (ruleStatuses.length > 0) {
|
||||
return ruleStatuses;
|
||||
const existingStatuses = await this.findRuleStatusSavedObjects(ruleId, MAX_RULE_STATUSES);
|
||||
if (existingStatuses.length > 0) {
|
||||
return existingStatuses;
|
||||
}
|
||||
const newStatus = await this.createNewRuleStatus(ruleId);
|
||||
|
||||
const newStatus = await this.createNewRuleStatus(ruleId);
|
||||
return [newStatus];
|
||||
};
|
||||
|
||||
|
@ -159,7 +185,7 @@ export class SavedObjectsAdapter implements IRuleExecutionLogClient {
|
|||
|
||||
// drop oldest failures
|
||||
const oldStatuses = [lastStatus, ...ruleStatuses].slice(MAX_RULE_STATUSES);
|
||||
await Promise.all(oldStatuses.map((status) => this.delete(status.id)));
|
||||
await Promise.all(oldStatuses.map((status) => this.ruleStatusClient.delete(status.id)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -15,26 +15,63 @@ export enum UnderlyingLogClient {
|
|||
'eventLog' = 'eventLog',
|
||||
}
|
||||
|
||||
export interface IRuleExecutionLogClient {
|
||||
/** @deprecated */
|
||||
find(args: FindExecutionLogArgs): Promise<Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>>;
|
||||
/** @deprecated */
|
||||
findBulk(args: FindBulkExecutionLogArgs): Promise<FindBulkExecutionLogResponse>;
|
||||
|
||||
getLastFailures(args: GetLastFailuresArgs): Promise<IRuleStatusSOAttributes[]>;
|
||||
getCurrentStatus(args: GetCurrentStatusArgs): Promise<IRuleStatusSOAttributes | undefined>;
|
||||
getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise<GetCurrentStatusBulkResult>;
|
||||
|
||||
deleteCurrentStatus(ruleId: string): Promise<void>;
|
||||
|
||||
logStatusChange(args: LogStatusChangeArgs): Promise<void>;
|
||||
logExecutionMetrics(args: LogExecutionMetricsArgs): Promise<void>;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface FindExecutionLogArgs {
|
||||
ruleId: string;
|
||||
spaceId: string;
|
||||
logsCount?: number;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface FindBulkExecutionLogArgs {
|
||||
ruleIds: string[];
|
||||
spaceId: string;
|
||||
logsCount?: number;
|
||||
}
|
||||
|
||||
export interface ExecutionMetrics {
|
||||
searchDurations?: string[];
|
||||
indexingDurations?: string[];
|
||||
/**
|
||||
* @deprecated lastLookBackDate is logged only by SavedObjectsAdapter and should be removed in the future
|
||||
*/
|
||||
lastLookBackDate?: string;
|
||||
executionGap?: Duration;
|
||||
/** @deprecated */
|
||||
export interface FindBulkExecutionLogResponse {
|
||||
[ruleId: string]: IRuleStatusSOAttributes[] | undefined;
|
||||
}
|
||||
|
||||
export interface GetLastFailuresArgs {
|
||||
ruleId: string;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface GetCurrentStatusArgs {
|
||||
ruleId: string;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface GetCurrentStatusBulkArgs {
|
||||
ruleIds: string[];
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface GetCurrentStatusBulkResult {
|
||||
[ruleId: string]: IRuleStatusSOAttributes;
|
||||
}
|
||||
|
||||
export interface CreateExecutionLogArgs {
|
||||
attributes: IRuleStatusSOAttributes;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface LogStatusChangeArgs {
|
||||
|
@ -50,20 +87,6 @@ export interface LogStatusChangeArgs {
|
|||
metrics?: ExecutionMetrics;
|
||||
}
|
||||
|
||||
export interface UpdateExecutionLogArgs {
|
||||
id: string;
|
||||
attributes: IRuleStatusSOAttributes;
|
||||
ruleId: string;
|
||||
ruleName: string;
|
||||
ruleType: string;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface CreateExecutionLogArgs {
|
||||
attributes: IRuleStatusSOAttributes;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export interface LogExecutionMetricsArgs {
|
||||
ruleId: string;
|
||||
ruleName: string;
|
||||
|
@ -72,17 +95,12 @@ export interface LogExecutionMetricsArgs {
|
|||
metrics: ExecutionMetrics;
|
||||
}
|
||||
|
||||
export interface FindBulkExecutionLogResponse {
|
||||
[ruleId: string]: IRuleStatusSOAttributes[] | undefined;
|
||||
}
|
||||
|
||||
export interface IRuleExecutionLogClient {
|
||||
find: (
|
||||
args: FindExecutionLogArgs
|
||||
) => Promise<Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>>;
|
||||
findBulk: (args: FindBulkExecutionLogArgs) => Promise<FindBulkExecutionLogResponse>;
|
||||
update: (args: UpdateExecutionLogArgs) => Promise<void>;
|
||||
delete: (id: string) => Promise<void>;
|
||||
logStatusChange: (args: LogStatusChangeArgs) => Promise<void>;
|
||||
logExecutionMetrics: (args: LogExecutionMetricsArgs) => Promise<void>;
|
||||
export interface ExecutionMetrics {
|
||||
searchDurations?: string[];
|
||||
indexingDurations?: string[];
|
||||
/**
|
||||
* @deprecated lastLookBackDate is logged only by SavedObjectsAdapter and should be removed in the future
|
||||
*/
|
||||
lastLookBackDate?: string;
|
||||
executionGap?: Duration;
|
||||
}
|
||||
|
|
|
@ -67,9 +67,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper =
|
|||
const esClient = scopedClusterClient.asCurrentUser;
|
||||
|
||||
const ruleStatusClient = new RuleExecutionLogClient({
|
||||
underlyingClient: config.ruleExecutionLog.underlyingClient,
|
||||
savedObjectsClient,
|
||||
eventLogService,
|
||||
underlyingClient: config.ruleExecutionLog.underlyingClient,
|
||||
});
|
||||
|
||||
const completeRule = {
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
*/
|
||||
|
||||
import { rulesClientMock } from '../../../../../alerting/server/mocks';
|
||||
import { deleteRules } from './delete_rules';
|
||||
import { SavedObjectsFindResult } from '../../../../../../../src/core/server';
|
||||
import { DeleteRuleOptions, IRuleStatusSOAttributes } from './types';
|
||||
import { ruleExecutionLogClientMock } from '../rule_execution_log/__mocks__/rule_execution_log_client';
|
||||
import { deleteRules } from './delete_rules';
|
||||
import { DeleteRuleOptions } from './types';
|
||||
|
||||
describe('deleteRules', () => {
|
||||
let rulesClient: ReturnType<typeof rulesClientMock.create>;
|
||||
|
@ -21,35 +20,15 @@ describe('deleteRules', () => {
|
|||
});
|
||||
|
||||
it('should delete the rule along with its actions, and statuses', async () => {
|
||||
const ruleStatus: SavedObjectsFindResult<IRuleStatusSOAttributes> = {
|
||||
id: 'statusId',
|
||||
type: '',
|
||||
references: [],
|
||||
attributes: {
|
||||
statusDate: '',
|
||||
lastFailureAt: null,
|
||||
lastFailureMessage: null,
|
||||
lastSuccessAt: null,
|
||||
lastSuccessMessage: null,
|
||||
status: null,
|
||||
lastLookBackDate: null,
|
||||
gap: null,
|
||||
bulkCreateTimeDurations: null,
|
||||
searchAfterTimeDurations: null,
|
||||
},
|
||||
score: 0,
|
||||
};
|
||||
|
||||
const rule: DeleteRuleOptions = {
|
||||
const options: DeleteRuleOptions = {
|
||||
ruleId: 'ruleId',
|
||||
rulesClient,
|
||||
ruleStatusClient,
|
||||
id: 'ruleId',
|
||||
ruleStatuses: [ruleStatus],
|
||||
};
|
||||
|
||||
await deleteRules(rule);
|
||||
await deleteRules(options);
|
||||
|
||||
expect(rulesClient.delete).toHaveBeenCalledWith({ id: rule.id });
|
||||
expect(ruleStatusClient.delete).toHaveBeenCalledWith(ruleStatus.id);
|
||||
expect(rulesClient.delete).toHaveBeenCalledWith({ id: options.ruleId });
|
||||
expect(ruleStatusClient.deleteCurrentStatus).toHaveBeenCalledWith(options.ruleId);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,17 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { DeleteRuleOptions } from './types';
|
||||
|
||||
export const deleteRules = async ({
|
||||
rulesClient,
|
||||
ruleStatusClient,
|
||||
ruleStatuses,
|
||||
id,
|
||||
}: DeleteRuleOptions) => {
|
||||
await rulesClient.delete({ id });
|
||||
await asyncForEach(ruleStatuses, async (obj) => {
|
||||
await ruleStatusClient.delete(obj.id);
|
||||
});
|
||||
export const deleteRules = async ({ ruleId, rulesClient, ruleStatusClient }: DeleteRuleOptions) => {
|
||||
await rulesClient.delete({ id: ruleId });
|
||||
await ruleStatusClient.deleteCurrentStatus(ruleId);
|
||||
};
|
||||
|
|
|
@ -33,25 +33,11 @@ export const enableRule = async ({
|
|||
}: EnableRuleArgs) => {
|
||||
await rulesClient.enable({ id: rule.id });
|
||||
|
||||
const ruleCurrentStatus = await ruleStatusClient.find({
|
||||
logsCount: 1,
|
||||
await ruleStatusClient.logStatusChange({
|
||||
ruleId: rule.id,
|
||||
ruleName: rule.name,
|
||||
ruleType: rule.alertTypeId,
|
||||
spaceId,
|
||||
newStatus: RuleExecutionStatus['going to run'],
|
||||
});
|
||||
|
||||
// set current status for this rule to be 'going to run'
|
||||
if (ruleCurrentStatus && ruleCurrentStatus.length > 0) {
|
||||
const currentStatusToDisable = ruleCurrentStatus[0];
|
||||
await ruleStatusClient.update({
|
||||
id: currentStatusToDisable.id,
|
||||
ruleId: rule.id,
|
||||
ruleName: rule.name,
|
||||
ruleType: rule.alertTypeId,
|
||||
attributes: {
|
||||
...currentStatusToDisable.attributes,
|
||||
status: RuleExecutionStatus['going to run'],
|
||||
},
|
||||
spaceId,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,12 +8,7 @@
|
|||
import { get } from 'lodash/fp';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import {
|
||||
SavedObject,
|
||||
SavedObjectAttributes,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsFindResult,
|
||||
} from 'kibana/server';
|
||||
import { SavedObject, SavedObjectAttributes, SavedObjectsClientContract } from 'kibana/server';
|
||||
import type {
|
||||
MachineLearningJobIdOrUndefined,
|
||||
From,
|
||||
|
@ -207,10 +202,8 @@ export const isAlertType = (
|
|||
: partialAlert.alertTypeId === SIGNALS_ID;
|
||||
};
|
||||
|
||||
export const isRuleStatusSavedObjectType = (
|
||||
obj: unknown
|
||||
): obj is SavedObject<IRuleSavedAttributesSavedObjectAttributes> => {
|
||||
return get('attributes', obj) != null;
|
||||
export const isRuleStatusSavedObjectAttributes = (obj: unknown): obj is IRuleStatusSOAttributes => {
|
||||
return get('status', obj) != null;
|
||||
};
|
||||
|
||||
export interface CreateRulesOptions {
|
||||
|
@ -342,10 +335,9 @@ export interface ReadRuleOptions {
|
|||
}
|
||||
|
||||
export interface DeleteRuleOptions {
|
||||
ruleId: Id;
|
||||
rulesClient: RulesClient;
|
||||
ruleStatusClient: IRuleExecutionLogClient;
|
||||
ruleStatuses: Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>;
|
||||
id: Id;
|
||||
}
|
||||
|
||||
export interface FindRuleOptions {
|
||||
|
|
|
@ -7,13 +7,16 @@
|
|||
|
||||
import { SavedObjectsFindResult } from 'kibana/server';
|
||||
import {
|
||||
LogExecutionMetricsArgs,
|
||||
IRuleExecutionLogClient,
|
||||
LogStatusChangeArgs,
|
||||
LogExecutionMetricsArgs,
|
||||
FindBulkExecutionLogArgs,
|
||||
FindBulkExecutionLogResponse,
|
||||
FindExecutionLogArgs,
|
||||
LogStatusChangeArgs,
|
||||
UpdateExecutionLogArgs,
|
||||
GetLastFailuresArgs,
|
||||
GetCurrentStatusArgs,
|
||||
GetCurrentStatusBulkArgs,
|
||||
GetCurrentStatusBulkResult,
|
||||
} from '../../rule_execution_log';
|
||||
import { IRuleStatusSOAttributes } from '../../rules/types';
|
||||
|
||||
|
@ -21,26 +24,50 @@ export const createWarningsAndErrors = () => {
|
|||
const warningsAndErrorsStore: LogStatusChangeArgs[] = [];
|
||||
|
||||
const previewRuleExecutionLogClient: IRuleExecutionLogClient = {
|
||||
async delete(id: string): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
async find(
|
||||
find(
|
||||
args: FindExecutionLogArgs
|
||||
): Promise<Array<SavedObjectsFindResult<IRuleStatusSOAttributes>>> {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
async findBulk(args: FindBulkExecutionLogArgs): Promise<FindBulkExecutionLogResponse> {
|
||||
|
||||
findBulk(args: FindBulkExecutionLogArgs): Promise<FindBulkExecutionLogResponse> {
|
||||
return Promise.resolve({});
|
||||
},
|
||||
async logStatusChange(args: LogStatusChangeArgs): Promise<void> {
|
||||
|
||||
getLastFailures(args: GetLastFailuresArgs): Promise<IRuleStatusSOAttributes[]> {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
|
||||
getCurrentStatus(args: GetCurrentStatusArgs): Promise<IRuleStatusSOAttributes> {
|
||||
return Promise.resolve({
|
||||
statusDate: new Date().toISOString(),
|
||||
status: null,
|
||||
lastFailureAt: null,
|
||||
lastFailureMessage: null,
|
||||
lastSuccessAt: null,
|
||||
lastSuccessMessage: null,
|
||||
lastLookBackDate: null,
|
||||
gap: null,
|
||||
bulkCreateTimeDurations: null,
|
||||
searchAfterTimeDurations: null,
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentStatusBulk(args: GetCurrentStatusBulkArgs): Promise<GetCurrentStatusBulkResult> {
|
||||
return Promise.resolve({});
|
||||
},
|
||||
|
||||
deleteCurrentStatus(ruleId: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
logStatusChange(args: LogStatusChangeArgs): Promise<void> {
|
||||
warningsAndErrorsStore.push(args);
|
||||
return Promise.resolve(undefined);
|
||||
return Promise.resolve();
|
||||
},
|
||||
async update(args: UpdateExecutionLogArgs): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
async logExecutionMetrics(args: LogExecutionMetricsArgs): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
logExecutionMetrics(args: LogExecutionMetricsArgs): Promise<void> {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -142,12 +142,13 @@ export const signalRulesAlertType = ({
|
|||
const searchAfterSize = Math.min(maxSignals, DEFAULT_SEARCH_AFTER_PAGE_SIZE);
|
||||
let hasError: boolean = false;
|
||||
let result = createSearchAfterReturnType();
|
||||
|
||||
const ruleStatusClient = ruleExecutionLogClientOverride
|
||||
? ruleExecutionLogClientOverride
|
||||
: new RuleExecutionLogClient({
|
||||
eventLogService,
|
||||
savedObjectsClient: services.savedObjectsClient,
|
||||
underlyingClient: config.ruleExecutionLog.underlyingClient,
|
||||
savedObjectsClient: services.savedObjectsClient,
|
||||
eventLogService,
|
||||
});
|
||||
|
||||
const completeRule: CompleteRule<RuleParams> = {
|
||||
|
|
|
@ -36,7 +36,13 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
private readonly appClientFactory: AppClientFactory;
|
||||
|
||||
constructor(private readonly options: ConstructorOptions) {
|
||||
const { config, plugins } = options;
|
||||
|
||||
this.appClientFactory = new AppClientFactory();
|
||||
this.appClientFactory.setup({
|
||||
getSpaceId: plugins.spaces?.spacesService?.getSpaceId,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
public async create(
|
||||
|
@ -44,14 +50,10 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
request: KibanaRequest
|
||||
): Promise<SecuritySolutionApiRequestHandlerContext> {
|
||||
const { options, appClientFactory } = this;
|
||||
const { config, plugins } = options;
|
||||
const { config, core, plugins } = options;
|
||||
const { lists, ruleRegistry, security, spaces } = plugins;
|
||||
|
||||
appClientFactory.setup({
|
||||
getSpaceId: plugins.spaces?.spacesService?.getSpaceId,
|
||||
config,
|
||||
});
|
||||
|
||||
const [, startPlugins] = await core.getStartServices();
|
||||
const frameworkRequest = await buildFrameworkRequest(context, security, request);
|
||||
|
||||
return {
|
||||
|
@ -69,9 +71,10 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
|
||||
getExecutionLogClient: () =>
|
||||
new RuleExecutionLogClient({
|
||||
underlyingClient: config.ruleExecutionLog.underlyingClient,
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
eventLogService: plugins.eventLog,
|
||||
underlyingClient: config.ruleExecutionLog.underlyingClient,
|
||||
eventLogClient: startPlugins.eventLog.getClient(request),
|
||||
}),
|
||||
|
||||
getExceptionListClient: () => {
|
||||
|
|
|
@ -36,6 +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 { 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,
|
||||
|
@ -122,6 +123,7 @@ export const initRoutes = (
|
|||
persistPinnedEventRoute(router, config, security);
|
||||
|
||||
findRulesStatusesRoute(router);
|
||||
findRuleStatusInternalRoute(router);
|
||||
|
||||
// Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals
|
||||
// POST /api/detection_engine/signals/status
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue