mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Implement rule monitoring dashboard (#159875)
**Addresses:** https://github.com/elastic/security-team/issues/6032 ## Summary This PR adds a new `[Elastic Security] Detection rule monitoring` Kibana dashboard and a new `POST /internal/detection_engine/health/_setup` API endpoint. ## Dashboard The dashboard can be helpful for monitoring the health and performance of Security detection rules. Users of the dashboard must have read access to the `.kibana-event-log-*` index. The dashboard is automatically installed into the current Kibana space when a user visits a page in Security Solution - similar to how we install the Fleet package with prebuilt detection rules. <img width="1791" alt="Kibana dashboards page" src="92cb3c75
-39ea-4069-b70f-8f531869edf7"> <img width="1775" alt="Security dashboards page" src="3b27aeb6
-2222-40fd-a453-c204fcee4f31">  ## API endpoint The PR also adds a new endpoint for setting up anything related to monitoring rules and the health of the Detection Engine. If you call the endpoint, it will install the new dashboard to the Default Kibana space: ``` POST /internal/detection_engine/health/_setup ``` In order to install the dashboard to a different Kibana space, you will need to call it like that: ``` POST /s/<space-id>/internal/detection_engine/health/_setup ``` The user calling the endpoint must have access to Security Solution. No additional privileges are required, because the endpoint installs the dashboard on behalf of the internal user (`kibana_system`). ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] https://github.com/elastic/security-docs/issues/3478 - [ ] [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
This commit is contained in:
parent
03392ddc83
commit
8fcf47553b
22 changed files with 615 additions and 29 deletions
|
@ -28,6 +28,13 @@ export const GET_SPACE_HEALTH_URL = `${INTERNAL_URL}/health/_space` as const;
|
|||
*/
|
||||
export const GET_RULE_HEALTH_URL = `${INTERNAL_URL}/health/_rule` as const;
|
||||
|
||||
/**
|
||||
* Similar to the "setup" command of beats, this endpoint installs resources
|
||||
* (dashboards, data views, etc) related to rule monitoring and Detection Engine health,
|
||||
* and can do any other setup work.
|
||||
*/
|
||||
export const SETUP_HEALTH_URL = `${INTERNAL_URL}/health/_setup` as const;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Rule execution logs API
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ import { TourContextProvider } from '../../common/components/guided_onboarding_t
|
|||
|
||||
import { useUrlState } from '../../common/hooks/use_url_state';
|
||||
import { useUpdateBrowserTitle } from '../../common/hooks/use_update_browser_title';
|
||||
import { useUpgradeSecurityPackages } from '../../detection_engine/rule_management/logic/use_upgrade_security_packages';
|
||||
import { useUpdateExecutionContext } from '../../common/hooks/use_update_execution_context';
|
||||
import { useUpgradeSecurityPackages } from '../../detection_engine/rule_management/logic/use_upgrade_security_packages';
|
||||
import { useSetupDetectionEngineHealthApi } from '../../detection_engine/rule_monitoring';
|
||||
|
||||
interface HomePageProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -41,12 +42,14 @@ const HomePageComponent: React.FC<HomePageProps> = ({ children, setHeaderActionM
|
|||
useUpdateExecutionContext();
|
||||
|
||||
const { browserFields } = useSourcererDataView(getScopeFromPath(pathname));
|
||||
|
||||
// side effect: this will attempt to upgrade the endpoint package if it is not up to date
|
||||
// this will run when a user navigates to the Security Solution app and when they navigate between
|
||||
// tabs in the app. This is useful for keeping the endpoint package as up to date as possible until
|
||||
// a background task solution can be built on the server side. Once a background task solution is available we
|
||||
// can remove this.
|
||||
useUpgradeSecurityPackages();
|
||||
useSetupDetectionEngineHealthApi();
|
||||
|
||||
return (
|
||||
<SecuritySolutionAppWrapper id="security-solution-app" className="kbnAppWrapper">
|
||||
|
|
|
@ -21,6 +21,8 @@ import type {
|
|||
} from '../api_client_interface';
|
||||
|
||||
export const api: jest.Mocked<IRuleMonitoringApiClient> = {
|
||||
setupDetectionEngineHealthApi: jest.fn<Promise<void>, []>().mockResolvedValue(),
|
||||
|
||||
fetchRuleExecutionEvents: jest
|
||||
.fn<Promise<GetRuleExecutionEventsResponse>, [FetchRuleExecutionEventsArgs]>()
|
||||
.mockResolvedValue({
|
||||
|
|
|
@ -27,6 +27,23 @@ describe('Rule Monitoring API Client', () => {
|
|||
|
||||
const signal = new AbortController().signal;
|
||||
|
||||
describe('setupDetectionEngineHealthApi', () => {
|
||||
const responseMock = {};
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockClear();
|
||||
fetchMock.mockResolvedValue(responseMock);
|
||||
});
|
||||
|
||||
it('calls API with correct parameters', async () => {
|
||||
await api.setupDetectionEngineHealthApi();
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith('/internal/detection_engine/health/_setup', {
|
||||
method: 'POST',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchRuleExecutionEvents', () => {
|
||||
const responseMock: GetRuleExecutionEventsResponse = {
|
||||
events: [],
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
import {
|
||||
getRuleExecutionEventsUrl,
|
||||
getRuleExecutionResultsUrl,
|
||||
SETUP_HEALTH_URL,
|
||||
} from '../../../../common/detection_engine/rule_monitoring';
|
||||
|
||||
import type {
|
||||
|
@ -25,6 +26,12 @@ import type {
|
|||
} from './api_client_interface';
|
||||
|
||||
export const api: IRuleMonitoringApiClient = {
|
||||
setupDetectionEngineHealthApi: async (): Promise<void> => {
|
||||
await http().fetch(SETUP_HEALTH_URL, {
|
||||
method: 'POST',
|
||||
});
|
||||
},
|
||||
|
||||
fetchRuleExecutionEvents: (
|
||||
args: FetchRuleExecutionEventsArgs
|
||||
): Promise<GetRuleExecutionEventsResponse> => {
|
||||
|
|
|
@ -16,6 +16,12 @@ import type {
|
|||
} from '../../../../common/detection_engine/rule_monitoring';
|
||||
|
||||
export interface IRuleMonitoringApiClient {
|
||||
/**
|
||||
* Installs resources (dashboards, data views, etc) related to rule monitoring
|
||||
* and Detection Engine health, and can do any other setup work.
|
||||
*/
|
||||
setupDetectionEngineHealthApi(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Fetches plain rule execution events (status changes, metrics, generic events) from Event Log.
|
||||
* @throws An error if response is not OK.
|
||||
|
@ -33,7 +39,14 @@ export interface IRuleMonitoringApiClient {
|
|||
): Promise<GetRuleExecutionResultsResponse>;
|
||||
}
|
||||
|
||||
export interface FetchRuleExecutionEventsArgs {
|
||||
export interface RuleMonitoringApiCallArgs {
|
||||
/**
|
||||
* Optional signal for cancelling the request.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface FetchRuleExecutionEventsArgs extends RuleMonitoringApiCallArgs {
|
||||
/**
|
||||
* Saved Object ID of the rule (`rule.id`, not static `rule.rule_id`).
|
||||
*/
|
||||
|
@ -63,14 +76,9 @@ export interface FetchRuleExecutionEventsArgs {
|
|||
* Number of results to fetch per page.
|
||||
*/
|
||||
perPage?: number;
|
||||
|
||||
/**
|
||||
* Optional signal for cancelling the request.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface FetchRuleExecutionResultsArgs {
|
||||
export interface FetchRuleExecutionResultsArgs extends RuleMonitoringApiCallArgs {
|
||||
/**
|
||||
* Saved Object ID of the rule (`rule.id`, not static `rule.rule_id`).
|
||||
*/
|
||||
|
@ -116,9 +124,4 @@ export interface FetchRuleExecutionResultsArgs {
|
|||
* Number of results to fetch per page.
|
||||
*/
|
||||
perPage?: number;
|
||||
|
||||
/**
|
||||
* Optional signal for cancelling the request.
|
||||
*/
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
|
|
@ -12,4 +12,5 @@ export * from './components/basic/indicators/execution_status_indicator';
|
|||
export * from './components/execution_events_table/execution_events_table';
|
||||
export * from './components/execution_results_table/use_execution_results';
|
||||
|
||||
export * from './logic/detection_engine_health/use_setup_detection_engine_health_api';
|
||||
export * from './logic/execution_settings/use_execution_settings';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { UseMutationOptions } from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { SETUP_HEALTH_URL } from '../../../../../common/detection_engine/rule_monitoring';
|
||||
import { api } from '../../api';
|
||||
|
||||
export const SETUP_DETECTION_ENGINE_HEALTH_API_MUTATION_KEY = ['POST', SETUP_HEALTH_URL];
|
||||
|
||||
export const useSetupDetectionEngineHealthApi = (options?: UseMutationOptions<void, Error>) => {
|
||||
const { mutate: setupDetectionEngineHealthApi } = useMutation(
|
||||
() => api.setupDetectionEngineHealthApi(),
|
||||
{
|
||||
...options,
|
||||
mutationKey: SETUP_DETECTION_ENGINE_HEALTH_API_MUTATION_KEY,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setupDetectionEngineHealthApi();
|
||||
}, [setupDetectionEngineHealthApi]);
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { buildSiemResponse } from '../../../../routes/utils';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../../../types';
|
||||
|
||||
import { SETUP_HEALTH_URL } from '../../../../../../../common/detection_engine/rule_monitoring';
|
||||
|
||||
/**
|
||||
* Similar to the "setup" command of beats, this endpoint installs resources
|
||||
* (dashboards, data views, etc) related to rule monitoring and Detection Engine health,
|
||||
* and can do any other setup work.
|
||||
*/
|
||||
export const setupHealthRoute = (router: SecuritySolutionPluginRouter) => {
|
||||
router.post(
|
||||
{
|
||||
path: SETUP_HEALTH_URL,
|
||||
validate: {},
|
||||
options: {
|
||||
tags: ['access:securitySolution'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
|
||||
try {
|
||||
const ctx = await context.resolve(['securitySolution']);
|
||||
const healthClient = ctx.securitySolution.getDetectionEngineHealthClient();
|
||||
|
||||
await healthClient.installAssetsForMonitoringHealth();
|
||||
|
||||
return response.ok({ body: {} });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import type { SecuritySolutionPluginRouter } from '../../../../types';
|
|||
import { getClusterHealthRoute } from './detection_engine_health/get_cluster_health/get_cluster_health_route';
|
||||
import { getRuleHealthRoute } from './detection_engine_health/get_rule_health/get_rule_health_route';
|
||||
import { getSpaceHealthRoute } from './detection_engine_health/get_space_health/get_space_health_route';
|
||||
import { setupHealthRoute } from './detection_engine_health/setup/setup_health_route';
|
||||
import { getRuleExecutionEventsRoute } from './rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route';
|
||||
import { getRuleExecutionResultsRoute } from './rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route';
|
||||
|
||||
|
@ -17,6 +18,7 @@ export const registerRuleMonitoringRoutes = (router: SecuritySolutionPluginRoute
|
|||
getClusterHealthRoute(router);
|
||||
getSpaceHealthRoute(router);
|
||||
getRuleHealthRoute(router);
|
||||
setupHealthRoute(router);
|
||||
|
||||
// Rule execution logs API
|
||||
getRuleExecutionEventsRoute(router);
|
||||
|
|
|
@ -16,6 +16,8 @@ import type { IDetectionEngineHealthClient } from '../detection_engine_health_cl
|
|||
type CalculateRuleHealth = IDetectionEngineHealthClient['calculateRuleHealth'];
|
||||
type CalculateSpaceHealth = IDetectionEngineHealthClient['calculateSpaceHealth'];
|
||||
type CalculateClusterHealth = IDetectionEngineHealthClient['calculateClusterHealth'];
|
||||
type InstallAssetsForMonitoringHealth =
|
||||
IDetectionEngineHealthClient['installAssetsForMonitoringHealth'];
|
||||
|
||||
export const detectionEngineHealthClientMock = {
|
||||
create: (): jest.Mocked<IDetectionEngineHealthClient> => ({
|
||||
|
@ -30,5 +32,12 @@ export const detectionEngineHealthClientMock = {
|
|||
calculateClusterHealth: jest
|
||||
.fn<ReturnType<CalculateClusterHealth>, Parameters<CalculateClusterHealth>>()
|
||||
.mockResolvedValue(clusterHealthSnapshotMock.getEmptyClusterHealthSnapshot()),
|
||||
|
||||
installAssetsForMonitoringHealth: jest
|
||||
.fn<
|
||||
ReturnType<InstallAssetsForMonitoringHealth>,
|
||||
Parameters<InstallAssetsForMonitoringHealth>
|
||||
>()
|
||||
.mockResolvedValue(),
|
||||
}),
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"type": "index-pattern",
|
||||
"id": "kibana-event-log-data-view",
|
||||
"managed": true,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"attributes": {
|
||||
"name": ".kibana-event-log-*",
|
||||
"title": ".kibana-event-log-*",
|
||||
"timeFieldName": "@timestamp",
|
||||
"allowNoIndex": true
|
||||
},
|
||||
"references": []
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ISavedObjectsImporter, Logger } from '@kbn/core/server';
|
||||
import { SavedObjectsUtils } from '@kbn/core/server';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import pRetry from 'p-retry';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import sourceRuleMonitoringDashboard from './dashboard_rule_monitoring.json';
|
||||
import sourceKibanaEventLogDataView from './data_view_kibana_event_log.json';
|
||||
import sourceManagedTag from './tag_managed.json';
|
||||
import sourceSecuritySolutionTag from './tag_security_solution.json';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
/**
|
||||
* Installs managed assets for monitoring rules and health of Detection Engine.
|
||||
*/
|
||||
export const installAssetsForRuleMonitoring = async (
|
||||
savedObjectsImporter: ISavedObjectsImporter,
|
||||
logger: Logger,
|
||||
currentSpaceId: string
|
||||
): Promise<void> => {
|
||||
const operation = async (attemptCount: number) => {
|
||||
logger.debug(`Installing assets for rule monitoring (attempt ${attemptCount})...`);
|
||||
|
||||
const assets = getAssetsForRuleMonitoring(currentSpaceId);
|
||||
|
||||
// The assets are marked as "managed: true" at the saved object level, which in the future
|
||||
// should be reflected in the UI for the user. Ticket to track:
|
||||
// https://github.com/elastic/kibana/issues/140364
|
||||
const importResult = await savedObjectsImporter.import({
|
||||
readStream: Readable.from(assets),
|
||||
managed: true,
|
||||
overwrite: true,
|
||||
createNewCopies: false,
|
||||
refresh: false,
|
||||
namespace: spaceIdToNamespace(currentSpaceId),
|
||||
});
|
||||
|
||||
importResult.warnings.forEach((w) => {
|
||||
logger.warn(w.message);
|
||||
});
|
||||
|
||||
if (!importResult.success) {
|
||||
const errors = (importResult.errors ?? []).map(
|
||||
(e) => `Couldn't import "${e.type}:${e.id}": ${JSON.stringify(e.error)}`
|
||||
);
|
||||
|
||||
errors.forEach((e) => {
|
||||
logger.error(e);
|
||||
});
|
||||
|
||||
// This will retry the operation
|
||||
throw new Error(errors.length > 0 ? errors[0] : `Unknown error (attempt ${attemptCount})`);
|
||||
}
|
||||
|
||||
logger.debug('Assets for rule monitoring installed');
|
||||
};
|
||||
|
||||
await pRetry(operation, { retries: MAX_RETRIES });
|
||||
};
|
||||
|
||||
const getAssetsForRuleMonitoring = (currentSpaceId: string) => {
|
||||
const withSpaceId = appendSpaceId(currentSpaceId);
|
||||
|
||||
const assetRuleMonitoringDashboard = cloneDeep(sourceRuleMonitoringDashboard);
|
||||
const assetKibanaEventLogDataView = cloneDeep(sourceKibanaEventLogDataView);
|
||||
const assetManagedTag = cloneDeep(sourceManagedTag);
|
||||
const assetSecuritySolutionTag = cloneDeep(sourceSecuritySolutionTag);
|
||||
|
||||
// Update ids of the assets to include the current space id
|
||||
assetRuleMonitoringDashboard.id = withSpaceId('security-detection-rule-monitoring');
|
||||
assetManagedTag.id = withSpaceId('fleet-managed');
|
||||
assetSecuritySolutionTag.id = withSpaceId('security-solution');
|
||||
|
||||
// Update saved object references of the dashboard accordingly
|
||||
assetRuleMonitoringDashboard.references = assetRuleMonitoringDashboard.references.map(
|
||||
(reference) => {
|
||||
if (reference.id === 'fleet-managed-<spaceId>') {
|
||||
return { ...reference, id: assetManagedTag.id };
|
||||
}
|
||||
if (reference.id === 'security-solution-<spaceId>') {
|
||||
return { ...reference, id: assetSecuritySolutionTag.id };
|
||||
}
|
||||
|
||||
return reference;
|
||||
}
|
||||
);
|
||||
|
||||
return [
|
||||
assetManagedTag,
|
||||
assetSecuritySolutionTag,
|
||||
assetKibanaEventLogDataView,
|
||||
assetRuleMonitoringDashboard,
|
||||
];
|
||||
};
|
||||
|
||||
const appendSpaceId = (spaceId: string) => (str: string) => `${str}-${spaceId}`;
|
||||
|
||||
const spaceIdToNamespace = SavedObjectsUtils.namespaceStringToId;
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"type": "tag",
|
||||
"id": "fleet-managed-<spaceId>",
|
||||
"managed": true,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"attributes": { "name": "Managed", "description": "", "color": "#FFFFFF" },
|
||||
"references": []
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"type": "tag",
|
||||
"id": "security-solution-<spaceId>",
|
||||
"managed": true,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.0.0",
|
||||
"attributes": { "name": "Security Solution", "description": "", "color": "#D36086" },
|
||||
"references": []
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { ISavedObjectsImporter, Logger } from '@kbn/core/server';
|
||||
import { withSecuritySpan } from '../../../../../utils/with_security_span';
|
||||
import type { ExtMeta } from '../utils/console_logging';
|
||||
|
||||
|
@ -21,10 +21,12 @@ import type {
|
|||
import type { IEventLogHealthClient } from './event_log/event_log_health_client';
|
||||
import type { IRuleObjectsHealthClient } from './rule_objects/rule_objects_health_client';
|
||||
import type { IDetectionEngineHealthClient } from './detection_engine_health_client_interface';
|
||||
import { installAssetsForRuleMonitoring } from './assets/install_assets_for_rule_monitoring';
|
||||
|
||||
export const createDetectionEngineHealthClient = (
|
||||
ruleObjectsHealthClient: IRuleObjectsHealthClient,
|
||||
eventLogHealthClient: IEventLogHealthClient,
|
||||
savedObjectsImporter: ISavedObjectsImporter,
|
||||
logger: Logger,
|
||||
currentSpaceId: string
|
||||
): IDetectionEngineHealthClient => {
|
||||
|
@ -50,9 +52,10 @@ export const createDetectionEngineHealthClient = (
|
|||
} catch (e) {
|
||||
const logMessage = 'Error calculating rule health';
|
||||
const logReason = e instanceof Error ? e.message : String(e);
|
||||
const logSuffix = `[rule id ${ruleId}]`;
|
||||
const logSuffix = `[rule id ${ruleId}][space id ${currentSpaceId}]`;
|
||||
const logMeta: ExtMeta = {
|
||||
rule: { id: ruleId },
|
||||
kibana: { spaceId: currentSpaceId },
|
||||
};
|
||||
|
||||
logger.error(`${logMessage}: ${logReason} ${logSuffix}`, logMeta);
|
||||
|
@ -112,11 +115,36 @@ export const createDetectionEngineHealthClient = (
|
|||
} catch (e) {
|
||||
const logMessage = 'Error calculating cluster health';
|
||||
const logReason = e instanceof Error ? e.message : String(e);
|
||||
const logSuffix = `[space id ${currentSpaceId}]`;
|
||||
const logMeta: ExtMeta = {
|
||||
kibana: { spaceId: currentSpaceId },
|
||||
};
|
||||
|
||||
logger.error(`${logMessage}: ${logReason}`);
|
||||
logger.error(`${logMessage}: ${logReason} ${logSuffix}`, logMeta);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
installAssetsForMonitoringHealth: (): Promise<void> => {
|
||||
return withSecuritySpan(
|
||||
'IDetectionEngineHealthClient.installAssetsForMonitoringHealth',
|
||||
async () => {
|
||||
try {
|
||||
await installAssetsForRuleMonitoring(savedObjectsImporter, logger, currentSpaceId);
|
||||
} catch (e) {
|
||||
const logMessage = 'Error installing assets for monitoring Detection Engine health';
|
||||
const logReason = e instanceof Error ? e.message : String(e);
|
||||
const logSuffix = `[space id ${currentSpaceId}]`;
|
||||
const logMeta: ExtMeta = {
|
||||
kibana: { spaceId: currentSpaceId },
|
||||
};
|
||||
|
||||
logger.error(`${logMessage}: ${logReason} ${logSuffix}`, logMeta);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -32,4 +32,9 @@ export interface IDetectionEngineHealthClient {
|
|||
* Calculates health stats for the whole cluster.
|
||||
*/
|
||||
calculateClusterHealth(args: ClusterHealthParameters): Promise<ClusterHealthSnapshot>;
|
||||
|
||||
/**
|
||||
* Installs assets for monitoring Detection Engine health, such as dashboards and data views.
|
||||
*/
|
||||
installAssetsForMonitoringHealth(): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { SavedObjectsClient } from '@kbn/core/server';
|
||||
import { invariant } from '../../../../../common/utils/invariant';
|
||||
import type { ConfigType } from '../../../../config';
|
||||
import { withSecuritySpan } from '../../../../utils/with_security_span';
|
||||
import type {
|
||||
SecuritySolutionPluginCoreSetupDependencies,
|
||||
SecuritySolutionPluginCoreStartDependencies,
|
||||
SecuritySolutionPluginSetupDependencies,
|
||||
SecuritySolutionPluginStartDependencies,
|
||||
} from '../../../../plugin_contract';
|
||||
|
||||
import type { IDetectionEngineHealthClient } from './detection_engine_health/detection_engine_health_client_interface';
|
||||
|
@ -36,24 +39,53 @@ import type {
|
|||
|
||||
export const createRuleMonitoringService = (
|
||||
config: ConfigType,
|
||||
logger: Logger,
|
||||
core: SecuritySolutionPluginCoreSetupDependencies,
|
||||
plugins: SecuritySolutionPluginSetupDependencies
|
||||
logger: Logger
|
||||
): IRuleMonitoringService => {
|
||||
let coreSetup: SecuritySolutionPluginCoreSetupDependencies | null = null;
|
||||
let pluginsSetup: SecuritySolutionPluginSetupDependencies | null = null;
|
||||
let coreStart: SecuritySolutionPluginCoreStartDependencies | null = null;
|
||||
|
||||
return {
|
||||
registerEventLogProvider: () => {
|
||||
setup: (
|
||||
core: SecuritySolutionPluginCoreSetupDependencies,
|
||||
plugins: SecuritySolutionPluginSetupDependencies
|
||||
): void => {
|
||||
coreSetup = core;
|
||||
pluginsSetup = plugins;
|
||||
|
||||
registerEventLogProvider(plugins.eventLog);
|
||||
},
|
||||
|
||||
start: (
|
||||
core: SecuritySolutionPluginCoreStartDependencies,
|
||||
plugins: SecuritySolutionPluginStartDependencies
|
||||
): void => {
|
||||
coreStart = core;
|
||||
},
|
||||
|
||||
createDetectionEngineHealthClient: (
|
||||
params: DetectionEngineHealthClientParams
|
||||
): IDetectionEngineHealthClient => {
|
||||
invariant(coreStart, 'Dependencies of RuleMonitoringService are not initialized');
|
||||
|
||||
const { rulesClient, eventLogClient, currentSpaceId } = params;
|
||||
const { savedObjects } = coreStart;
|
||||
|
||||
const ruleObjectsHealthClient = createRuleObjectsHealthClient(rulesClient);
|
||||
const eventLogHealthClient = createEventLogHealthClient(eventLogClient);
|
||||
|
||||
// Create an importer that can import saved objects on behalf of the internal Kibana user.
|
||||
// This is important because we want to let users with access to Security Solution
|
||||
// to be able to install our internal assets like rule monitoring dashboard without
|
||||
// the need to configure the additional `Saved Objects Management: All` privilege.
|
||||
const savedObjectsRepository = savedObjects.createInternalRepository();
|
||||
const savedObjectsClient = new SavedObjectsClient(savedObjectsRepository);
|
||||
const savedObjectsImporter = savedObjects.createImporter(savedObjectsClient);
|
||||
|
||||
return createDetectionEngineHealthClient(
|
||||
ruleObjectsHealthClient,
|
||||
eventLogHealthClient,
|
||||
savedObjectsImporter,
|
||||
logger,
|
||||
currentSpaceId
|
||||
);
|
||||
|
@ -75,6 +107,8 @@ export const createRuleMonitoringService = (
|
|||
async () => {
|
||||
const { savedObjectsClient, context, ruleMonitoringService, ruleResultService } = params;
|
||||
|
||||
invariant(coreSetup, 'Dependencies of RuleMonitoringService are not initialized');
|
||||
invariant(pluginsSetup, 'Dependencies of RuleMonitoringService are not initialized');
|
||||
invariant(ruleMonitoringService, 'ruleMonitoringService required for detection rules');
|
||||
invariant(ruleResultService, 'ruleResultService required for detection rules');
|
||||
|
||||
|
@ -83,11 +117,11 @@ export const createRuleMonitoringService = (
|
|||
const ruleExecutionSettings = await fetchRuleExecutionSettings(
|
||||
config,
|
||||
childLogger,
|
||||
core,
|
||||
coreSetup,
|
||||
savedObjectsClient
|
||||
);
|
||||
|
||||
const eventLogWriter = createEventLogWriter(plugins.eventLog);
|
||||
const eventLogWriter = createEventLogWriter(pluginsSetup.eventLog);
|
||||
|
||||
return createRuleExecutionLogClientForExecutors(
|
||||
ruleExecutionSettings,
|
||||
|
|
|
@ -13,6 +13,13 @@ import type {
|
|||
RulesClientApi,
|
||||
} from '@kbn/alerting-plugin/server/types';
|
||||
|
||||
import type {
|
||||
SecuritySolutionPluginCoreSetupDependencies,
|
||||
SecuritySolutionPluginCoreStartDependencies,
|
||||
SecuritySolutionPluginSetupDependencies,
|
||||
SecuritySolutionPluginStartDependencies,
|
||||
} from '../../../../plugin_contract';
|
||||
|
||||
import type { IDetectionEngineHealthClient } from './detection_engine_health/detection_engine_health_client_interface';
|
||||
import type { IRuleExecutionLogForRoutes } from './rule_execution_log/client_for_routes/client_interface';
|
||||
import type {
|
||||
|
@ -21,7 +28,15 @@ import type {
|
|||
} from './rule_execution_log/client_for_executors/client_interface';
|
||||
|
||||
export interface IRuleMonitoringService {
|
||||
registerEventLogProvider(): void;
|
||||
setup(
|
||||
core: SecuritySolutionPluginCoreSetupDependencies,
|
||||
plugins: SecuritySolutionPluginSetupDependencies
|
||||
): void;
|
||||
|
||||
start(
|
||||
core: SecuritySolutionPluginCoreStartDependencies,
|
||||
plugins: SecuritySolutionPluginStartDependencies
|
||||
): void;
|
||||
|
||||
createDetectionEngineHealthClient(
|
||||
params: DetectionEngineHealthClientParams
|
||||
|
@ -37,7 +52,6 @@ export interface IRuleMonitoringService {
|
|||
}
|
||||
|
||||
export interface DetectionEngineHealthClientParams {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
rulesClient: RulesClientApi;
|
||||
eventLogClient: IEventLogClient;
|
||||
currentSpaceId: string;
|
||||
|
|
|
@ -55,6 +55,7 @@ import { TelemetryReceiver } from './lib/telemetry/receiver';
|
|||
import { licenseService } from './lib/license';
|
||||
import { PolicyWatcher } from './endpoint/lib/policy/license_watch';
|
||||
import previewPolicy from './lib/detection_engine/routes/index/preview_policy.json';
|
||||
import type { IRuleMonitoringService } from './lib/detection_engine/rule_monitoring';
|
||||
import { createRuleMonitoringService } from './lib/detection_engine/rule_monitoring';
|
||||
import { EndpointMetadataService } from './endpoint/services/metadata';
|
||||
import type {
|
||||
|
@ -104,6 +105,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
private readonly appClientFactory: AppClientFactory;
|
||||
private readonly appFeatures: AppFeatures;
|
||||
|
||||
private readonly ruleMonitoringService: IRuleMonitoringService;
|
||||
private readonly endpointAppContextService = new EndpointAppContextService();
|
||||
private readonly telemetryReceiver: ITelemetryReceiver;
|
||||
private readonly telemetryEventsSender: ITelemetryEventsSender;
|
||||
|
@ -126,6 +128,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
this.appClientFactory = new AppClientFactory();
|
||||
this.appFeatures = new AppFeatures(this.logger, this.config.experimentalFeatures);
|
||||
|
||||
this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger);
|
||||
this.telemetryEventsSender = new TelemetryEventsSender(this.logger);
|
||||
this.telemetryReceiver = new TelemetryReceiver(this.logger);
|
||||
|
||||
|
@ -154,8 +157,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
initUiSettings(core.uiSettings, experimentalFeatures);
|
||||
appFeatures.init(plugins.features);
|
||||
|
||||
const ruleMonitoringService = createRuleMonitoringService(config, logger, core, plugins);
|
||||
ruleMonitoringService.registerEventLogProvider();
|
||||
this.ruleMonitoringService.setup(core, plugins);
|
||||
|
||||
const requestContextFactory = new RequestContextFactory({
|
||||
config,
|
||||
|
@ -163,7 +165,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
core,
|
||||
plugins,
|
||||
endpointAppContextService: this.endpointAppContextService,
|
||||
ruleMonitoringService,
|
||||
ruleMonitoringService: this.ruleMonitoringService,
|
||||
kibanaVersion: pluginContext.env.packageInfo.version,
|
||||
kibanaBranch: pluginContext.env.packageInfo.branch,
|
||||
});
|
||||
|
@ -233,7 +235,8 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
config: this.config,
|
||||
publicBaseUrl: core.http.basePath.publicBaseUrl,
|
||||
ruleDataClient,
|
||||
ruleExecutionLoggerFactory: ruleMonitoringService.createRuleExecutionLogClientForExecutors,
|
||||
ruleExecutionLoggerFactory:
|
||||
this.ruleMonitoringService.createRuleExecutionLogClientForExecutors,
|
||||
version: pluginContext.env.packageInfo.version,
|
||||
};
|
||||
|
||||
|
@ -397,6 +400,8 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
): SecuritySolutionPluginStart {
|
||||
const { config, logger } = this;
|
||||
|
||||
this.ruleMonitoringService.start(core, plugins);
|
||||
|
||||
const savedObjectsClient = new SavedObjectsClient(core.savedObjects.createInternalRepository());
|
||||
const registerIngestCallback = plugins.fleet?.registerExternalCallback;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
|
|
@ -105,7 +105,6 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
|
||||
getDetectionEngineHealthClient: memoize(() =>
|
||||
ruleMonitoringService.createDetectionEngineHealthClient({
|
||||
savedObjectsClient: coreContext.savedObjects.client,
|
||||
rulesClient: startPlugins.alerting.getRulesClientWithRequest(request),
|
||||
eventLogClient: startPlugins.eventLog.getClient(request),
|
||||
currentSpaceId: getSpaceId(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue