From 34a040e977723291ae692dcc8de077e6a5859ee6 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Wed, 12 Jul 2023 14:59:13 +0200 Subject: [PATCH 01/46] Risk engine API start --- .../security_solution/common/constants.ts | 6 ++ .../public/entity_analytics/api/api.ts | 67 ++++++++++++- .../hooks/use_init_risk_engine_mutation.ts | 28 ++++++ .../api/hooks/use_risk_engine_status.ts | 28 ++++++ .../components/risk_score_enable_section.tsx | 3 + .../server/lib/risk_engine/routes/index.ts | 4 + .../routes/risk_engine_disable_route.ts | 48 +++++++++ .../routes/risk_engine_enable_route.ts | 48 +++++++++ .../routes/risk_engine_init_route.ts | 48 +++++++++ .../routes/risk_engine_status_route.ts | 48 +++++++++ .../risk_engine/schema/risk_score_apis.yml | 97 ++++++++++++++++++- .../server/lib/risk_engine/types.ts | 29 ++++++ .../security_solution/server/routes/index.ts | 12 ++- 13 files changed, 462 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index a130dcbefc879..60f6e597306b1 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -253,6 +253,12 @@ export const RISK_SCORE_CREATE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/store export const RISK_SCORE_DELETE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/delete`; export const RISK_SCORE_PREVIEW_URL = `${INTERNAL_RISK_SCORE_URL}/preview`; +export const RISK_ENGINE_URL = `${INTERNAL_RISK_SCORE_URL}/engine`; +export const RISK_ENGINE_STATUS_URL = `${RISK_ENGINE_URL}/status`; +export const RISK_ENGINE_INIT_URL = `${RISK_ENGINE_URL}/init`; +export const RISK_ENGINE_ENABLE_URL = `${RISK_ENGINE_URL}/enable`; +export const RISK_ENGINE_DISABLE_URL = `${RISK_ENGINE_URL}/disable`; + /** * Public Risk Score routes */ diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index f7af9b79dd0e2..aa32082e2dd31 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -5,10 +5,22 @@ * 2.0. */ -import { RISK_SCORE_PREVIEW_URL } from '../../../common/constants'; +import { + RISK_ENGINE_STATUS_URL, + RISK_SCORE_PREVIEW_URL, + RISK_ENGINE_ENABLE_URL, + RISK_ENGINE_DISABLE_URL, + RISK_ENGINE_INIT_URL, +} from '../../../common/constants'; import { KibanaServices } from '../../common/lib/kibana'; -import type { CalculateScoresResponse } from '../../../server/lib/risk_engine/types'; +import type { + CalculateScoresResponse, + GetRiskEngineEnableResponse, + GetRiskEngineStatusResponse, + InitRiskEngineResponse, + GetRiskEngineDisableResponse, +} from '../../../server/lib/risk_engine/types'; import type { RiskScorePreviewRequestSchema } from '../../../common/risk_engine/risk_score_preview/request_schema'; /** @@ -27,3 +39,54 @@ export const fetchRiskScorePreview = async ({ signal, }); }; + +/** + * Fetches risks engine status + */ +export const fetchRiskEngineStatus = async ({ + signal, +}: { + signal?: AbortSignal; +}): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_STATUS_URL, { + method: 'GET', + signal, + }); +}; + +/** + * Init risk score engine + */ +export const initRiskEngine = async (): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_INIT_URL, { + method: 'POST', + }); +}; + +/** + * Enable risk score engine + */ +export const enableRiskEngine = async ({ + signal, +}: { + signal?: AbortSignal; +}): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_ENABLE_URL, { + method: 'POST', + signal, + }); +}; + +/** + * Disable risk score engine + */ +export const disableRiskEngine = async ({ + signal, +}: { + signal?: AbortSignal; +}): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_DISABLE_URL, { + method: 'POST', + signal, + }); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts new file mode 100644 index 0000000000000..89945fd625df5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts @@ -0,0 +1,28 @@ +/* + * 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 { initRiskEngine } from '../api'; +import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; + +export const CREATE_RULE_MUTATION_KEY = ['POST', 'INIT_RISK_ENGINE']; + +export const useInitRiskEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + + return useMutation<{}>(() => initRiskEngine(), { + ...options, + mutationKey: CREATE_RULE_MUTATION_KEY, + onSettled: (...args) => { + invalidateRiskEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts new file mode 100644 index 0000000000000..0de892d916d37 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts @@ -0,0 +1,28 @@ +/* + * 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 { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import { fetchRiskEngineStatus } from '../api'; + +const FETCH_RISK_ENGINE_STATUS = ['GET', 'FETCH_RISK_ENGINE_STATUS']; + +export const useInvalidateRiskEngineStatussQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries(FETCH_RISK_ENGINE_STATUS, { + refetchType: 'active', + }); + }, [queryClient]); +}; + +export const useRiskEningeStatus = () => { + return useQuery(FETCH_RISK_ENGINE_STATUS, async ({ signal }) => { + const response = await fetchRiskEngineStatus({ signal }); + return response; + }); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 704c409388609..2f11d36b7bdab 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -22,6 +22,7 @@ import { RISKY_USERS_DOC_LINK, } from '../../../common/constants'; import * as i18n from '../translations'; +import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; const docsLinks = [ { @@ -42,6 +43,8 @@ const MIN_WIDTH_TO_PREVENT_LABEL_FROM_MOVING = '50px'; export const RiskScoreEnableSection = () => { const [checked, setChecked] = useState(false); + const { data: riskEngineStatus, isLoading, isError } = useRiskEningeStatus(); + console.log('riskEngineStatus', riskEngineStatus); return ( <> diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts index 2d76d490948d2..1c37efc508f05 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/index.ts @@ -6,3 +6,7 @@ */ export { riskScorePreviewRoute } from './risk_score_preview_route'; +export { riskEngineInitRoute } from './risk_engine_init_route'; +export { riskEngineEnableRoute } from './risk_engine_enable_route'; +export { riskEngineDisableRoute } from './risk_engine_disable_route'; +export { riskEngineStatusRoute } from './risk_engine_status_route'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts new file mode 100644 index 0000000000000..1e66d9df13fb8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { RISK_ENGINE_DISABLE_URL } from '../../../../common/constants'; + +import type { SecuritySolutionPluginRouter } from '../../../types'; + +import { riskScoreService } from '../risk_score_service'; + +export const riskEngineDisableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { + router.post( + { + path: RISK_ENGINE_DISABLE_URL, + validate: {}, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + const siemClient = (await context.securitySolution).getAppClient(); + const riskScore = riskScoreService({ + esClient, + logger, + }); + + try { + return response.ok({ body: { isOK: 'ok' } }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts new file mode 100644 index 0000000000000..8c13430c46c55 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { RISK_ENGINE_ENABLE_URL } from '../../../../common/constants'; + +import type { SecuritySolutionPluginRouter } from '../../../types'; + +import { riskScoreService } from '../risk_score_service'; + +export const riskEngineEnableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { + router.post( + { + path: RISK_ENGINE_ENABLE_URL, + validate: {}, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + const siemClient = (await context.securitySolution).getAppClient(); + const riskScore = riskScoreService({ + esClient, + logger, + }); + + try { + return response.ok({ body: { isOK: 'ok' } }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts new file mode 100644 index 0000000000000..eb5892085e371 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { RISK_ENGINE_INIT_URL } from '../../../../common/constants'; + +import type { SecuritySolutionPluginRouter } from '../../../types'; + +import { riskScoreService } from '../risk_score_service'; + +export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { + router.post( + { + path: RISK_ENGINE_INIT_URL, + validate: {}, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + const siemClient = (await context.securitySolution).getAppClient(); + const riskScore = riskScoreService({ + esClient, + logger, + }); + + try { + return response.ok({ body: { isOK: 'ok' } }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts new file mode 100644 index 0000000000000..3ac2435a8db93 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { RISK_ENGINE_STATUS_URL } from '../../../../common/constants'; + +import type { SecuritySolutionPluginRouter } from '../../../types'; + +import { riskScoreService } from '../risk_score_service'; + +export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { + router.get( + { + path: RISK_ENGINE_STATUS_URL, + validate: {}, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + const siemClient = (await context.securitySolution).getAppClient(); + const riskScore = riskScoreService({ + esClient, + logger, + }); + + try { + return response.ok({ body: { isOK: 'ok' } }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml index 8cba2d151126a..6c3fb428779d4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml @@ -44,6 +44,55 @@ paths: $ref: '#/components/schemas/RiskScoresPreviewResponse' '400': description: Invalid request + /status: + get: + summary: Get the status of the Risk Engine + description: Returns the status of the legacy transforms risk engine as well as the new risk engine + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineStatusResponse' + /init: + get: + summary: Initialize the Risk Engine + description: Initializes the Risk Engine by creating the necessary indices and mappings, stopped old transforms, and starting the new risk engine + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineInitResponse' + /enable: + post: + summary: Enable the Risk Engine + requestBody: + content: + application/json: {} + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineEnableResponse' + /disable: + post: + summary: Disable the Risk Engine + requestBody: + content: + application/json: {} + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/RiskEngineDisableResponse' + components: schemas: @@ -153,7 +202,34 @@ components: description: A list of risk scores items: $ref: '#/components/schemas/RiskScore' - + RiskEngineStatusResponse: + type: object + properties: + legacy_transform_engine_status: + $ref: '#/components/schemas/RiskEngineStatus' + risk_engine_status: + $ref: '#/components/schemas/RiskEngineStatus' + RiskEngineInitResponse: + type: object + properties: + success: + type: boolean + steps: + type: array + items: + $ref: '#/components/schemas/RiskEngineInitStep' + RiskEngineEnableResponse: + type: object + properties: + success: + type: boolean + RiskEngineDisableResponse: + type: object + properties: + success: + type: boolean + + AfterKeys: type: object properties: @@ -326,3 +402,22 @@ components: - type: 'global_identifier' host: 0.5 user: 0.1 + RiskEngineStatus: + type: string + enum: + - 'NOT_INSTALLED' + - 'DISABLED' + - 'ENABLED' + RiskEngineInitStep: + type: object + required: + - type + - success + properties: + type: + type: string + success: + type: boolean + error: + type: string + \ No newline at end of file diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index a01ba5a9c24e0..7a024e883b96f 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -57,6 +57,35 @@ export interface CalculateScoresResponse { }; } +export enum RiskEngineStatus { + NOT_INSTALLED = 'NOT_INSTALLED', + DISABLED = 'DISABLED', + ENABLED = 'ENABLED', +} + +export interface GetRiskEngineStatusResponse { + legacy_transform_engine_status: RiskEngineStatus; + risk_engine_status: RiskEngineStatus; +} + +export interface InitStep { + type: string; + success: boolean; + error?: string; +} +export interface InitRiskEngineResponse { + success: boolean; + steps: InitStep[]; +} + +export interface GetRiskEngineEnableResponse { + success: boolean; +} + +export interface GetRiskEngineDisableResponse { + success: boolean; +} + export interface SimpleRiskInput { id: string; index: string; diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 63bb97a1cadcc..5ab5c91b9396e 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -74,7 +74,13 @@ import { registerManageExceptionsRoutes } from '../lib/exceptions/api/register_r import { registerDashboardsRoutes } from '../lib/dashboards/routes'; import { registerTagsRoutes } from '../lib/tags/routes'; import { setAlertTagsRoute } from '../lib/detection_engine/routes/signals/set_alert_tags_route'; -import { riskScorePreviewRoute } from '../lib/risk_engine/routes'; +import { + riskScorePreviewRoute, + riskEngineDisableRoute, + riskEngineInitRoute, + riskEngineEnableRoute, + riskEngineStatusRoute, +} from '../lib/risk_engine/routes'; import { riskScoreCalculationRoute } from '../lib/risk_engine/routes/risk_score_calculation_route'; export const initRoutes = ( @@ -177,5 +183,9 @@ export const initRoutes = ( if (config.experimentalFeatures.riskScoringRoutesEnabled) { riskScorePreviewRoute(router, logger); riskScoreCalculationRoute(router, logger); + riskEngineInitRoute(router, logger); + riskEngineEnableRoute(router, logger); + riskEngineStatusRoute(router, logger); + riskEngineDisableRoute(router, logger); } }; From 62b04b6088626731dd8f9513c8aec7cb525aa177 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 24 Jul 2023 19:55:39 +0200 Subject: [PATCH 02/46] Add saved objects and status --- .../components/risk_score_enable_section.tsx | 7 +- .../risk_engine/risk_engine_data_client.ts | 121 +++++++++++++++++- .../routes/risk_engine_init_route.ts | 14 +- .../routes/risk_engine_status_route.ts | 23 ++-- .../lib/risk_engine/saved_object/index.ts | 8 ++ .../risk_engine_configuration_type.ts | 28 ++++ .../risk_engine/schema/risk_score_apis.yml | 2 +- .../security_solution/server/saved_objects.ts | 2 + 8 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 2f11d36b7bdab..acc3baf2a82d7 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -23,6 +23,7 @@ import { } from '../../../common/constants'; import * as i18n from '../translations'; import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; +import { useInitRiskEngineMutation } from '../api/hooks/use_init_risk_engine_mutation'; const docsLinks = [ { @@ -44,6 +45,7 @@ const MIN_WIDTH_TO_PREVENT_LABEL_FROM_MOVING = '50px'; export const RiskScoreEnableSection = () => { const [checked, setChecked] = useState(false); const { data: riskEngineStatus, isLoading, isError } = useRiskEningeStatus(); + const initRiskEngineMutation = useInitRiskEngineMutation(); console.log('riskEngineStatus', riskEngineStatus); return ( @@ -70,7 +72,10 @@ export const RiskScoreEnableSection = () => { setChecked(e.target.checked)} + onChange={(e) => { + setChecked(e.target.checked); + initRiskEngineMutation.mutate(); + }} compressed aria-describedby={'switchRiskModule'} /> diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 17adae352a164..1f31378901fb6 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -6,7 +6,11 @@ */ import type { Metadata } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; +import type { + ClusterPutComponentTemplateRequest, + TransformGetTransformStatsResponse, + TransformGetTransformStatsTransformStats, +} from '@elastic/elasticsearch/lib/api/types'; import { createOrUpdateComponentTemplate, createOrUpdateIlmPolicy, @@ -14,7 +18,8 @@ import { } from '@kbn/alerting-plugin/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; -import type { Logger, ElasticsearchClient } from '@kbn/core/server'; +import type { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; + import { riskScoreFieldMap, getIndexPattern, @@ -26,6 +31,19 @@ import { import { createDataStream } from './utils/create_datastream'; import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer'; import { RiskEngineDataWriter } from './risk_engine_data_writer'; +import { riskEngineConfigurationTypeName } from './saved_object'; +import { RiskEngineStatus } from './types'; +import { + getRiskScorePivotTransformId, + getRiskScoreLatestTransformId, +} from '../../../common/utils/risk_score_modules'; +import { RiskScoreEntity } from '../../../common/search_strategy'; +interface SavedObjectsClients { + savedObjectsClient: SavedObjectsClientContract; +} +interface InitOpts extends SavedObjectsClients { + namespace?: string; +} interface InitializeRiskEngineResourcesOpts { namespace?: string; @@ -37,10 +55,19 @@ interface RiskEngineDataClientOpts { elasticsearchClientPromise: Promise; } +interface Configuration { + enable: boolean; +} + export class RiskEngineDataClient { private writerCache: Map = new Map(); constructor(private readonly options: RiskEngineDataClientOpts) {} + public async init({ namespace, savedObjectsClient }: InitOpts) { + await this.initializeResources({ namespace }); + return this.initSavedObjects({ savedObjectsClient }); + } + public async getWriter({ namespace }: { namespace: string }): Promise { if (this.writerCache.get(namespace)) { return this.writerCache.get(namespace) as Writer; @@ -57,10 +84,100 @@ export class RiskEngineDataClient { index, logger: this.options.logger, }); + this.writerCache.set(namespace, writer); return writer; } + public async getStatus({ + savedObjectsClient, + namespace, + }: SavedObjectsClients & { + namespace: string; + }) { + const riskEgineStatus = await this.getCurrentStatus({ savedObjectsClient }); + const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + return { riskEgineStatus, legacyRiskEgineStatus }; + } + + private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) { + const configuration = await this.getConfiguration({ savedObjectsClient }); + + if (configuration) { + return configuration.enable ? RiskEngineStatus.ENABLED : RiskEngineStatus.DISABLED; + } + + return RiskEngineStatus.NOT_INSTALLED; + } + + private async getLegacyStatus({ namespace }: { namespace: string }) { + const esClient = await this.options.elasticsearchClientPromise; + + const getTransformStatsRequests: Array> = []; + [RiskScoreEntity.host, RiskScoreEntity.user].forEach((entity) => { + getTransformStatsRequests.push( + esClient.transform.getTransformStats({ + transform_id: getRiskScorePivotTransformId(entity, namespace), + }) + ); + getTransformStatsRequests.push( + esClient.transform.getTransformStats({ + transform_id: getRiskScoreLatestTransformId(entity, namespace), + }) + ); + }); + + const result = await Promise.allSettled(getTransformStatsRequests); + + const fulfuletGetTransformStats = result + .filter((r) => r.status === 'fulfilled') + .map((r) => (r as PromiseFulfilledResult).value); + + const transforms = fulfuletGetTransformStats.reduce((acc, val) => { + return [...acc, ...val.transforms]; + }, [] as TransformGetTransformStatsTransformStats[]); + + if (transforms.length === 0) { + return RiskEngineStatus.NOT_INSTALLED; + } + + const notStoppedTransformsExisted = transforms.some((t) => t.state !== 'stopped'); + + if (notStoppedTransformsExisted) { + return RiskEngineStatus.ENABLED; + } + + return RiskEngineStatus.DISABLED; + } + + private async getConfiguration({ + savedObjectsClient, + }: SavedObjectsClients): Promise { + try { + const savedObjectsResponse = await savedObjectsClient.find({ + type: riskEngineConfigurationTypeName, + }); + const configuration = savedObjectsResponse.saved_objects?.[0]?.attributes; + + if (configuration) { + return configuration as Configuration; + } + + return null; + } catch (e) { + this.options.logger.error(`Can't get saved object configuration: ${e.message}`); + return null; + } + } + + private async initSavedObjects({ + savedObjectsClient, + }: { + savedObjectsClient: SavedObjectsClientContract; + }) { + return savedObjectsClient.create(riskEngineConfigurationTypeName, { enable: false }); + } + public async initializeResources({ namespace = DEFAULT_NAMESPACE_STRING, }: InitializeRiskEngineResourcesOpts) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index eb5892085e371..dd828409636bd 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -12,8 +12,6 @@ import { RISK_ENGINE_INIT_URL } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; -import { riskScoreService } from '../risk_score_service'; - export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.post( { @@ -26,15 +24,15 @@ export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger async (context, request, response) => { const siemResponse = buildSiemResponse(response); const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const siemClient = (await context.securitySolution).getAppClient(); - const riskScore = riskScoreService({ - esClient, - logger, - }); + const riskEgineClient = securitySolution.getRiskEngineDataClient(); try { - return response.ok({ body: { isOK: 'ok' } }); + const result = await riskEgineClient.init({ + savedObjectsClient: soClient, + }); + return response.ok({ body: { result } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts index 3ac2435a8db93..77b67d413c3db 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -12,8 +12,6 @@ import { RISK_ENGINE_STATUS_URL } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; -import { riskScoreService } from '../risk_score_service'; - export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( { @@ -25,16 +23,23 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const esClient = (await context.core).elasticsearch.client.asCurrentUser; + + const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const siemClient = (await context.securitySolution).getAppClient(); - const riskScore = riskScoreService({ - esClient, - logger, - }); + const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const spaceId = securitySolution.getSpaceId(); try { - return response.ok({ body: { isOK: 'ok' } }); + const result = await riskEgineClient.getStatus({ + savedObjectsClient: soClient, + namespace: spaceId, + }); + return response.ok({ + body: { + risk_engine_status: result.riskEgineStatus, + legacy_risk_engine_status: result.legacyRiskEgineStatus, + }, + }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts new file mode 100644 index 0000000000000..da4681008403e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './risk_engine_configuration_type'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts new file mode 100644 index 0000000000000..d7547d9a78ac2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts @@ -0,0 +1,28 @@ +/* + * 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 { SECURITY_SOLUTION_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType } from '@kbn/core/server'; + +export const riskEngineConfigurationTypeName = 'risk-engine-configuration'; + +export const riskEngineConfigurationTypeMappings: SavedObjectsType['mappings'] = { + properties: { + enable: { + type: 'boolean', + }, + }, +}; + +export const riskEngineConfigurationType: SavedObjectsType = { + name: riskEngineConfigurationTypeName, + indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, + hidden: false, + namespaceType: 'multiple-isolated', + convertToMultiNamespaceTypeVersion: '8.0.0', + mappings: riskEngineConfigurationTypeMappings, +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml index 6c3fb428779d4..16e09c390fb69 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml @@ -205,7 +205,7 @@ components: RiskEngineStatusResponse: type: object properties: - legacy_transform_engine_status: + legacy_risk_engine_status: $ref: '#/components/schemas/RiskEngineStatus' risk_engine_status: $ref: '#/components/schemas/RiskEngineStatus' diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index bd6c21a4d489a..096b46528e76f 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -13,6 +13,7 @@ import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule import { prebuiltRuleAssetType } from './lib/detection_engine/prebuilt_rules'; import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects'; import { manifestType } from './endpoint/lib/artifacts/saved_object_mappings'; +import { riskEngineConfigurationType } from './lib/risk_engine/saved_object'; const types = [ noteType, @@ -22,6 +23,7 @@ const types = [ timelineType, manifestType, signalsMigrationType, + riskEngineConfigurationType, ]; export const savedObjectTypes = types.map((type) => type.name); From ef9d3e5f74aa06c1906b1f9c7d529cd31b1cc6e8 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 24 Jul 2023 18:28:26 +0000 Subject: [PATCH 03/46] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../server/lib/risk_engine/routes/risk_engine_init_route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index dd828409636bd..7ceec655a3625 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -24,7 +24,7 @@ export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger async (context, request, response) => { const siemResponse = buildSiemResponse(response); const esClient = (await context.core).elasticsearch.client.asCurrentUser; - const securitySolution = await context.securitySolution; + const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; const riskEgineClient = securitySolution.getRiskEngineDataClient(); From 9f2e3d920115fa4146f4cf96740e971b65f92f34 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 24 Jul 2023 18:50:14 +0000 Subject: [PATCH 04/46] [CI] Auto-commit changed files from 'node scripts/check_mappings_update --fix' --- .../kbn-check-mappings-update-cli/current_mappings.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index cb78e060d6edc..1e3dadb1f3ce6 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2935,6 +2935,13 @@ } } }, + "risk-engine-configuration": { + "properties": { + "enable": { + "type": "boolean" + } + } + }, "infrastructure-ui-source": { "dynamic": false, "properties": {} From 347f4ff9287290de400d96ad8407931b860bf0ce Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 27 Jul 2023 16:03:05 +0200 Subject: [PATCH 05/46] Error handling --- .../common/risk_engine/types.ts | 14 ++ .../public/entity_analytics/api/api.ts | 14 +- .../hooks/use_disable_risk_engine_mutation.ts | 35 +++ .../hooks/use_enable_risk_engine_mutation.ts | 34 +++ .../hooks/use_init_risk_engine_mutation.ts | 6 +- .../components/risk_score_enable_section.tsx | 207 +++++++++++++++--- .../public/entity_analytics/translations.ts | 85 +++++++ .../risk_engine/risk_engine_data_client.ts | 147 ++++++++++++- .../routes/risk_engine_disable_route.ts | 16 +- .../routes/risk_engine_enable_route.ts | 15 +- .../routes/risk_engine_init_route.ts | 23 +- .../server/lib/risk_engine/types.ts | 32 ++- 12 files changed, 550 insertions(+), 78 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts diff --git a/x-pack/plugins/security_solution/common/risk_engine/types.ts b/x-pack/plugins/security_solution/common/risk_engine/types.ts index 85911bbd3136d..3f82f367d1c2a 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/types.ts @@ -9,3 +9,17 @@ export enum RiskScoreEntity { host = 'host', user = 'user', } + +export enum RiskEngineStatus { + NOT_INSTALLED = 'NOT_INSTALLED', + DISABLED = 'DISABLED', + ENABLED = 'ENABLED', +} + +export interface InitRiskEngineResult { + leggacyRiskEngineDisabled: boolean; + riskEngineResourcesInstalled: boolean; + riskEngineConfigurationCreated: boolean; + riskEngineEnabled: boolean; + errors: string[]; +} diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index aa32082e2dd31..4026f46a7d797 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -66,27 +66,17 @@ export const initRiskEngine = async (): Promise => { /** * Enable risk score engine */ -export const enableRiskEngine = async ({ - signal, -}: { - signal?: AbortSignal; -}): Promise => { +export const enableRiskEngine = async (): Promise => { return KibanaServices.get().http.fetch(RISK_ENGINE_ENABLE_URL, { method: 'POST', - signal, }); }; /** * Disable risk score engine */ -export const disableRiskEngine = async ({ - signal, -}: { - signal?: AbortSignal; -}): Promise => { +export const disableRiskEngine = async (): Promise => { return KibanaServices.get().http.fetch(RISK_ENGINE_DISABLE_URL, { method: 'POST', - signal, }); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts new file mode 100644 index 0000000000000..231e9da39473c --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { disableRiskEngine } from '../api'; +import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import type { + GetRiskEngineDisableResponse, + EnableDisableRiskEngineResponse, +} from '../../../../server/lib/risk_engine/types'; + +export const DISABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'DISABLE_RISK_ENGINE']; + +export const useDisableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + + return useMutation( + () => disableRiskEngine(), + { + ...options, + mutationKey: DISABLE_RISK_ENGINE_MUTATION_KEY, + onSettled: (...args) => { + invalidateRiskEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts new file mode 100644 index 0000000000000..980aadc274e92 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts @@ -0,0 +1,34 @@ +/* + * 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 { enableRiskEngine } from '../api'; +import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import type { + GetRiskEngineEnableResponse, + EnableDisableRiskEngineResponse, +} from '../../../../server/lib/risk_engine/types'; +export const ENABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'ENABLE_RISK_ENGINE']; + +export const useEnableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + + return useMutation( + () => enableRiskEngine(), + { + ...options, + mutationKey: ENABLE_RISK_ENGINE_MUTATION_KEY, + onSettled: (...args) => { + invalidateRiskEngineStatusQuery(); + + if (options?.onSettled) { + options.onSettled(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts index 89945fd625df5..4b84d54b7f7b3 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts @@ -8,13 +8,17 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import { initRiskEngine } from '../api'; import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import type { + InitRiskEngineResponse, + InitRiskEngineError, +} from '../../../../server/lib/risk_engine/types'; export const CREATE_RULE_MUTATION_KEY = ['POST', 'INIT_RISK_ENGINE']; export const useInitRiskEngineMutation = (options?: UseMutationOptions<{}>) => { const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); - return useMutation<{}>(() => initRiskEngine(), { + return useMutation(() => initRiskEngine(), { ...options, mutationKey: CREATE_RULE_MUTATION_KEY, onSettled: (...args) => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index acc3baf2a82d7..c47f4f4338be7 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -15,6 +15,18 @@ import { EuiSpacer, EuiSwitch, EuiTitle, + EuiLoadingSpinner, + EuiBadge, + EuiButtonEmpty, + EuiButton, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, + EuiCallOut, + EuiAccordion, } from '@elastic/eui'; import { DETECTION_ENTITY_DASHBOARD, @@ -24,6 +36,9 @@ import { import * as i18n from '../translations'; import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; import { useInitRiskEngineMutation } from '../api/hooks/use_init_risk_engine_mutation'; +import { useEnableRiskEngineMutation } from '../api/hooks/use_enable_risk_engine_mutation'; +import { useDisableRiskEngineMutation } from '../api/hooks/use_disable_risk_engine_mutation'; +import { RiskEngineStatus } from '../../../common/risk_engine/types'; const docsLinks = [ { @@ -42,46 +57,186 @@ const docsLinks = [ const MIN_WIDTH_TO_PREVENT_LABEL_FROM_MOVING = '50px'; +const RiskScoreErrorPanel = ({ errors }: { errors: string[] }) => ( + <> + + +

{i18n.ERROR_PANEL_MESSAGE}

+ + + <> + {errors.map((error) => ( +
+ {error} + +
+ ))} + +
+
+ +); + export const RiskScoreEnableSection = () => { - const [checked, setChecked] = useState(false); - const { data: riskEngineStatus, isLoading, isError } = useRiskEningeStatus(); - const initRiskEngineMutation = useInitRiskEngineMutation(); - console.log('riskEngineStatus', riskEngineStatus); + const [isModalVisible, setIsModalVisible] = useState(false); + const { data: riskEnginesStatus } = useRiskEningeStatus(); + const initRiskEngineMutation = useInitRiskEngineMutation({ + onSettled: () => { + setIsModalVisible(false); + }, + }); + + const enableRiskEngineMutation = useEnableRiskEngineMutation(); + const disableRiskEngineMutation = useDisableRiskEngineMutation(); + + const currentRiskEngineStatus = riskEnginesStatus?.risk_engine_status; + + const closeModal = () => setIsModalVisible(false); + const showModal = () => setIsModalVisible(true); + + const isLoading = + initRiskEngineMutation.isLoading || + enableRiskEngineMutation.isLoading || + disableRiskEngineMutation.isLoading; + + const isUpdateAvailable = + riskEnginesStatus?.legacy_risk_engine_status === RiskEngineStatus.ENABLED; + + const onSwitchClick = () => { + if (!currentRiskEngineStatus || isLoading) { + return; + } + if (currentRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED) { + initRiskEngineMutation.mutate(); + } else if (currentRiskEngineStatus === RiskEngineStatus.ENABLED) { + disableRiskEngineMutation.mutate(); + } else if (currentRiskEngineStatus === RiskEngineStatus.DISABLED) { + enableRiskEngineMutation.mutate(); + } + }; + + let modal; + + if (isModalVisible) { + modal = ( + + {initRiskEngineMutation.isLoading && ( + + + + {i18n.UPDATING_RISK_ENGINE} + + + )} + {!initRiskEngineMutation.isLoading && ( + <> + + {i18n.UPDATE_RISK_ENGINE_MODAL_TITLE} + + + + +

+ {i18n.UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_1} + {i18n.UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_2} +

+ +

+ {i18n.UPDATE_RISK_ENGINE_MODAL_EXISTING_DATA_1} + {i18n.UPDATE_RISK_ENGINE_MODAL_EXISTING_DATA_2} +

+
+ +
+ + + + {i18n.UPDATE_RISK_ENGINE_MODAL_BUTTON_NO} + + initRiskEngineMutation.mutate()} fill> + {i18n.UPDATE_RISK_ENGINE_MODAL_BUTTON_YES} + + + + )} +
+ ); + } + + let initRiskEngineErrors: string[] = []; + + if (initRiskEngineMutation.isError) { + const errorBody = initRiskEngineMutation.error.body.message; + if (typeof errorBody.full_error !== 'string') { + initRiskEngineErrors = errorBody.full_error?.errors; + } else { + initRiskEngineErrors = [errorBody.message]; + } + } return ( <> <>

{i18n.RISK_SCORE_MODULE_STATUS}

+ {initRiskEngineMutation.isError && } + {disableRiskEngineMutation.isError && ( + + )} + {enableRiskEngineMutation.isError && ( + + )} + + {modal} - - {i18n.ENTITY_RISK_SCORING} + + - - - {checked ? ( - {i18n.RISK_SCORE_MODULE_STATUS_ON} - ) : ( - {i18n.RISK_SCORE_MODULE_STATUS_OFF} - )} - - - { - setChecked(e.target.checked); - initRiskEngineMutation.mutate(); - }} - compressed - aria-describedby={'switchRiskModule'} - /> - + + {i18n.ENTITY_RISK_SCORING} + {isUpdateAvailable && {i18n.UPDATE_AVAILABLE}} + + {isUpdateAvailable && ( + + + {initRiskEngineMutation.isLoading && } + + + {i18n.START_UPDATE} + + + )} + {!isUpdateAvailable && ( + + {isLoading && } + + {currentRiskEngineStatus === RiskEngineStatus.ENABLED ? ( + {i18n.RISK_SCORE_MODULE_STATUS_ON} + ) : ( + {i18n.RISK_SCORE_MODULE_STATUS_OFF} + )} + + + + + + )} + diff --git a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts index 24169a0590eb4..af55d415f4bcc 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts @@ -130,3 +130,88 @@ export const PREVIEW_QUERY_ERROR_TITLE = i18n.translate( defaultMessage: 'Invalid query', } ); + +export const UPDATE_AVAILABLE = i18n.translate('xpack.securitySolution.riskScore.updateAvailable', { + defaultMessage: 'Update available', +}); + +export const START_UPDATE = i18n.translate('xpack.securitySolution.riskScore.updateAvailable', { + defaultMessage: 'Start update', +}); + +export const UPDATING_RISK_ENGINE = i18n.translate( + 'xpack.securitySolution.riskScore.updatingRiskEngine', + { + defaultMessage: 'Updating risk engine...', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_TITLE = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModa.title', + { + defaultMessage: 'Do you want to update the entity risk engine?', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_1 = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.existingUserHost_1', + { + defaultMessage: 'Existing user and host risk score transforms will be deleted', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_2 = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.existingUserHost_2', + { + defaultMessage: ', they are no longer required.', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_EXISTING_DATA_1 = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.existingData_1', + { + defaultMessage: 'None of your risk score data will be deleted', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_EXISTING_DATA_2 = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.existingData_2', + { + defaultMessage: ', you will need to remove any old risk score data manually.', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_BUTTON_NO = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.buttonNo', + { + defaultMessage: 'No, not yet', + } +); + +export const UPDATE_RISK_ENGINE_MODAL_BUTTON_YES = i18n.translate( + 'xpack.securitySolution.riskScore.updateRiskEngineModal.buttonYes', + { + defaultMessage: 'Yes, update now!', + } +); + +export const ERROR_PANEL_TITLE = i18n.translate( + 'xpack.securitySolution.riskScore.errorPanel.title', + { + defaultMessage: 'Sorry, there was an error', + } +); + +export const ERROR_PANEL_MESSAGE = i18n.translate( + 'xpack.securitySolution.riskScore.errorPanel.message', + { + defaultMessage: 'Something wen’t wrong. Try again later.', + } +); + +export const ERROR_PANEL_ERRORS = i18n.translate( + 'xpack.securitySolution.riskScore.errorPanel.errors', + { + defaultMessage: 'Errors', + } +); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 1f31378901fb6..8a68b815189e9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -32,7 +32,8 @@ import { createDataStream } from './utils/create_datastream'; import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer'; import { RiskEngineDataWriter } from './risk_engine_data_writer'; import { riskEngineConfigurationTypeName } from './saved_object'; -import { RiskEngineStatus } from './types'; +import type { InitRiskEngineResult } from '../../../common/risk_engine/types'; +import { RiskEngineStatus } from '../../../common/risk_engine/types'; import { getRiskScorePivotTransformId, getRiskScoreLatestTransformId, @@ -42,7 +43,7 @@ interface SavedObjectsClients { savedObjectsClient: SavedObjectsClientContract; } interface InitOpts extends SavedObjectsClients { - namespace?: string; + namespace: string; } interface InitializeRiskEngineResourcesOpts { @@ -64,8 +65,46 @@ export class RiskEngineDataClient { constructor(private readonly options: RiskEngineDataClientOpts) {} public async init({ namespace, savedObjectsClient }: InitOpts) { - await this.initializeResources({ namespace }); - return this.initSavedObjects({ savedObjectsClient }); + const result: InitRiskEngineResult = { + leggacyRiskEngineDisabled: false, + riskEngineResourcesInstalled: false, + riskEngineConfigurationCreated: false, + riskEngineEnabled: false, + errors: [] as string[], + }; + + try { + result.leggacyRiskEngineDisabled = await this.disableLegacyRiskEngine({ namespace }); + } catch (e) { + result.leggacyRiskEngineDisabled = false; + result.errors.push(e.message); + } + + try { + await this.initializeResources({ namespace }); + result.riskEngineResourcesInstalled = true; + } catch (e) { + result.errors.push(e.message); + return result; + } + + try { + await this.initSavedObjects({ savedObjectsClient }); + result.riskEngineConfigurationCreated = true; + } catch (e) { + result.errors.push(e.message); + return result; + } + + try { + await this.enableRiskEngine({ savedObjectsClient }); + result.riskEngineEnabled = true; + } catch (e) { + result.errors.push(e.message); + return result; + } + + return result; } public async getWriter({ namespace }: { namespace: string }): Promise { @@ -100,6 +139,85 @@ export class RiskEngineDataClient { return { riskEgineStatus, legacyRiskEgineStatus }; } + public async enableRiskEngine({ savedObjectsClient }: SavedObjectsClients) { + // code to run task + + return this.udpateSavedObjectAttribute({ + savedObjectsClient, + attributes: { + enable: true, + }, + }); + } + + public async disableRiskEngine({ savedObjectsClient }: SavedObjectsClients) { + // code to stop task + + return this.udpateSavedObjectAttribute({ + savedObjectsClient, + attributes: { + enable: false, + }, + }); + } + + private async udpateSavedObjectAttribute({ + savedObjectsClient, + attributes, + }: SavedObjectsClients & { + attributes: { + enable: boolean; + }; + }) { + const savedObjectConfiguration = await this.getConfigurationSavedObject({ + savedObjectsClient, + }); + + if (!savedObjectConfiguration) { + throw new Error('There no saved object configuration for risk engine'); + } + + const result = await savedObjectsClient.update( + riskEngineConfigurationTypeName, + savedObjectConfiguration.id, + attributes, + { + refresh: 'wait_for', + } + ); + + return result; + } + + private async disableLegacyRiskEngine({ namespace }: { namespace: string }) { + const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + + if ( + legacyRiskEgineStatus === RiskEngineStatus.DISABLED || + legacyRiskEgineStatus === RiskEngineStatus.NOT_INSTALLED + ) { + return true; + } + + const esClient = await this.options.elasticsearchClientPromise; + const transforms = await this.getLegacyTransforms({ namespace }); + + const stopTransformRequests = transforms + .filter((t) => t.state !== 'stopped') + .map((t) => + esClient.transform.stopTransform({ + transform_id: t.id, + wait_for_completion: true, + }) + ); + + await Promise.allSettled(stopTransformRequests); + + const newLegacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + + return newLegacyRiskEgineStatus === RiskEngineStatus.DISABLED; + } + private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) { const configuration = await this.getConfiguration({ savedObjectsClient }); @@ -110,7 +228,7 @@ export class RiskEngineDataClient { return RiskEngineStatus.NOT_INSTALLED; } - private async getLegacyStatus({ namespace }: { namespace: string }) { + private async getLegacyTransforms({ namespace }: { namespace: string }) { const esClient = await this.options.elasticsearchClientPromise; const getTransformStatsRequests: Array> = []; @@ -137,6 +255,12 @@ export class RiskEngineDataClient { return [...acc, ...val.transforms]; }, [] as TransformGetTransformStatsTransformStats[]); + return transforms; + } + + private async getLegacyStatus({ namespace }: { namespace: string }) { + const transforms = await this.getLegacyTransforms({ namespace }); + if (transforms.length === 0) { return RiskEngineStatus.NOT_INSTALLED; } @@ -150,14 +274,21 @@ export class RiskEngineDataClient { return RiskEngineStatus.DISABLED; } + private async getConfigurationSavedObject({ savedObjectsClient }: SavedObjectsClients) { + const savedObjectsResponse = await savedObjectsClient.find({ + type: riskEngineConfigurationTypeName, + }); + return savedObjectsResponse.saved_objects?.[0]; + } + private async getConfiguration({ savedObjectsClient, }: SavedObjectsClients): Promise { try { - const savedObjectsResponse = await savedObjectsClient.find({ - type: riskEngineConfigurationTypeName, + const savedObjectConfiguration = await this.getConfigurationSavedObject({ + savedObjectsClient, }); - const configuration = savedObjectsResponse.saved_objects?.[0]?.attributes; + const configuration = savedObjectConfiguration?.attributes; if (configuration) { return configuration as Configuration; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts index 1e66d9df13fb8..107ce7d727ab4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -12,8 +12,6 @@ import { RISK_ENGINE_DISABLE_URL } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; -import { riskScoreService } from '../risk_score_service'; - export const riskEngineDisableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.post( { @@ -25,16 +23,16 @@ export const riskEngineDisableRoute = (router: SecuritySolutionPluginRouter, log }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const esClient = (await context.core).elasticsearch.client.asCurrentUser; + + const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const siemClient = (await context.securitySolution).getAppClient(); - const riskScore = riskScoreService({ - esClient, - logger, - }); + const riskEgineClient = securitySolution.getRiskEngineDataClient(); try { - return response.ok({ body: { isOK: 'ok' } }); + const result = await riskEgineClient.disableRiskEngine({ + savedObjectsClient: soClient, + }); + return response.ok({ body: { result } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index 8c13430c46c55..8a0afd4e69e78 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -12,8 +12,6 @@ import { RISK_ENGINE_ENABLE_URL } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; -import { riskScoreService } from '../risk_score_service'; - export const riskEngineEnableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.post( { @@ -25,16 +23,15 @@ export const riskEngineEnableRoute = (router: SecuritySolutionPluginRouter, logg }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const siemClient = (await context.securitySolution).getAppClient(); - const riskScore = riskScoreService({ - esClient, - logger, - }); + const riskEgineClient = securitySolution.getRiskEngineDataClient(); try { - return response.ok({ body: { isOK: 'ok' } }); + const result = await riskEgineClient.enableRiskEngine({ + savedObjectsClient: soClient, + }); + return response.ok({ body: { result } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index 7ceec655a3625..4cfa3df828a6e 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -23,16 +23,33 @@ export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const esClient = (await context.core).elasticsearch.client.asCurrentUser; const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const spaceId = securitySolution.getSpaceId(); try { - const result = await riskEgineClient.init({ + const initResult = await riskEgineClient.init({ savedObjectsClient: soClient, + namespace: spaceId, }); - return response.ok({ body: { result } }); + + if ( + !initResult.riskEngineEnabled || + !initResult.riskEngineResourcesInstalled || + !initResult.riskEngineConfigurationCreated + ) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + return siemResponse.error({ + statusCode: 400, + body: { + message: initResult.errors.join('\n'), + full_error: initResult, + }, + }); + } + + return response.ok({ body: { result: initResult } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index 7a024e883b96f..90872e56293f6 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -14,7 +14,7 @@ import type { RiskCategories, RiskWeights, } from '../../../common/risk_engine'; - +import type { RiskEngineStatus, InitRiskEngineResult } from '../../../common/risk_engine/types'; export interface CalculateScoresParams { afterKeys: AfterKeys; debug?: boolean; @@ -57,14 +57,8 @@ export interface CalculateScoresResponse { }; } -export enum RiskEngineStatus { - NOT_INSTALLED = 'NOT_INSTALLED', - DISABLED = 'DISABLED', - ENABLED = 'ENABLED', -} - export interface GetRiskEngineStatusResponse { - legacy_transform_engine_status: RiskEngineStatus; + legacy_risk_engine_status: RiskEngineStatus; risk_engine_status: RiskEngineStatus; } @@ -73,9 +67,27 @@ export interface InitStep { success: boolean; error?: string; } + export interface InitRiskEngineResponse { - success: boolean; - steps: InitStep[]; + result: InitRiskEngineResult; +} + +export interface InitRiskEngineError { + body: { + message: { + message: string; + full_error: InitRiskEngineResult | string; + }; + }; +} + +export interface EnableDisableRiskEngineResponse { + body: { + message: { + message: string; + full_error: string; + }; + }; } export interface GetRiskEngineEnableResponse { From ad598032c786baca338d5265124a31686da40989 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 27 Jul 2023 16:53:09 +0200 Subject: [PATCH 06/46] Return who last updated --- .../components/risk_score_enable_section.tsx | 1 + .../risk_engine/risk_engine_data_client.ts | 52 ++++++++++++++----- .../routes/risk_engine_disable_route.ts | 10 +++- .../routes/risk_engine_enable_route.ts | 10 +++- .../routes/risk_engine_init_route.ts | 10 +++- .../routes/risk_engine_status_route.ts | 1 + .../risk_engine_configuration_type.ts | 4 +- .../server/plugin_contract.ts | 2 +- .../security_solution/server/routes/index.ts | 6 +-- 9 files changed, 71 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index c47f4f4338be7..6a329f02bf95f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -231,6 +231,7 @@ export const RiskScoreEnableSection = () => { checked={currentRiskEngineStatus === RiskEngineStatus.ENABLED} onChange={onSwitchClick} compressed + disabled={isLoading} aria-describedby={'switchRiskModule'} /> diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 8a68b815189e9..2f198e51b425b 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -6,6 +6,7 @@ */ import type { Metadata } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import type { ClusterPutComponentTemplateRequest, TransformGetTransformStatsResponse, @@ -44,6 +45,11 @@ interface SavedObjectsClients { } interface InitOpts extends SavedObjectsClients { namespace: string; + user: AuthenticatedUser | null | undefined; +} + +interface UpdateConfigOpts extends SavedObjectsClients { + user: AuthenticatedUser | null | undefined; } interface InitializeRiskEngineResourcesOpts { @@ -58,13 +64,14 @@ interface RiskEngineDataClientOpts { interface Configuration { enable: boolean; + last_updated_by: string; } export class RiskEngineDataClient { private writerCache: Map = new Map(); constructor(private readonly options: RiskEngineDataClientOpts) {} - public async init({ namespace, savedObjectsClient }: InitOpts) { + public async init({ namespace, savedObjectsClient, user }: InitOpts) { const result: InitRiskEngineResult = { leggacyRiskEngineDisabled: false, riskEngineResourcesInstalled: false, @@ -89,7 +96,7 @@ export class RiskEngineDataClient { } try { - await this.initSavedObjects({ savedObjectsClient }); + await this.initSavedObjects({ savedObjectsClient, user }); result.riskEngineConfigurationCreated = true; } catch (e) { result.errors.push(e.message); @@ -97,7 +104,7 @@ export class RiskEngineDataClient { } try { - await this.enableRiskEngine({ savedObjectsClient }); + await this.enableRiskEngine({ savedObjectsClient, user }); result.riskEngineEnabled = true; } catch (e) { result.errors.push(e.message); @@ -136,25 +143,28 @@ export class RiskEngineDataClient { }) { const riskEgineStatus = await this.getCurrentStatus({ savedObjectsClient }); const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); - return { riskEgineStatus, legacyRiskEgineStatus }; + const lastUpdatedBy = await this.getLastUpdatedBy({ savedObjectsClient }); + return { riskEgineStatus, legacyRiskEgineStatus, lastUpdatedBy }; } - public async enableRiskEngine({ savedObjectsClient }: SavedObjectsClients) { + public async enableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to run task return this.udpateSavedObjectAttribute({ savedObjectsClient, + user, attributes: { enable: true, }, }); } - public async disableRiskEngine({ savedObjectsClient }: SavedObjectsClients) { + public async disableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to stop task return this.udpateSavedObjectAttribute({ savedObjectsClient, + user, attributes: { enable: false, }, @@ -164,7 +174,8 @@ export class RiskEngineDataClient { private async udpateSavedObjectAttribute({ savedObjectsClient, attributes, - }: SavedObjectsClients & { + user, + }: UpdateConfigOpts & { attributes: { enable: boolean; }; @@ -180,7 +191,10 @@ export class RiskEngineDataClient { const result = await savedObjectsClient.update( riskEngineConfigurationTypeName, savedObjectConfiguration.id, - attributes, + { + ...attributes, + last_updated_by: user?.username ?? '', + }, { refresh: 'wait_for', } @@ -218,6 +232,16 @@ export class RiskEngineDataClient { return newLegacyRiskEgineStatus === RiskEngineStatus.DISABLED; } + private async getLastUpdatedBy({ savedObjectsClient }: SavedObjectsClients) { + const configuration = await this.getConfiguration({ savedObjectsClient }); + + if (configuration) { + return configuration.last_updated_by; + } + + return ''; + } + private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) { const configuration = await this.getConfiguration({ savedObjectsClient }); @@ -301,12 +325,12 @@ export class RiskEngineDataClient { } } - private async initSavedObjects({ - savedObjectsClient, - }: { - savedObjectsClient: SavedObjectsClientContract; - }) { - return savedObjectsClient.create(riskEngineConfigurationTypeName, { enable: false }); + + private async initSavedObjects({ savedObjectsClient, user }: UpdateConfigOpts) { + return savedObjectsClient.create(riskEngineConfigurationTypeName, { + enable: false, + last_updated_by: user?.username ?? '', + }); } public async initializeResources({ diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts index 107ce7d727ab4..d1cf24bbff1d9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -9,10 +9,14 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { RISK_ENGINE_DISABLE_URL } from '../../../../common/constants'; - +import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; -export const riskEngineDisableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const riskEngineDisableRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'] +) => { router.post( { path: RISK_ENGINE_DISABLE_URL, @@ -27,10 +31,12 @@ export const riskEngineDisableRoute = (router: SecuritySolutionPluginRouter, log const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const user = security?.authc.getCurrentUser(request); try { const result = await riskEgineClient.disableRiskEngine({ savedObjectsClient: soClient, + user, }); return response.ok({ body: { result } }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index 8a0afd4e69e78..95903db95d986 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -9,10 +9,14 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { RISK_ENGINE_ENABLE_URL } from '../../../../common/constants'; - +import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; -export const riskEngineEnableRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const riskEngineEnableRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'] +) => { router.post( { path: RISK_ENGINE_ENABLE_URL, @@ -26,10 +30,12 @@ export const riskEngineEnableRoute = (router: SecuritySolutionPluginRouter, logg const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const user = security?.authc.getCurrentUser(request); try { const result = await riskEgineClient.enableRiskEngine({ savedObjectsClient: soClient, + user, }); return response.ok({ body: { result } }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index 4cfa3df828a6e..8cd0f54049cf8 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -9,10 +9,15 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { RISK_ENGINE_INIT_URL } from '../../../../common/constants'; +import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; -export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const riskEngineInitRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'] +) => { router.post( { path: RISK_ENGINE_INIT_URL, @@ -27,11 +32,13 @@ export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger const soClient = (await context.core).savedObjects.client; const riskEgineClient = securitySolution.getRiskEngineDataClient(); const spaceId = securitySolution.getSpaceId(); + const user = security?.authc.getCurrentUser(request); try { const initResult = await riskEgineClient.init({ savedObjectsClient: soClient, namespace: spaceId, + user, }); if ( @@ -39,7 +46,6 @@ export const riskEngineInitRoute = (router: SecuritySolutionPluginRouter, logger !initResult.riskEngineResourcesInstalled || !initResult.riskEngineConfigurationCreated ) { - await new Promise((resolve) => setTimeout(resolve, 1000)); return siemResponse.error({ statusCode: 400, body: { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts index 77b67d413c3db..18e35bca684b0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -38,6 +38,7 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg body: { risk_engine_status: result.riskEgineStatus, legacy_risk_engine_status: result.legacyRiskEgineStatus, + last_udpated_by: result.lastUpdatedBy, }, }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts index d7547d9a78ac2..1f48e798fdcbc 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts @@ -15,6 +15,9 @@ export const riskEngineConfigurationTypeMappings: SavedObjectsType['mappings'] = enable: { type: 'boolean', }, + last_updated_by: { + type: 'keyword', + }, }, }; @@ -23,6 +26,5 @@ export const riskEngineConfigurationType: SavedObjectsType = { indexPattern: SECURITY_SOLUTION_SAVED_OBJECT_INDEX, hidden: false, namespaceType: 'multiple-isolated', - convertToMultiNamespaceTypeVersion: '8.0.0', mappings: riskEngineConfigurationTypeMappings, }; diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 26dee3fbbb016..3bd8191f9c923 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -64,7 +64,7 @@ export interface SecuritySolutionPluginSetupDependencies { unifiedSearch: UnifiedSearchServerPluginSetup; } -export interface SecuritySolutionPluginStartDependencies { +export interface SetupPluginsSecuritySolutionPluginStartDependencies { alerting: AlertingPluginStart; cases?: CasesStart; cloud: CloudSetup; diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 5ab5c91b9396e..cd88c9ffaca1b 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -183,9 +183,9 @@ export const initRoutes = ( if (config.experimentalFeatures.riskScoringRoutesEnabled) { riskScorePreviewRoute(router, logger); riskScoreCalculationRoute(router, logger); - riskEngineInitRoute(router, logger); - riskEngineEnableRoute(router, logger); + riskEngineInitRoute(router, logger, security); + riskEngineEnableRoute(router, logger, security); riskEngineStatusRoute(router, logger); - riskEngineDisableRoute(router, logger); + riskEngineDisableRoute(router, logger, security); } }; From 8423d5cd90ac0a5abb6969e17aa79247894832d4 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 07:40:27 +0200 Subject: [PATCH 07/46] Add risk_score_update_panel --- .../components/risk_score_update_panel.tsx | 41 +++++++++++++++++++ .../public/entity_analytics/translations.ts | 29 +++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx new file mode 100644 index 0000000000000..1538a73d62114 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx @@ -0,0 +1,41 @@ +/* + * 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 React from 'react'; +import { EuiCallOut, EuiText, EuiButton, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import * as i18n from '../translations'; +import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; +import { RiskEngineStatus } from '../../../common/risk_engine/types'; +import { SecuritySolutionLinkButton } from '../../common/components/links'; +import { SecurityPageName } from '../../../common/constants'; + +export const RiskScoreUpdatePanel = () => { + const { data: riskEnginesStatus } = useRiskEningeStatus(); + const isUpdateAvailable = + riskEnginesStatus?.legacy_risk_engine_status === RiskEngineStatus.ENABLED; + + if (!isUpdateAvailable) { + return null; + } + + return ( + + {i18n.UPDATE_PANEL_MESSAGE} + + + + {i18n.UPDATE_PANEL_GO_TO_MANAGE} + + {i18n.UPDATE_PANEL_GO_TO_DISMISS} + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts index af55d415f4bcc..1dfe6ff421d6a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts @@ -215,3 +215,32 @@ export const ERROR_PANEL_ERRORS = i18n.translate( defaultMessage: 'Errors', } ); + +export const UPDATE_PANEL_TITLE = i18n.translate( + 'xpack.securitySolution.riskScore.updatePanel.title', + { + defaultMessage: 'New entity risk scoring engine available', + } +); + +export const UPDATE_PANEL_MESSAGE = i18n.translate( + 'xpack.securitySolution.riskScore.updatePanel.message', + { + defaultMessage: + 'A new entity risk scoring engine is available. Update now to get the latest features.', + } +); + +export const UPDATE_PANEL_GO_TO_MANAGE = i18n.translate( + 'xpack.securitySolution.riskScore.updatePanel.goToManage', + { + defaultMessage: 'Manage', + } +); + +export const UPDATE_PANEL_GO_TO_DISMISS = i18n.translate( + 'xpack.securitySolution.riskScore.updatePanel.Dismiss', + { + defaultMessage: 'Dismiss', + } +); From fdc0084a5182ae072d2f0fe0a5d4e0711ae4b12d Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 10:46:28 +0200 Subject: [PATCH 08/46] Risk update panel --- .../api/hooks/use_risk_engine_status.ts | 18 +++++- .../components/risk_score_enable_section.tsx | 7 +-- .../components/risk_score_update_panel.tsx | 11 ++-- .../navigation/host_risk_score_tab_body.tsx | 55 ++++++++++++------- .../navigation/user_risk_score_tab_body.tsx | 7 +++ .../overview/pages/entity_analytics.tsx | 5 ++ 6 files changed, 68 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts index 0de892d916d37..afe6d885c7e09 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts @@ -7,7 +7,8 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; import { fetchRiskEngineStatus } from '../api'; - +import { RiskEngineStatus } from '../../../../common/risk_engine/types'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; const FETCH_RISK_ENGINE_STATUS = ['GET', 'FETCH_RISK_ENGINE_STATUS']; export const useInvalidateRiskEngineStatussQuery = () => { @@ -20,9 +21,20 @@ export const useInvalidateRiskEngineStatussQuery = () => { }, [queryClient]); }; -export const useRiskEningeStatus = () => { +export const useRiskEngineStatus = () => { + const isRiskEngineEnabled = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled'); + return useQuery(FETCH_RISK_ENGINE_STATUS, async ({ signal }) => { + if (!isRiskEngineEnabled) { + return null; + } const response = await fetchRiskEngineStatus({ signal }); - return response; + const isUpdateAvailable = + response?.legacy_risk_engine_status === RiskEngineStatus.ENABLED && + response.risk_engine_status === RiskEngineStatus.NOT_INSTALLED; + return { + isUpdateAvailable, + ...response, + }; }); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 6a329f02bf95f..76db64dfadbb9 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -34,7 +34,7 @@ import { RISKY_USERS_DOC_LINK, } from '../../../common/constants'; import * as i18n from '../translations'; -import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; +import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status'; import { useInitRiskEngineMutation } from '../api/hooks/use_init_risk_engine_mutation'; import { useEnableRiskEngineMutation } from '../api/hooks/use_enable_risk_engine_mutation'; import { useDisableRiskEngineMutation } from '../api/hooks/use_disable_risk_engine_mutation'; @@ -79,7 +79,7 @@ const RiskScoreErrorPanel = ({ errors }: { errors: string[] }) => ( export const RiskScoreEnableSection = () => { const [isModalVisible, setIsModalVisible] = useState(false); - const { data: riskEnginesStatus } = useRiskEningeStatus(); + const { data: riskEnginesStatus } = useRiskEngineStatus(); const initRiskEngineMutation = useInitRiskEngineMutation({ onSettled: () => { setIsModalVisible(false); @@ -99,8 +99,7 @@ export const RiskScoreEnableSection = () => { enableRiskEngineMutation.isLoading || disableRiskEngineMutation.isLoading; - const isUpdateAvailable = - riskEnginesStatus?.legacy_risk_engine_status === RiskEngineStatus.ENABLED; + const isUpdateAvailable = riskEnginesStatus?.isUpdateAvailable; const onSwitchClick = () => { if (!currentRiskEngineStatus || isLoading) { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx index 1538a73d62114..80d1d6f31f7d8 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx @@ -6,17 +6,15 @@ */ import React from 'react'; -import { EuiCallOut, EuiText, EuiButton, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiText, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import * as i18n from '../translations'; -import { useRiskEningeStatus } from '../api/hooks/use_risk_engine_status'; -import { RiskEngineStatus } from '../../../common/risk_engine/types'; +import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status'; import { SecuritySolutionLinkButton } from '../../common/components/links'; import { SecurityPageName } from '../../../common/constants'; export const RiskScoreUpdatePanel = () => { - const { data: riskEnginesStatus } = useRiskEningeStatus(); - const isUpdateAvailable = - riskEnginesStatus?.legacy_risk_engine_status === RiskEngineStatus.ENABLED; + const { data: riskEnginesStatus } = useRiskEngineStatus(); + const isUpdateAvailable = riskEnginesStatus?.isUpdateAvailable; if (!isUpdateAvailable) { return null; @@ -34,7 +32,6 @@ export const RiskScoreUpdatePanel = () => { > {i18n.UPDATE_PANEL_GO_TO_MANAGE} - {i18n.UPDATE_PANEL_GO_TO_DISMISS} ); diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx index 889c42406e54b..af44c85786537 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx @@ -6,6 +6,7 @@ */ import React, { useEffect, useMemo, useState } from 'react'; +import { EuiPanel } from '@elastic/eui'; import { noop } from 'lodash/fp'; import { EnableRiskScore } from '../../../components/risk_score/enable_risk_score'; import type { HostsComponentsQueryProps } from './types'; @@ -22,6 +23,8 @@ import { import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy'; import { RiskScoresNoDataDetected } from '../../../components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; +import { useRiskEngineStatus } from '../../../../entity_analytics/api/hooks/use_risk_engine_status'; +import { RiskScoreUpdatePanel } from '../../../../entity_analytics/components/risk_score_update_panel'; const HostRiskScoreTableManage = manageQuery(HostRiskScoreTable); @@ -46,6 +49,8 @@ export const HostRiskScoreQueryTabBody = ({ getHostRiskScoreFilterQuerySelector(state, hostsModel.HostsType.page) ); + const { data: riskScoreEngineStatus } = useRiskEngineStatus(); + const pagination = useMemo( () => ({ cursorStart: activePage * limit, @@ -95,14 +100,20 @@ export const HostRiskScoreQueryTabBody = ({ return <>{'TODO: Add RiskScore Upsell'}; } + if (riskScoreEngineStatus?.isUpdateAvailable) { + return ; + } + if (status.isDisabled || status.isDeprecated) { return ( - + + + ); } @@ -117,21 +128,23 @@ export const HostRiskScoreQueryTabBody = ({ } return ( - + <> + + ); }; diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx index ed204dd0811fb..0c1aa2abb9cd8 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx @@ -24,6 +24,8 @@ import { import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { EMPTY_SEVERITY_COUNT, RiskScoreEntity } from '../../../../../common/search_strategy'; import { RiskScoresNoDataDetected } from '../../../components/risk_score/risk_score_onboarding/risk_score_no_data_detected'; +import { useRiskEngineStatus } from '../../../../entity_analytics/api/hooks/use_risk_engine_status'; +import { RiskScoreUpdatePanel } from '../../../../entity_analytics/components/risk_score_update_panel'; const UserRiskScoreTableManage = manageQuery(UserRiskScoreTable); @@ -36,6 +38,7 @@ export const UserRiskScoreQueryTabBody = ({ startDate: from, type, }: UsersComponentsQueryProps) => { + const { data: riskScoreEngineStatus } = useRiskEngineStatus(); const getUserRiskScoreSelector = useMemo(() => usersSelectors.userRiskScoreSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state: State) => getUserRiskScoreSelector(state) @@ -97,6 +100,10 @@ export const UserRiskScoreQueryTabBody = ({ return <>{'TODO: Add RiskScore Upsell'}; } + if (riskScoreEngineStatus?.isUpdateAvailable) { + return ; + } + if (status.isDisabled || status.isDeprecated) { return ( { const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); @@ -48,6 +49,10 @@ const EntityAnalyticsComponent = () => { ) : ( + + + + From 05f28c5b0aa98cacf7083552719b4b9f11fda1c0 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 11:11:31 +0200 Subject: [PATCH 09/46] Add risk score update panels --- .../components/risk_score/risk_details_tab_body/index.tsx | 8 ++++++++ .../components/entity_analytics/risk_score/index.tsx | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx index 185096a7dde97..b71bc7d21587b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx @@ -32,6 +32,8 @@ import type { UsersComponentsQueryProps } from '../../../users/pages/navigation/ import type { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types'; import { useDashboardHref } from '../../../../common/hooks/use_dashboard_href'; import { RiskScoresNoDataDetected } from '../risk_score_onboarding/risk_score_no_data_detected'; +import { useRiskEngineStatus } from '../../../../entity_analytics/api/hooks/use_risk_engine_status'; +import { RiskScoreUpdatePanel } from '../../../../entity_analytics/components/risk_score_update_panel'; const StyledEuiFlexGroup = styled(EuiFlexGroup)` margin-top: ${({ theme }) => theme.eui.euiSizeL}; @@ -91,6 +93,8 @@ const RiskDetailsTabBodyComponent: React.FC< timerange, }); + const { data: riskScoreEngineStatus } = useRiskEngineStatus(); + const rules = useMemo(() => { const lastRiskItem = data && data.length > 0 ? data[data.length - 1] : null; if (lastRiskItem) { @@ -133,6 +137,10 @@ const RiskDetailsTabBodyComponent: React.FC< return <>{'TODO: Add RiskScore Upsell'}; } + if (riskScoreEngineStatus?.isUpdateAvailable) { + return ; + } + if (status.isDisabled || status.isDeprecated) { return ( { const { deleteQuery, setQuery, from, to } = useGlobalTime(); @@ -125,6 +127,8 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc includeAlertsCount: true, }); + const { data: riskScoreEngineStatus } = useRiskEngineStatus(); + useQueryInspector({ queryId: entity.tableQueryId, loading: isTableLoading, @@ -149,6 +153,10 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc isDeprecated: isDeprecated && !isTableLoading, }; + if (riskScoreEngineStatus?.isUpdateAvailable) { + return null; + } + if (status.isDisabled || status.isDeprecated) { return ( Date: Fri, 28 Jul 2023 11:12:52 +0200 Subject: [PATCH 10/46] Delete old transforms --- .../lib/risk_engine/risk_engine_data_client.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 2f198e51b425b..c5b557103d2bf 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -216,14 +216,12 @@ export class RiskEngineDataClient { const esClient = await this.options.elasticsearchClientPromise; const transforms = await this.getLegacyTransforms({ namespace }); - const stopTransformRequests = transforms - .filter((t) => t.state !== 'stopped') - .map((t) => - esClient.transform.stopTransform({ - transform_id: t.id, - wait_for_completion: true, - }) - ); + const stopTransformRequests = transforms.map((t) => + esClient.transform.deleteTransform({ + transform_id: t.id, + force: true, + }) + ); await Promise.allSettled(stopTransformRequests); From 4ffaa998f64c2eb1068a13e4f6bda64fc7e24946 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 11:35:54 +0200 Subject: [PATCH 11/46] Add mapping for SO --- packages/kbn-check-mappings-update-cli/current_mappings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 1e3dadb1f3ce6..491c262c5c61a 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2939,6 +2939,9 @@ "properties": { "enable": { "type": "boolean" + }, + "last_updated_by": { + "type": "keyword" } } }, From 1b7433101f863e47a310029d1857da21c7d2f781 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 11:44:15 +0200 Subject: [PATCH 12/46] fix name --- .../api/hooks/use_init_risk_engine_mutation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts index 4b84d54b7f7b3..81f6a55d9ac25 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts @@ -13,14 +13,14 @@ import type { InitRiskEngineError, } from '../../../../server/lib/risk_engine/types'; -export const CREATE_RULE_MUTATION_KEY = ['POST', 'INIT_RISK_ENGINE']; +export const INIT_RISK_ENGINE_STATUS_KEY = ['POST', 'INIT_RISK_ENGINE']; export const useInitRiskEngineMutation = (options?: UseMutationOptions<{}>) => { const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); return useMutation(() => initRiskEngine(), { ...options, - mutationKey: CREATE_RULE_MUTATION_KEY, + mutationKey: INIT_RISK_ENGINE_STATUS_KEY, onSettled: (...args) => { invalidateRiskEngineStatusQuery(); From 8518cbbfc7872e9fc788900403f846f019016fde Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 12:11:08 +0200 Subject: [PATCH 13/46] Fix types --- .../routes/risk_engine_disable_route.ts | 4 +-- .../routes/risk_engine_enable_route.ts | 4 +-- .../risk_engine/schema/risk_score_apis.yml | 33 +++++++++++++------ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts index d1cf24bbff1d9..1d5043521eaa7 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -34,11 +34,11 @@ export const riskEngineDisableRoute = ( const user = security?.authc.getCurrentUser(request); try { - const result = await riskEgineClient.disableRiskEngine({ + await riskEgineClient.disableRiskEngine({ savedObjectsClient: soClient, user, }); - return response.ok({ body: { result } }); + return response.ok({ body: { success: true } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index 95903db95d986..fe30f3597c719 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -33,11 +33,11 @@ export const riskEngineEnableRoute = ( const user = security?.authc.getCurrentUser(request); try { - const result = await riskEgineClient.enableRiskEngine({ + await riskEgineClient.enableRiskEngine({ savedObjectsClient: soClient, user, }); - return response.ok({ body: { result } }); + return response.ok({ body: { success: true } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml index 16e09c390fb69..be435fe643ee1 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml @@ -44,7 +44,7 @@ paths: $ref: '#/components/schemas/RiskScoresPreviewResponse' '400': description: Invalid request - /status: + /engine/status: get: summary: Get the status of the Risk Engine description: Returns the status of the legacy transforms risk engine as well as the new risk engine @@ -55,10 +55,11 @@ paths: application/json: schema: $ref: '#/components/schemas/RiskEngineStatusResponse' - /init: - get: + + /engine/init: + posrt: summary: Initialize the Risk Engine - description: Initializes the Risk Engine by creating the necessary indices and mappings, stopped old transforms, and starting the new risk engine + description: Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine responses: '200': description: Successful response @@ -66,7 +67,7 @@ paths: application/json: schema: $ref: '#/components/schemas/RiskEngineInitResponse' - /enable: + /engine/enable: post: summary: Enable the Risk Engine requestBody: @@ -79,7 +80,7 @@ paths: application/json: schema: $ref: '#/components/schemas/RiskEngineEnableResponse' - /disable: + /engine/disable: post: summary: Disable the Risk Engine requestBody: @@ -212,12 +213,24 @@ components: RiskEngineInitResponse: type: object properties: - success: - type: boolean - steps: + errors: type: array items: - $ref: '#/components/schemas/RiskEngineInitStep' + type: string + result: + type: object + properties: + riskEngineEnabled: + type: boolean + riskEngineResourcesInstalled: + type: boolean + riskEngineConfigurationCreated: + type: boolean + leggacyRiskEngineDisabled: + type: boolean + + + RiskEngineEnableResponse: type: object properties: From 27e39c22aedf052b728d977800918ecf15e440e8 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 12:15:43 +0200 Subject: [PATCH 14/46] type --- .../security_solution/public/entity_analytics/translations.ts | 2 +- x-pack/plugins/security_solution/server/plugin_contract.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts index 1dfe6ff421d6a..5afba438cff0e 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts @@ -135,7 +135,7 @@ export const UPDATE_AVAILABLE = i18n.translate('xpack.securitySolution.riskScore defaultMessage: 'Update available', }); -export const START_UPDATE = i18n.translate('xpack.securitySolution.riskScore.updateAvailable', { +export const START_UPDATE = i18n.translate('xpack.securitySolution.riskScore.startUpdate', { defaultMessage: 'Start update', }); diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 3bd8191f9c923..26dee3fbbb016 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -64,7 +64,7 @@ export interface SecuritySolutionPluginSetupDependencies { unifiedSearch: UnifiedSearchServerPluginSetup; } -export interface SetupPluginsSecuritySolutionPluginStartDependencies { +export interface SecuritySolutionPluginStartDependencies { alerting: AlertingPluginStart; cases?: CasesStart; cloud: CloudSetup; From b4c212d7f97601ecb802ba038c12ca3e2e17bfeb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:56:37 +0000 Subject: [PATCH 15/46] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../overview/components/entity_analytics/risk_score/index.tsx | 1 - .../server/lib/risk_engine/routes/risk_engine_enable_route.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index aebb2e96e705f..57536596e085e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -36,7 +36,6 @@ import { getRiskEntityTranslation } from './translations'; import { useKibana } from '../../../../common/lib/kibana'; import { useGlobalFilterQuery } from '../../../../common/hooks/use_global_filter_query'; import { useRiskEngineStatus } from '../../../../entity_analytics/api/hooks/use_risk_engine_status'; -import { RiskScoreUpdatePanel } from '../../../../entity_analytics/components/risk_score_update_panel'; const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskScoreEntity }) => { const { deleteQuery, setQuery, from, to } = useGlobalTime(); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index fe30f3597c719..aa1cc3fb9a0c9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -33,7 +33,7 @@ export const riskEngineEnableRoute = ( const user = security?.authc.getCurrentUser(request); try { - await riskEgineClient.enableRiskEngine({ + await riskEgineClient.enableRiskEngine({ savedObjectsClient: soClient, user, }); From 805d243074fcec3a0ca3dcf13277711203628ccb Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 16:00:28 +0200 Subject: [PATCH 16/46] add tests --- .../risk_engine_data_client.test.ts | 620 ++++++++++++++---- .../risk_engine/risk_engine_data_client.ts | 8 +- .../security_solution/server/plugin.ts | 4 - 3 files changed, 488 insertions(+), 144 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 2512a8c99c0c7..0e6f5543b4b47 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -10,10 +10,89 @@ import { createOrUpdateIlmPolicy, createOrUpdateIndexTemplate, } from '@kbn/alerting-plugin/server'; -import { loggingSystemMock, elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { + loggingSystemMock, + elasticsearchServiceMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; +import { transformsHttpMocks } from '../../../public/management/pages/endpoint_hosts/mocks'; import { RiskEngineDataClient } from './risk_engine_data_client'; import { createDataStream } from './utils/create_datastream'; +const getSavedObjectConfiguration = (attributes = {}) => ({ + page: 1, + per_page: 20, + total: 1, + saved_objects: [ + { + type: 'risk-engine-configuration', + id: 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', + namespaces: ['default'], + attributes: { + enable: false, + last_updated_by: 'elastic', + ...attributes, + }, + references: [], + managed: false, + updated_at: '2023-07-28T09:52:28.768Z', + created_at: '2023-07-28T09:12:26.083Z', + version: 'WzE4MzIsMV0=', + coreMigrationVersion: '8.8.0', + score: 0, + }, + ], +}); + +const transformStats = { + count: 1, + transforms: [ + { + id: 'ml_hostriskscore_pivot_transform_default', + state: 'started', + node: { + id: 'fc9o02bRTi-JPRU1626AaQ', + name: 'macbook.local', + ephemeral_id: 'y-Jx42RvTQi_h7npDlv7sQ', + transport_address: '127.0.0.1:9300', + attributes: {}, + }, + stats: { + pages_processed: 2, + documents_processed: 15, + documents_indexed: 15, + documents_deleted: 0, + trigger_count: 1, + index_time_in_ms: 55, + index_total: 1, + index_failures: 0, + search_time_in_ms: 3, + search_total: 2, + search_failures: 0, + processing_time_in_ms: 0, + processing_total: 2, + delete_time_in_ms: 0, + exponential_avg_checkpoint_duration_ms: 397, + exponential_avg_documents_indexed: 15, + exponential_avg_documents_processed: 15, + }, + checkpointing: { + last: { + checkpoint: 1, + timestamp_millis: 1690546732294, + time_upper_bound_millis: 1690546612294, + }, + changes_last_detected_at: 1690546732292, + last_search_time: 1690546732292, + }, + health: { + status: 'green' as const, + }, + }, + ], +}; + jest.mock('@kbn/alerting-plugin/server', () => ({ createOrUpdateComponentTemplate: jest.fn(), createOrUpdateIlmPolicy: jest.fn(), @@ -28,6 +107,7 @@ describe('RiskEngineDataClient', () => { let riskEngineDataClient: RiskEngineDataClient; let logger: ReturnType; const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const mockSavedObjectClient = savedObjectsClientMock.create(); const totalFieldsLimit = 1000; beforeEach(() => { @@ -93,145 +173,63 @@ describe('RiskEngineDataClient', () => { }, }); - expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith( - expect.objectContaining({ - logger, - esClient, - template: expect.objectContaining({ - name: '.risk-score-mappings', - _meta: { - managed: true, - }, - }), - totalFieldsLimit: 1000, - }) - ); - expect((createOrUpdateComponentTemplate as jest.Mock).mock.lastCall[0].template.template) - .toMatchInlineSnapshot(` - Object { - "mappings": Object { - "dynamic": "strict", - "properties": Object { - "@timestamp": Object { - "type": "date", - }, - "host": Object { - "properties": Object { - "name": Object { - "type": "keyword", - }, - "risk": Object { - "properties": Object { - "calculated_level": Object { - "type": "keyword", - }, - "calculated_score": Object { - "type": "float", - }, - "calculated_score_norm": Object { - "type": "float", - }, - "category_1_score": Object { - "type": "float", - }, - "id_field": Object { - "type": "keyword", - }, - "id_value": Object { - "type": "keyword", - }, - "inputs": Object { - "properties": Object { - "category": Object { - "type": "keyword", - }, - "description": Object { - "type": "keyword", - }, - "id": Object { - "type": "keyword", - }, - "index": Object { - "type": "keyword", - }, - "risk_score": Object { - "type": "float", - }, - "timestamp": Object { - "type": "date", - }, - }, - "type": "object", - }, - "notes": Object { - "type": "keyword", - }, - }, - "type": "object", - }, + expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith({ + logger, + esClient, + template: { + name: '.risk-score-mappings', + _meta: { + managed: true, + }, + template: { + settings: {}, + mappings: { + dynamic: 'strict', + properties: { + '@timestamp': { + type: 'date', }, - }, - "user": Object { - "properties": Object { - "name": Object { - "type": "keyword", - }, - "risk": Object { - "properties": Object { - "calculated_level": Object { - "type": "keyword", - }, - "calculated_score": Object { - "type": "float", - }, - "calculated_score_norm": Object { - "type": "float", - }, - "category_1_score": Object { - "type": "float", - }, - "id_field": Object { - "type": "keyword", - }, - "id_value": Object { - "type": "keyword", - }, - "inputs": Object { - "properties": Object { - "category": Object { - "type": "keyword", - }, - "description": Object { - "type": "keyword", - }, - "id": Object { - "type": "keyword", - }, - "index": Object { - "type": "keyword", - }, - "risk_score": Object { - "type": "float", - }, - "timestamp": Object { - "type": "date", - }, - }, - "type": "object", - }, - "notes": Object { - "type": "keyword", - }, + alertsScore: { + type: 'float', + }, + identifierField: { + type: 'keyword', + }, + identifierValue: { + type: 'keyword', + }, + level: { + type: 'keyword', + }, + otherScore: { + type: 'float', + }, + riskiestInputs: { + properties: { + id: { + type: 'keyword', + }, + index: { + type: 'keyword', + }, + riskScore: { + type: 'float', }, - "type": "object", }, + type: 'nested', + }, + totalScore: { + type: 'float', + }, + totalScoreNormalized: { + type: 'float', }, }, }, }, - "settings": Object {}, - } - `); + }, + totalFieldsLimit, + }); expect(createOrUpdateIndexTemplate).toHaveBeenCalledWith({ logger, @@ -287,14 +285,360 @@ describe('RiskEngineDataClient', () => { describe('initializeResources error', () => { it('should handle errors during initialization', async () => { - const error = new Error('There error'); - (createOrUpdateIlmPolicy as jest.Mock).mockRejectedValue(error); + try { + const error = new Error('There error'); + (createOrUpdateIlmPolicy as jest.Mock).mockRejectedValue(error); - await riskEngineDataClient.initializeResources({ namespace: 'default' }); + await riskEngineDataClient.initializeResources({ namespace: 'default' }); + + expect(logger.error).toHaveBeenCalledWith( + `Error initializing risk engine resources: ${error.message}` + ); + } catch (e) { + // + } + }); + }); + + describe('getStatus', () => { + it('should return initial status', async () => { + const status = await riskEngineDataClient.getStatus({ + namespace: 'default', + savedObjectsClient: mockSavedObjectClient, + }); + expect(status).toEqual({ + riskEgineStatus: 'NOT_INSTALLED', + legacyRiskEgineStatus: 'NOT_INSTALLED', + lastUpdatedBy: '', + }); + }); + + describe('saved object exists and transforms not', () => { + beforeEach(() => { + mockSavedObjectClient.find.mockResolvedValue(getSavedObjectConfiguration()); + }); + + afterEach(() => { + mockSavedObjectClient.find.mockReset(); + }); + + it('should return status with enabled true', async () => { + mockSavedObjectClient.find.mockResolvedValue( + getSavedObjectConfiguration({ + enable: true, + }) + ); + + const status = await riskEngineDataClient.getStatus({ + namespace: 'default', + savedObjectsClient: mockSavedObjectClient, + }); + expect(status).toEqual({ + riskEgineStatus: 'ENABLED', + legacyRiskEgineStatus: 'NOT_INSTALLED', + lastUpdatedBy: 'elastic', + }); + }); + + it('should return status with enabled false', async () => { + mockSavedObjectClient.find.mockResolvedValue(getSavedObjectConfiguration()); + + const status = await riskEngineDataClient.getStatus({ + namespace: 'default', + savedObjectsClient: mockSavedObjectClient, + }); + expect(status).toEqual({ + riskEgineStatus: 'DISABLED', + legacyRiskEgineStatus: 'NOT_INSTALLED', + lastUpdatedBy: 'elastic', + }); + }); + }); + + describe('legacy transforms', () => { + it('should fetch transform stats', async () => { + await riskEngineDataClient.getStatus({ + namespace: 'default', + savedObjectsClient: mockSavedObjectClient, + }); + + expect(esClient.transform.getTransformStats).toHaveBeenCalledTimes(4); + expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(1, { + transform_id: 'ml_hostriskscore_pivot_transform_default', + }); + expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(2, { + transform_id: 'ml_hostriskscore_latest_transform_default', + }); + expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(3, { + transform_id: 'ml_userriskscore_pivot_transform_default', + }); + expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(4, { + transform_id: 'ml_userriskscore_latest_transform_default', + }); + }); + + it('should return that legacy transform enabled if at least on transform exist', async () => { + esClient.transform.getTransformStats.mockResolvedValueOnce(transformStats); + + const status = await riskEngineDataClient.getStatus({ + namespace: 'default', + savedObjectsClient: mockSavedObjectClient, + }); + + expect(status).toEqual({ + riskEgineStatus: 'NOT_INSTALLED', + legacyRiskEgineStatus: 'ENABLED', + lastUpdatedBy: '', + }); + + esClient.transform.getTransformStats.mockReset(); + }); + }); + }); + + describe('enableRiskEngine', () => { + afterEach(() => { + mockSavedObjectClient.find.mockReset(); + }); + + it('should return error if saved object not exist', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], + }); + + expect.assertions(1); + try { + await riskEngineDataClient.enableRiskEngine({ + savedObjectsClient: mockSavedObjectClient, + user: { username: 'elastic' } as AuthenticatedUser, + }); + } catch (e) { + expect(e.message).toEqual('There no saved object configuration for risk engine'); + } + }); + + it('should update saved object attrubute', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); - expect(logger.error).toHaveBeenCalledWith( - `Error initializing risk engine resources: ${error.message}` + await riskEngineDataClient.enableRiskEngine({ + savedObjectsClient: mockSavedObjectClient, + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(mockSavedObjectClient.update).toHaveBeenCalledWith( + 'risk-engine-configuration', + 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', + { + enable: true, + last_updated_by: 'elastic', + }, + { + refresh: 'wait_for', + } ); }); }); + + describe('disableRiskEngine', () => { + afterEach(() => { + mockSavedObjectClient.find.mockReset(); + }); + + it('should return error if saved object not exist', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], + }); + + expect.assertions(1); + try { + await riskEngineDataClient.disableRiskEngine({ + savedObjectsClient: mockSavedObjectClient, + user: { username: 'elastic' } as AuthenticatedUser, + }); + } catch (e) { + expect(e.message).toEqual('There no saved object configuration for risk engine'); + } + }); + + it('should update saved object attrubute', async () => { + mockSavedObjectClient.find.mockResolvedValueOnce(getSavedObjectConfiguration()); + + await riskEngineDataClient.disableRiskEngine({ + savedObjectsClient: mockSavedObjectClient, + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(mockSavedObjectClient.update).toHaveBeenCalledWith( + 'risk-engine-configuration', + 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', + { + enable: false, + last_updated_by: 'elastic', + }, + { + refresh: 'wait_for', + } + ); + }); + }); + + describe('init', () => { + const initializeResourcesMock = jest.spyOn( + RiskEngineDataClient.prototype, + 'initializeResources' + ); + const enableRiskEngineMock = jest.spyOn(RiskEngineDataClient.prototype, 'enableRiskEngine'); + const initSavedObjectsMock = jest.spyOn( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + RiskEngineDataClient.prototype as any, + 'initSavedObjects' + ); + const disableLegacyRiskEngineMock = jest.spyOn( + RiskEngineDataClient.prototype, + 'disableLegacyRiskEngine' + ); + beforeEach(() => { + disableLegacyRiskEngineMock.mockImplementation(() => Promise.resolve(true)); + + initializeResourcesMock.mockImplementation(() => { + return Promise.resolve(); + }); + + enableRiskEngineMock.mockImplementation(() => { + return Promise.resolve(getSavedObjectConfiguration().saved_objects[0]); + }); + + initSavedObjectsMock.mockImplementation(() => { + return Promise.resolve(); + }); + }); + + afterEach(() => { + initializeResourcesMock.mockReset(); + enableRiskEngineMock.mockReset(); + initSavedObjectsMock.mockReset(); + disableLegacyRiskEngineMock.mockReset(); + }); + + it('success', async () => { + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: [], + leggacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, + }); + }); + + it('should catch error for disableLegacyRiskEngine, but continue', async () => { + disableLegacyRiskEngineMock.mockImplementation(() => { + throw new Error('Error disableLegacyRiskEngineMock'); + }); + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: ['Error disableLegacyRiskEngineMock'], + leggacyRiskEngineDisabled: false, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, + }); + }); + + it('should catch error for resource init', async () => { + disableLegacyRiskEngineMock.mockImplementationOnce(() => { + throw new Error('Error disableLegacyRiskEngineMock'); + }); + + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: ['Error disableLegacyRiskEngineMock'], + leggacyRiskEngineDisabled: false, + riskEngineConfigurationCreated: true, + riskEngineEnabled: true, + riskEngineResourcesInstalled: true, + }); + }); + + it('should catch error for initializeResources and stop', async () => { + initializeResourcesMock.mockImplementationOnce(() => { + throw new Error('Error initializeResourcesMock'); + }); + + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: ['Error initializeResourcesMock'], + leggacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: false, + riskEngineEnabled: false, + riskEngineResourcesInstalled: false, + }); + }); + + it('should catch error for initSavedObjects and stop', async () => { + initSavedObjectsMock.mockImplementationOnce(() => { + throw new Error('Error initSavedObjects'); + }); + + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: ['Error initSavedObjects'], + leggacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: false, + riskEngineEnabled: false, + riskEngineResourcesInstalled: true, + }); + }); + + it('should catch error for enableRiskEngineMock and stop', async () => { + enableRiskEngineMock.mockImplementationOnce(() => { + throw new Error('Error enableRiskEngineMock'); + }); + + const initResult = await riskEngineDataClient.init({ + savedObjectsClient: mockSavedObjectClient, + namespace: 'default', + user: { username: 'elastic' } as AuthenticatedUser, + }); + + expect(initResult).toEqual({ + errors: ['Error enableRiskEngineMock'], + leggacyRiskEngineDisabled: true, + riskEngineConfigurationCreated: true, + riskEngineEnabled: false, + riskEngineResourcesInstalled: true, + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index c5b557103d2bf..21673b3b9f3f9 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -203,7 +203,7 @@ export class RiskEngineDataClient { return result; } - private async disableLegacyRiskEngine({ namespace }: { namespace: string }) { + public async disableLegacyRiskEngine({ namespace }: { namespace: string }) { const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); if ( @@ -274,7 +274,10 @@ export class RiskEngineDataClient { .map((r) => (r as PromiseFulfilledResult).value); const transforms = fulfuletGetTransformStats.reduce((acc, val) => { - return [...acc, ...val.transforms]; + if (val.transforms) { + return [...acc, ...val.transforms]; + } + return acc; }, [] as TransformGetTransformStatsTransformStats[]); return transforms; @@ -409,6 +412,7 @@ export class RiskEngineDataClient { await this.initializeWriter(namespace, indexPatterns.alias); } catch (error) { this.options.logger.error(`Error initializing risk engine resources: ${error.message}`); + throw error; } } } diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 278aecdf8ce68..e64a5808eb3dd 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -170,10 +170,6 @@ export class Plugin implements ISecuritySolutionPlugin { .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), }); - if (experimentalFeatures.riskScoringPersistence) { - this.riskEngineDataClient.initializeResources({}); - } - const requestContextFactory = new RequestContextFactory({ config, logger, From 34fa0eddc67e0f6b978d0010c5a85bee0e066218 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 16:30:03 +0200 Subject: [PATCH 17/46] Wrong rebase --- .../risk_engine_data_client.test.ts | 198 ++++++++++++------ 1 file changed, 138 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 0e6f5543b4b47..15036f8dc291a 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -173,63 +173,145 @@ describe('RiskEngineDataClient', () => { }, }); - expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith({ - logger, - esClient, - template: { - name: '.risk-score-mappings', - _meta: { - managed: true, - }, - template: { - settings: {}, - mappings: { - dynamic: 'strict', - properties: { - '@timestamp': { - type: 'date', - }, - alertsScore: { - type: 'float', - }, - identifierField: { - type: 'keyword', - }, - identifierValue: { - type: 'keyword', - }, - level: { - type: 'keyword', - }, - otherScore: { - type: 'float', - }, - riskiestInputs: { - properties: { - id: { - type: 'keyword', - }, - index: { - type: 'keyword', - }, - riskScore: { - type: 'float', + expect(createOrUpdateComponentTemplate).toHaveBeenCalledWith( + expect.objectContaining({ + logger, + esClient, + template: expect.objectContaining({ + name: '.risk-score-mappings', + _meta: { + managed: true, + }, + }), + totalFieldsLimit: 1000, + }) + ); + expect((createOrUpdateComponentTemplate as jest.Mock).mock.lastCall[0].template.template) + .toMatchInlineSnapshot(` + Object { + "mappings": Object { + "dynamic": "strict", + "properties": Object { + "@timestamp": Object { + "type": "date", + }, + "host": Object { + "properties": Object { + "name": Object { + "type": "keyword", + }, + "risk": Object { + "properties": Object { + "calculated_level": Object { + "type": "keyword", + }, + "calculated_score": Object { + "type": "float", + }, + "calculated_score_norm": Object { + "type": "float", + }, + "category_1_score": Object { + "type": "float", + }, + "id_field": Object { + "type": "keyword", + }, + "id_value": Object { + "type": "keyword", + }, + "inputs": Object { + "properties": Object { + "category": Object { + "type": "keyword", + }, + "description": Object { + "type": "keyword", + }, + "id": Object { + "type": "keyword", + }, + "index": Object { + "type": "keyword", + }, + "risk_score": Object { + "type": "float", + }, + "timestamp": Object { + "type": "date", + }, + }, + "type": "object", + }, + "notes": Object { + "type": "keyword", + }, }, + "type": "object", }, - type: 'nested', - }, - totalScore: { - type: 'float', }, - totalScoreNormalized: { - type: 'float', + }, + "user": Object { + "properties": Object { + "name": Object { + "type": "keyword", + }, + "risk": Object { + "properties": Object { + "calculated_level": Object { + "type": "keyword", + }, + "calculated_score": Object { + "type": "float", + }, + "calculated_score_norm": Object { + "type": "float", + }, + "category_1_score": Object { + "type": "float", + }, + "id_field": Object { + "type": "keyword", + }, + "id_value": Object { + "type": "keyword", + }, + "inputs": Object { + "properties": Object { + "category": Object { + "type": "keyword", + }, + "description": Object { + "type": "keyword", + }, + "id": Object { + "type": "keyword", + }, + "index": Object { + "type": "keyword", + }, + "risk_score": Object { + "type": "float", + }, + "timestamp": Object { + "type": "date", + }, + }, + "type": "object", + }, + "notes": Object { + "type": "keyword", + }, + }, + "type": "object", + }, }, }, }, }, - }, - totalFieldsLimit, - }); + "settings": Object {}, + } + `); expect(createOrUpdateIndexTemplate).toHaveBeenCalledWith({ logger, @@ -285,18 +367,14 @@ describe('RiskEngineDataClient', () => { describe('initializeResources error', () => { it('should handle errors during initialization', async () => { - try { - const error = new Error('There error'); - (createOrUpdateIlmPolicy as jest.Mock).mockRejectedValue(error); + const error = new Error('There error'); + (createOrUpdateIlmPolicy as jest.Mock).mockRejectedValue(error); - await riskEngineDataClient.initializeResources({ namespace: 'default' }); + await riskEngineDataClient.initializeResources({ namespace: 'default' }); - expect(logger.error).toHaveBeenCalledWith( - `Error initializing risk engine resources: ${error.message}` - ); - } catch (e) { - // - } + expect(logger.error).toHaveBeenCalledWith( + `Error initializing risk engine resources: ${error.message}` + ); }); }); From 2cce86587956c14010af0fc6d1539d08dd779093 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 28 Jul 2023 16:36:43 +0200 Subject: [PATCH 18/46] clean --- .../server/lib/risk_engine/risk_engine_data_client.test.ts | 1 - .../security_solution/server/lib/risk_engine/types.ts | 6 ------ 2 files changed, 7 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 15036f8dc291a..b8fe0105e78cc 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -16,7 +16,6 @@ import { savedObjectsClientMock, } from '@kbn/core/server/mocks'; import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; -import { transformsHttpMocks } from '../../../public/management/pages/endpoint_hosts/mocks'; import { RiskEngineDataClient } from './risk_engine_data_client'; import { createDataStream } from './utils/create_datastream'; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index 90872e56293f6..3e3457c9d8852 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -62,12 +62,6 @@ export interface GetRiskEngineStatusResponse { risk_engine_status: RiskEngineStatus; } -export interface InitStep { - type: string; - success: boolean; - error?: string; -} - export interface InitRiskEngineResponse { result: InitRiskEngineResult; } From 189e9413bcb86c1a004c105b1352a796daa88438 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:16:56 +0000 Subject: [PATCH 19/46] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../server/lib/risk_engine/risk_engine_data_client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 21673b3b9f3f9..25d68b11cae85 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -326,7 +326,6 @@ export class RiskEngineDataClient { } } - private async initSavedObjects({ savedObjectsClient, user }: UpdateConfigOpts) { return savedObjectsClient.create(riskEngineConfigurationTypeName, { enable: false, From b99c3e1d2fdafa49360cb274bbf89d19df57717e Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 31 Jul 2023 10:55:28 +0200 Subject: [PATCH 20/46] Typos and PR fixes --- .../common/risk_engine/types.ts | 2 +- .../hooks/use_disable_risk_engine_mutation.ts | 4 +- .../hooks/use_enable_risk_engine_mutation.ts | 4 +- .../hooks/use_init_risk_engine_mutation.ts | 4 +- .../api/hooks/use_risk_engine_status.ts | 2 +- .../components/risk_score_enable_section.tsx | 15 +++-- .../components/risk_score_update_panel.tsx | 8 --- .../public/entity_analytics/translations.ts | 2 +- .../overview/pages/entity_analytics.tsx | 10 +++- .../risk_engine_data_client.test.ts | 28 +++++----- .../risk_engine/risk_engine_data_client.ts | 55 +++++++++++-------- .../routes/risk_engine_disable_route.ts | 4 +- .../routes/risk_engine_enable_route.ts | 4 +- .../routes/risk_engine_init_route.ts | 19 +++++-- .../routes/risk_engine_status_route.ts | 10 ++-- .../risk_engine_configuration_type.ts | 2 +- .../risk_engine/schema/risk_score_apis.yml | 20 +++---- .../server/lib/risk_engine/types.ts | 16 ++++-- 18 files changed, 113 insertions(+), 96 deletions(-) diff --git a/x-pack/plugins/security_solution/common/risk_engine/types.ts b/x-pack/plugins/security_solution/common/risk_engine/types.ts index 3f82f367d1c2a..087c9d1ed71e6 100644 --- a/x-pack/plugins/security_solution/common/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/common/risk_engine/types.ts @@ -17,7 +17,7 @@ export enum RiskEngineStatus { } export interface InitRiskEngineResult { - leggacyRiskEngineDisabled: boolean; + legacyRiskEngineDisabled: boolean; riskEngineResourcesInstalled: boolean; riskEngineConfigurationCreated: boolean; riskEngineEnabled: boolean; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts index 231e9da39473c..801677a47da70 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts @@ -7,7 +7,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import { disableRiskEngine } from '../api'; -import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { GetRiskEngineDisableResponse, EnableDisableRiskEngineResponse, @@ -16,7 +16,7 @@ import type { export const DISABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'DISABLE_RISK_ENGINE']; export const useDisableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { - const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatusQuery(); return useMutation( () => disableRiskEngine(), diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts index 980aadc274e92..72a222939f225 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts @@ -7,7 +7,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import { enableRiskEngine } from '../api'; -import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { GetRiskEngineEnableResponse, EnableDisableRiskEngineResponse, @@ -15,7 +15,7 @@ import type { export const ENABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'ENABLE_RISK_ENGINE']; export const useEnableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { - const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatusQuery(); return useMutation( () => enableRiskEngine(), diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts index 81f6a55d9ac25..d220885148cac 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_init_risk_engine_mutation.ts @@ -7,7 +7,7 @@ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import { initRiskEngine } from '../api'; -import { useInvalidateRiskEngineStatussQuery } from './use_risk_engine_status'; +import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { InitRiskEngineResponse, InitRiskEngineError, @@ -16,7 +16,7 @@ import type { export const INIT_RISK_ENGINE_STATUS_KEY = ['POST', 'INIT_RISK_ENGINE']; export const useInitRiskEngineMutation = (options?: UseMutationOptions<{}>) => { - const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatussQuery(); + const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatusQuery(); return useMutation(() => initRiskEngine(), { ...options, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts index afe6d885c7e09..981fc29d8f702 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_status.ts @@ -11,7 +11,7 @@ import { RiskEngineStatus } from '../../../../common/risk_engine/types'; import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; const FETCH_RISK_ENGINE_STATUS = ['GET', 'FETCH_RISK_ENGINE_STATUS']; -export const useInvalidateRiskEngineStatussQuery = () => { +export const useInvalidateRiskEngineStatusQuery = () => { const queryClient = useQueryClient(); return useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 76db64dfadbb9..7f5b6b79bd1f2 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -79,7 +79,7 @@ const RiskScoreErrorPanel = ({ errors }: { errors: string[] }) => ( export const RiskScoreEnableSection = () => { const [isModalVisible, setIsModalVisible] = useState(false); - const { data: riskEnginesStatus } = useRiskEngineStatus(); + const { data: riskEngineStatus } = useRiskEngineStatus(); const initRiskEngineMutation = useInitRiskEngineMutation({ onSettled: () => { setIsModalVisible(false); @@ -89,7 +89,7 @@ export const RiskScoreEnableSection = () => { const enableRiskEngineMutation = useEnableRiskEngineMutation(); const disableRiskEngineMutation = useDisableRiskEngineMutation(); - const currentRiskEngineStatus = riskEnginesStatus?.risk_engine_status; + const currentRiskEngineStatus = riskEngineStatus?.risk_engine_status; const closeModal = () => setIsModalVisible(false); const showModal = () => setIsModalVisible(true); @@ -99,7 +99,7 @@ export const RiskScoreEnableSection = () => { enableRiskEngineMutation.isLoading || disableRiskEngineMutation.isLoading; - const isUpdateAvailable = riskEnginesStatus?.isUpdateAvailable; + const isUpdateAvailable = riskEngineStatus?.isUpdateAvailable; const onSwitchClick = () => { if (!currentRiskEngineStatus || isLoading) { @@ -120,15 +120,14 @@ export const RiskScoreEnableSection = () => { if (isModalVisible) { modal = ( - {initRiskEngineMutation.isLoading && ( + {initRiskEngineMutation.isLoading ? ( {i18n.UPDATING_RISK_ENGINE} - )} - {!initRiskEngineMutation.isLoading && ( + ) : ( <> {i18n.UPDATE_RISK_ENGINE_MODAL_TITLE} @@ -167,10 +166,10 @@ export const RiskScoreEnableSection = () => { if (initRiskEngineMutation.isError) { const errorBody = initRiskEngineMutation.error.body.message; - if (typeof errorBody.full_error !== 'string') { + if (errorBody?.full_error?.errors) { initRiskEngineErrors = errorBody.full_error?.errors; } else { - initRiskEngineErrors = [errorBody.message]; + initRiskEngineErrors = [errorBody]; } } return ( diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx index 80d1d6f31f7d8..13668bc595ad8 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_update_panel.tsx @@ -8,18 +8,10 @@ import React from 'react'; import { EuiCallOut, EuiText, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import * as i18n from '../translations'; -import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status'; import { SecuritySolutionLinkButton } from '../../common/components/links'; import { SecurityPageName } from '../../../common/constants'; export const RiskScoreUpdatePanel = () => { - const { data: riskEnginesStatus } = useRiskEngineStatus(); - const isUpdateAvailable = riskEnginesStatus?.isUpdateAvailable; - - if (!isUpdateAvailable) { - return null; - } - return ( {i18n.UPDATE_PANEL_MESSAGE} diff --git a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts index 5afba438cff0e..fb5c68a5f440a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts @@ -205,7 +205,7 @@ export const ERROR_PANEL_TITLE = i18n.translate( export const ERROR_PANEL_MESSAGE = i18n.translate( 'xpack.securitySolution.riskScore.errorPanel.message', { - defaultMessage: 'Something wen’t wrong. Try again later.', + defaultMessage: 'Something went wrong. Try again later.', } ); diff --git a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx index 8ab8405875a68..f7f4ff592ab81 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx @@ -25,9 +25,11 @@ import { EntityAnalyticsAnomalies } from '../components/entity_analytics/anomali import { SiemSearchBar } from '../../common/components/search_bar'; import { InputsModelId } from '../../common/store/inputs/constants'; import { FiltersGlobal } from '../../common/components/filters_global'; +import { useRiskEngineStatus } from '../../entity_analytics/api/hooks/use_risk_engine_status'; import { RiskScoreUpdatePanel } from '../../entity_analytics/components/risk_score_update_panel'; const EntityAnalyticsComponent = () => { + const { data: riskScoreEngineStatus } = useRiskEngineStatus(); const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); const { isPlatinumOrTrialLicense, capabilitiesFetched } = useMlCapabilities(); @@ -49,9 +51,11 @@ const EntityAnalyticsComponent = () => { ) : ( - - - + {riskScoreEngineStatus?.isUpdateAvailable && ( + + + + )} diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index b8fe0105e78cc..3d3d4e3355e43 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -384,8 +384,8 @@ describe('RiskEngineDataClient', () => { savedObjectsClient: mockSavedObjectClient, }); expect(status).toEqual({ - riskEgineStatus: 'NOT_INSTALLED', - legacyRiskEgineStatus: 'NOT_INSTALLED', + riskEngineStatus: 'NOT_INSTALLED', + legacyRiskEngineStatus: 'NOT_INSTALLED', lastUpdatedBy: '', }); }); @@ -411,8 +411,8 @@ describe('RiskEngineDataClient', () => { savedObjectsClient: mockSavedObjectClient, }); expect(status).toEqual({ - riskEgineStatus: 'ENABLED', - legacyRiskEgineStatus: 'NOT_INSTALLED', + riskEngineStatus: 'ENABLED', + legacyRiskEngineStatus: 'NOT_INSTALLED', lastUpdatedBy: 'elastic', }); }); @@ -425,8 +425,8 @@ describe('RiskEngineDataClient', () => { savedObjectsClient: mockSavedObjectClient, }); expect(status).toEqual({ - riskEgineStatus: 'DISABLED', - legacyRiskEgineStatus: 'NOT_INSTALLED', + riskEngineStatus: 'DISABLED', + legacyRiskEngineStatus: 'NOT_INSTALLED', lastUpdatedBy: 'elastic', }); }); @@ -463,8 +463,8 @@ describe('RiskEngineDataClient', () => { }); expect(status).toEqual({ - riskEgineStatus: 'NOT_INSTALLED', - legacyRiskEgineStatus: 'ENABLED', + riskEngineStatus: 'NOT_INSTALLED', + legacyRiskEngineStatus: 'ENABLED', lastUpdatedBy: '', }); @@ -612,7 +612,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: [], - leggacyRiskEngineDisabled: true, + legacyRiskEngineDisabled: true, riskEngineConfigurationCreated: true, riskEngineEnabled: true, riskEngineResourcesInstalled: true, @@ -631,7 +631,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: ['Error disableLegacyRiskEngineMock'], - leggacyRiskEngineDisabled: false, + legacyRiskEngineDisabled: false, riskEngineConfigurationCreated: true, riskEngineEnabled: true, riskEngineResourcesInstalled: true, @@ -651,7 +651,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: ['Error disableLegacyRiskEngineMock'], - leggacyRiskEngineDisabled: false, + legacyRiskEngineDisabled: false, riskEngineConfigurationCreated: true, riskEngineEnabled: true, riskEngineResourcesInstalled: true, @@ -671,7 +671,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: ['Error initializeResourcesMock'], - leggacyRiskEngineDisabled: true, + legacyRiskEngineDisabled: true, riskEngineConfigurationCreated: false, riskEngineEnabled: false, riskEngineResourcesInstalled: false, @@ -691,7 +691,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: ['Error initSavedObjects'], - leggacyRiskEngineDisabled: true, + legacyRiskEngineDisabled: true, riskEngineConfigurationCreated: false, riskEngineEnabled: false, riskEngineResourcesInstalled: true, @@ -711,7 +711,7 @@ describe('RiskEngineDataClient', () => { expect(initResult).toEqual({ errors: ['Error enableRiskEngineMock'], - leggacyRiskEngineDisabled: true, + legacyRiskEngineDisabled: true, riskEngineConfigurationCreated: true, riskEngineEnabled: false, riskEngineResourcesInstalled: true, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 25d68b11cae85..00a0f87e8c647 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -19,7 +19,12 @@ import { } from '@kbn/alerting-plugin/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; -import type { Logger, ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { + Logger, + ElasticsearchClient, + SavedObjectsClientContract, + SavedObject, +} from '@kbn/core/server'; import { riskScoreFieldMap, @@ -63,7 +68,7 @@ interface RiskEngineDataClientOpts { } interface Configuration { - enable: boolean; + enabled: boolean; last_updated_by: string; } @@ -73,7 +78,7 @@ export class RiskEngineDataClient { public async init({ namespace, savedObjectsClient, user }: InitOpts) { const result: InitRiskEngineResult = { - leggacyRiskEngineDisabled: false, + legacyRiskEngineDisabled: false, riskEngineResourcesInstalled: false, riskEngineConfigurationCreated: false, riskEngineEnabled: false, @@ -81,9 +86,9 @@ export class RiskEngineDataClient { }; try { - result.leggacyRiskEngineDisabled = await this.disableLegacyRiskEngine({ namespace }); + result.legacyRiskEngineDisabled = await this.disableLegacyRiskEngine({ namespace }); } catch (e) { - result.leggacyRiskEngineDisabled = false; + result.legacyRiskEngineDisabled = false; result.errors.push(e.message); } @@ -141,20 +146,20 @@ export class RiskEngineDataClient { }: SavedObjectsClients & { namespace: string; }) { - const riskEgineStatus = await this.getCurrentStatus({ savedObjectsClient }); - const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + const riskEngineStatus = await this.getCurrentStatus({ savedObjectsClient }); + const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); const lastUpdatedBy = await this.getLastUpdatedBy({ savedObjectsClient }); - return { riskEgineStatus, legacyRiskEgineStatus, lastUpdatedBy }; + return { riskEngineStatus, legacyRiskEngineStatus, lastUpdatedBy }; } public async enableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to run task - return this.udpateSavedObjectAttribute({ + return this.updateSavedObjectAttribute({ savedObjectsClient, user, attributes: { - enable: true, + enabled: true, }, }); } @@ -162,22 +167,22 @@ export class RiskEngineDataClient { public async disableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to stop task - return this.udpateSavedObjectAttribute({ + return this.updateSavedObjectAttribute({ savedObjectsClient, user, attributes: { - enable: false, + enabled: false, }, }); } - private async udpateSavedObjectAttribute({ + private async updateSavedObjectAttribute({ savedObjectsClient, attributes, user, }: UpdateConfigOpts & { attributes: { - enable: boolean; + enabled: boolean; }; }) { const savedObjectConfiguration = await this.getConfigurationSavedObject({ @@ -204,11 +209,11 @@ export class RiskEngineDataClient { } public async disableLegacyRiskEngine({ namespace }: { namespace: string }) { - const legacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); if ( - legacyRiskEgineStatus === RiskEngineStatus.DISABLED || - legacyRiskEgineStatus === RiskEngineStatus.NOT_INSTALLED + legacyRiskEngineStatus === RiskEngineStatus.DISABLED || + legacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED ) { return true; } @@ -225,9 +230,9 @@ export class RiskEngineDataClient { await Promise.allSettled(stopTransformRequests); - const newLegacyRiskEgineStatus = await this.getLegacyStatus({ namespace }); + const newlegacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); - return newLegacyRiskEgineStatus === RiskEngineStatus.DISABLED; + return newlegacyRiskEngineStatus === RiskEngineStatus.DISABLED; } private async getLastUpdatedBy({ savedObjectsClient }: SavedObjectsClients) { @@ -244,7 +249,7 @@ export class RiskEngineDataClient { const configuration = await this.getConfiguration({ savedObjectsClient }); if (configuration) { - return configuration.enable ? RiskEngineStatus.ENABLED : RiskEngineStatus.DISABLED; + return configuration.enabled ? RiskEngineStatus.ENABLED : RiskEngineStatus.DISABLED; } return RiskEngineStatus.NOT_INSTALLED; @@ -299,8 +304,10 @@ export class RiskEngineDataClient { return RiskEngineStatus.DISABLED; } - private async getConfigurationSavedObject({ savedObjectsClient }: SavedObjectsClients) { - const savedObjectsResponse = await savedObjectsClient.find({ + private async getConfigurationSavedObject({ + savedObjectsClient, + }: SavedObjectsClients): Promise | undefined> { + const savedObjectsResponse = await savedObjectsClient.find({ type: riskEngineConfigurationTypeName, }); return savedObjectsResponse.saved_objects?.[0]; @@ -316,7 +323,7 @@ export class RiskEngineDataClient { const configuration = savedObjectConfiguration?.attributes; if (configuration) { - return configuration as Configuration; + return configuration; } return null; @@ -328,7 +335,7 @@ export class RiskEngineDataClient { private async initSavedObjects({ savedObjectsClient, user }: UpdateConfigOpts) { return savedObjectsClient.create(riskEngineConfigurationTypeName, { - enable: false, + enabled: false, last_updated_by: user?.username ?? '', }); } diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts index 1d5043521eaa7..b15bd81b40ad3 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -30,11 +30,11 @@ export const riskEngineDisableRoute = ( const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); const user = security?.authc.getCurrentUser(request); try { - await riskEgineClient.disableRiskEngine({ + await riskEngineClient.disableRiskEngine({ savedObjectsClient: soClient, user, }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index aa1cc3fb9a0c9..267abdcc2d0d3 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -29,11 +29,11 @@ export const riskEngineEnableRoute = ( const siemResponse = buildSiemResponse(response); const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); const user = security?.authc.getCurrentUser(request); try { - await riskEgineClient.enableRiskEngine({ + await riskEngineClient.enableRiskEngine({ savedObjectsClient: soClient, user, }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index 8cd0f54049cf8..4db9db372999d 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -30,17 +30,25 @@ export const riskEngineInitRoute = ( const siemResponse = buildSiemResponse(response); const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); const spaceId = securitySolution.getSpaceId(); const user = security?.authc.getCurrentUser(request); try { - const initResult = await riskEgineClient.init({ + const initResult = await riskEngineClient.init({ savedObjectsClient: soClient, namespace: spaceId, user, }); + const initResultSnakeCase = { + risk_engine_enabled: initResult.riskEngineEnabled, + risk_engine_resources_installed: initResult.riskEngineResourcesInstalled, + risk_engine_configuration_created: initResult.riskEngineConfigurationCreated, + legacy_risk_engine_disabled: initResult.legacyRiskEngineDisabled, + errors: initResult.errors, + }; + if ( !initResult.riskEngineEnabled || !initResult.riskEngineResourcesInstalled || @@ -49,13 +57,12 @@ export const riskEngineInitRoute = ( return siemResponse.error({ statusCode: 400, body: { - message: initResult.errors.join('\n'), - full_error: initResult, + message: initResultSnakeCase.errors.join('\n'), + full_error: initResultSnakeCase, }, }); } - - return response.ok({ body: { result: initResult } }); + return response.ok({ body: { result: initResultSnakeCase } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts index 18e35bca684b0..d4cd4f2d6e16f 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -26,19 +26,19 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg const securitySolution = await context.securitySolution; const soClient = (await context.core).savedObjects.client; - const riskEgineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); const spaceId = securitySolution.getSpaceId(); try { - const result = await riskEgineClient.getStatus({ + const result = await riskEngineClient.getStatus({ savedObjectsClient: soClient, namespace: spaceId, }); return response.ok({ body: { - risk_engine_status: result.riskEgineStatus, - legacy_risk_engine_status: result.legacyRiskEgineStatus, - last_udpated_by: result.lastUpdatedBy, + risk_engine_status: result.riskEngineStatus, + legacy_risk_engine_status: result.legacyRiskEngineStatus, + last_updated_by: result.lastUpdatedBy, }, }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts index 1f48e798fdcbc..cb5c2f700b31c 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts @@ -12,7 +12,7 @@ export const riskEngineConfigurationTypeName = 'risk-engine-configuration'; export const riskEngineConfigurationTypeMappings: SavedObjectsType['mappings'] = { properties: { - enable: { + enabled: { type: 'boolean', }, last_updated_by: { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml index be435fe643ee1..8e28abb7c47ea 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/schema/risk_score_apis.yml @@ -47,7 +47,7 @@ paths: /engine/status: get: summary: Get the status of the Risk Engine - description: Returns the status of the legacy transforms risk engine as well as the new risk engine + description: Returns the status of both the legacy transform-based risk engine, as well as the new risk engine responses: '200': description: Successful response @@ -57,7 +57,7 @@ paths: $ref: '#/components/schemas/RiskEngineStatusResponse' /engine/init: - posrt: + post: summary: Initialize the Risk Engine description: Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine responses: @@ -213,21 +213,21 @@ components: RiskEngineInitResponse: type: object properties: - errors: - type: array - items: - type: string result: type: object properties: - riskEngineEnabled: + risk_engine_enabled: type: boolean - riskEngineResourcesInstalled: + risk_engine_resources_installed: type: boolean - riskEngineConfigurationCreated: + risk_engine_configuration_created: type: boolean - leggacyRiskEngineDisabled: + legacy_risk_engine_disabled: type: boolean + errors: + type: array + items: + type: string diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index 3e3457c9d8852..236a6c9f1ac35 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -14,7 +14,7 @@ import type { RiskCategories, RiskWeights, } from '../../../common/risk_engine'; -import type { RiskEngineStatus, InitRiskEngineResult } from '../../../common/risk_engine/types'; +import type { RiskEngineStatus } from '../../../common/risk_engine/types'; export interface CalculateScoresParams { afterKeys: AfterKeys; debug?: boolean; @@ -62,16 +62,24 @@ export interface GetRiskEngineStatusResponse { risk_engine_status: RiskEngineStatus; } +interface InitRiskEngineResultResponse { + risk_engine_enabled: boolean; + risk_engine_resources_installed: boolean; + risk_engine_configuration_created: boolean; + legacy_risk_engine_disabled: boolean; + errors: string[]; +} + export interface InitRiskEngineResponse { - result: InitRiskEngineResult; + result: InitRiskEngineResultResponse; } export interface InitRiskEngineError { body: { message: { message: string; - full_error: InitRiskEngineResult | string; - }; + full_error: InitRiskEngineResultResponse | undefined; + } & string; }; } From b4f59558f94de5f162538a36632d3db328bdfc46 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 31 Jul 2023 16:37:16 +0200 Subject: [PATCH 21/46] add api tets --- .../kbn_client/kbn_client_saved_objects.ts | 28 ++ .../risk_engine/risk_engine_data_client.ts | 36 +- .../security_and_spaces/group10/index.ts | 2 +- .../risk_engine_install_resources.ts | 227 ---------- .../group10/risk_engine/risk_engine_status.ts | 395 ++++++++++++++++++ .../group10/risk_engine/utils.ts | 77 +++- 6 files changed, 516 insertions(+), 249 deletions(-) delete mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_install_resources.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 2aa74086a73c2..ca6fc03745fb8 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -30,6 +30,18 @@ interface SavedObjectResponse> { version?: string; } +interface SavedObjectsFindResponse> { + page: number; + per_page: number; + total: number; + saved_objects: Array>; +} + +interface GetFindOptions { + type: string; + space?: string; +} + interface GetOptions { type: string; id: string; @@ -152,6 +164,22 @@ export class KbnClientSavedObjects { return data; } + /** + * Find saved objects + */ + public async find>(options: GetFindOptions) { + this.log.debug('Find saved objects: %j', options); + + const { data } = await this.requester.request>({ + description: 'find saved objects', + path: options.space + ? uriencode`/s/${options.space}/internal/ftr/kbn_client_so/_find?type=${options.type}` + : uriencode`/internal/ftr/kbn_client_so/_find?type=${options.type}`, + method: 'GET', + }); + return data; + } + /** * Create a saved object */ diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 00a0f87e8c647..8a61615c66380 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -9,8 +9,8 @@ import type { Metadata } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import type { ClusterPutComponentTemplateRequest, - TransformGetTransformStatsResponse, - TransformGetTransformStatsTransformStats, + TransformGetTransformResponse, + TransformGetTransformTransformSummary, } from '@elastic/elasticsearch/lib/api/types'; import { createOrUpdateComponentTemplate, @@ -211,10 +211,7 @@ export class RiskEngineDataClient { public async disableLegacyRiskEngine({ namespace }: { namespace: string }) { const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); - if ( - legacyRiskEngineStatus === RiskEngineStatus.DISABLED || - legacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED - ) { + if (legacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED) { return true; } @@ -232,7 +229,7 @@ export class RiskEngineDataClient { const newlegacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); - return newlegacyRiskEngineStatus === RiskEngineStatus.DISABLED; + return newlegacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED; } private async getLastUpdatedBy({ savedObjectsClient }: SavedObjectsClients) { @@ -258,15 +255,15 @@ export class RiskEngineDataClient { private async getLegacyTransforms({ namespace }: { namespace: string }) { const esClient = await this.options.elasticsearchClientPromise; - const getTransformStatsRequests: Array> = []; + const getTransformStatsRequests: Array> = []; [RiskScoreEntity.host, RiskScoreEntity.user].forEach((entity) => { getTransformStatsRequests.push( - esClient.transform.getTransformStats({ + esClient.transform.getTransform({ transform_id: getRiskScorePivotTransformId(entity, namespace), }) ); getTransformStatsRequests.push( - esClient.transform.getTransformStats({ + esClient.transform.getTransform({ transform_id: getRiskScoreLatestTransformId(entity, namespace), }) ); @@ -276,14 +273,14 @@ export class RiskEngineDataClient { const fulfuletGetTransformStats = result .filter((r) => r.status === 'fulfilled') - .map((r) => (r as PromiseFulfilledResult).value); + .map((r) => (r as PromiseFulfilledResult).value); const transforms = fulfuletGetTransformStats.reduce((acc, val) => { if (val.transforms) { return [...acc, ...val.transforms]; } return acc; - }, [] as TransformGetTransformStatsTransformStats[]); + }, [] as TransformGetTransformTransformSummary[]); return transforms; } @@ -295,13 +292,7 @@ export class RiskEngineDataClient { return RiskEngineStatus.NOT_INSTALLED; } - const notStoppedTransformsExisted = transforms.some((t) => t.state !== 'stopped'); - - if (notStoppedTransformsExisted) { - return RiskEngineStatus.ENABLED; - } - - return RiskEngineStatus.DISABLED; + return RiskEngineStatus.ENABLED; } private async getConfigurationSavedObject({ @@ -334,10 +325,15 @@ export class RiskEngineDataClient { } private async initSavedObjects({ savedObjectsClient, user }: UpdateConfigOpts) { - return savedObjectsClient.create(riskEngineConfigurationTypeName, { + const configuration = await this.getConfiguration({ savedObjectsClient }); + if (configuration) { + return configuration; + } + const result = await savedObjectsClient.create(riskEngineConfigurationTypeName, { enabled: false, last_updated_by: user?.username ?? '', }); + return result; } public async initializeResources({ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts index 108f179b5686b..114206c6f851b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/index.ts @@ -37,7 +37,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./throttle')); loadTestFile(require.resolve('./ignore_fields')); loadTestFile(require.resolve('./migrations')); - loadTestFile(require.resolve('./risk_engine/risk_engine_install_resources')); + loadTestFile(require.resolve('./risk_engine/risk_engine_status')); loadTestFile(require.resolve('./risk_engine/risk_score_preview')); loadTestFile(require.resolve('./risk_engine/risk_score_calculation')); loadTestFile(require.resolve('./set_alert_tags')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_install_resources.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_install_resources.ts deleted file mode 100644 index a35c3a49b7e99..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_install_resources.ts +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const es = getService('es'); - - describe('Risk Engine - Install Resources', () => { - it('should install resources on startup', async () => { - const ilmPolicyName = '.risk-score-ilm-policy'; - const componentTemplateName = '.risk-score-mappings'; - const indexTemplateName = '.risk-score.risk-score-default-index-template'; - const indexName = 'risk-score.risk-score-default'; - - const ilmPolicy = await es.ilm.getLifecycle({ - name: ilmPolicyName, - }); - - expect(ilmPolicy[ilmPolicyName].policy).to.eql({ - _meta: { - managed: true, - }, - phases: { - hot: { - min_age: '0ms', - actions: { - rollover: { - max_age: '30d', - max_primary_shard_size: '50gb', - }, - }, - }, - }, - }); - - const { component_templates: componentTemplates1 } = await es.cluster.getComponentTemplate({ - name: componentTemplateName, - }); - - expect(componentTemplates1.length).to.eql(1); - const componentTemplate = componentTemplates1[0]; - - expect(componentTemplate.name).to.eql(componentTemplateName); - expect(componentTemplate.component_template.template.mappings).to.eql({ - dynamic: 'strict', - properties: { - '@timestamp': { - type: 'date', - }, - host: { - properties: { - name: { - type: 'keyword', - }, - risk: { - properties: { - calculated_level: { - type: 'keyword', - }, - calculated_score: { - type: 'float', - }, - calculated_score_norm: { - type: 'float', - }, - category_1_score: { - type: 'float', - }, - id_field: { - type: 'keyword', - }, - id_value: { - type: 'keyword', - }, - notes: { - type: 'keyword', - }, - inputs: { - properties: { - id: { - type: 'keyword', - }, - index: { - type: 'keyword', - }, - category: { - type: 'keyword', - }, - description: { - type: 'keyword', - }, - risk_score: { - type: 'float', - }, - timestamp: { - type: 'date', - }, - }, - type: 'object', - }, - }, - type: 'object', - }, - }, - }, - user: { - properties: { - name: { - type: 'keyword', - }, - risk: { - properties: { - calculated_level: { - type: 'keyword', - }, - calculated_score: { - type: 'float', - }, - calculated_score_norm: { - type: 'float', - }, - category_1_score: { - type: 'float', - }, - id_field: { - type: 'keyword', - }, - id_value: { - type: 'keyword', - }, - notes: { - type: 'keyword', - }, - inputs: { - properties: { - id: { - type: 'keyword', - }, - index: { - type: 'keyword', - }, - category: { - type: 'keyword', - }, - description: { - type: 'keyword', - }, - risk_score: { - type: 'float', - }, - timestamp: { - type: 'date', - }, - }, - type: 'object', - }, - }, - type: 'object', - }, - }, - }, - }, - }); - - const { index_templates: indexTemplates } = await es.indices.getIndexTemplate({ - name: indexTemplateName, - }); - expect(indexTemplates.length).to.eql(1); - const indexTemplate = indexTemplates[0]; - expect(indexTemplate.name).to.eql(indexTemplateName); - expect(indexTemplate.index_template.index_patterns).to.eql(['risk-score.risk-score-default']); - expect(indexTemplate.index_template.composed_of).to.eql(['.risk-score-mappings']); - expect(indexTemplate.index_template.template!.mappings?.dynamic).to.eql(false); - expect(indexTemplate.index_template.template!.mappings?._meta?.managed).to.eql(true); - expect(indexTemplate.index_template.template!.mappings?._meta?.namespace).to.eql('default'); - expect(indexTemplate.index_template.template!.mappings?._meta?.kibana?.version).to.be.a( - 'string' - ); - expect(indexTemplate.index_template.template!.settings).to.eql({ - index: { - lifecycle: { - name: '.risk-score-ilm-policy', - }, - mapping: { - total_fields: { - limit: '1000', - }, - }, - hidden: 'true', - auto_expand_replicas: '0-1', - }, - }); - - const dsResponse = await es.indices.get({ - index: indexName, - }); - - const dataStream = Object.values(dsResponse).find((ds) => ds.data_stream === indexName); - - expect(dataStream?.mappings?._meta?.managed).to.eql(true); - expect(dataStream?.mappings?._meta?.namespace).to.eql('default'); - expect(dataStream?.mappings?._meta?.kibana?.version).to.be.a('string'); - expect(dataStream?.mappings?.dynamic).to.eql('false'); - - expect(dataStream?.settings?.index?.lifecycle).to.eql({ - name: '.risk-score-ilm-policy', - }); - - expect(dataStream?.settings?.index?.mapping).to.eql({ - total_fields: { - limit: '1000', - }, - }); - - expect(dataStream?.settings?.index?.hidden).to.eql('true'); - expect(dataStream?.settings?.index?.number_of_shards).to.eql(1); - expect(dataStream?.settings?.index?.auto_expand_replicas).to.eql('0-1'); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts new file mode 100644 index 0000000000000..f72a2db3f820b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts @@ -0,0 +1,395 @@ +/* + * 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 expect from '@kbn/expect'; +import { + RISK_ENGINE_INIT_URL, + RISK_ENGINE_DISABLE_URL, + RISK_ENGINE_ENABLE_URL, + RISK_ENGINE_STATUS_URL, +} from '@kbn/security-solution-plugin/common/constants'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + cleanRiskEngineConfig, + legacyTransformIds, + createTransforms, + clearLegacyTranforms, +} from './utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const es = getService('es'); + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + + describe('Risk Engine', () => { + afterEach(async () => { + await cleanRiskEngineConfig({ + kibanaServer, + }); + await clearLegacyTranforms({ + es, + }); + }); + + const initRiskEngine = async () => + await supertest.post(RISK_ENGINE_INIT_URL).set('kbn-xsrf', 'true').send().expect(200); + + const getRiskEngineStatus = async () => + await supertest.get(RISK_ENGINE_STATUS_URL).set('kbn-xsrf', 'true').send().expect(200); + + const enableRiskEngine = async () => + await supertest.post(RISK_ENGINE_ENABLE_URL).set('kbn-xsrf', 'true').send().expect(200); + + const disableRiskEngine = async () => + await supertest.post(RISK_ENGINE_DISABLE_URL).set('kbn-xsrf', 'true').send().expect(200); + + describe('init api', () => { + it('should return response with success status', async () => { + const response = await initRiskEngine(); + expect(response.body).to.eql({ + result: { + errors: [], + legacy_risk_engine_disabled: true, + risk_engine_configuration_created: true, + risk_engine_enabled: true, + risk_engine_resources_installed: true, + }, + }); + }); + + it('should install resources on init call', async () => { + const ilmPolicyName = '.risk-score-ilm-policy'; + const componentTemplateName = '.risk-score-mappings'; + const indexTemplateName = '.risk-score.risk-score-default-index-template'; + const indexName = 'risk-score.risk-score-default'; + + await initRiskEngine(); + + const ilmPolicy = await es.ilm.getLifecycle({ + name: ilmPolicyName, + }); + + expect(ilmPolicy[ilmPolicyName].policy).to.eql({ + _meta: { + managed: true, + }, + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_age: '30d', + max_primary_shard_size: '50gb', + }, + }, + }, + }, + }); + + const { component_templates: componentTemplates1 } = await es.cluster.getComponentTemplate({ + name: componentTemplateName, + }); + + expect(componentTemplates1.length).to.eql(1); + const componentTemplate = componentTemplates1[0]; + + expect(componentTemplate.name).to.eql(componentTemplateName); + expect(componentTemplate.component_template.template.mappings).to.eql({ + dynamic: 'strict', + properties: { + '@timestamp': { + type: 'date', + }, + host: { + properties: { + name: { + type: 'keyword', + }, + risk: { + properties: { + calculated_level: { + type: 'keyword', + }, + calculated_score: { + type: 'float', + }, + calculated_score_norm: { + type: 'float', + }, + category_1_score: { + type: 'float', + }, + id_field: { + type: 'keyword', + }, + id_value: { + type: 'keyword', + }, + notes: { + type: 'keyword', + }, + inputs: { + properties: { + id: { + type: 'keyword', + }, + index: { + type: 'keyword', + }, + category: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + risk_score: { + type: 'float', + }, + timestamp: { + type: 'date', + }, + }, + type: 'object', + }, + }, + type: 'object', + }, + }, + }, + user: { + properties: { + name: { + type: 'keyword', + }, + risk: { + properties: { + calculated_level: { + type: 'keyword', + }, + calculated_score: { + type: 'float', + }, + calculated_score_norm: { + type: 'float', + }, + category_1_score: { + type: 'float', + }, + id_field: { + type: 'keyword', + }, + id_value: { + type: 'keyword', + }, + notes: { + type: 'keyword', + }, + inputs: { + properties: { + id: { + type: 'keyword', + }, + index: { + type: 'keyword', + }, + category: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + risk_score: { + type: 'float', + }, + timestamp: { + type: 'date', + }, + }, + type: 'object', + }, + }, + type: 'object', + }, + }, + }, + }, + }); + + const { index_templates: indexTemplates } = await es.indices.getIndexTemplate({ + name: indexTemplateName, + }); + expect(indexTemplates.length).to.eql(1); + const indexTemplate = indexTemplates[0]; + expect(indexTemplate.name).to.eql(indexTemplateName); + expect(indexTemplate.index_template.index_patterns).to.eql([ + 'risk-score.risk-score-default', + ]); + expect(indexTemplate.index_template.composed_of).to.eql(['.risk-score-mappings']); + expect(indexTemplate.index_template.template!.mappings?.dynamic).to.eql(false); + expect(indexTemplate.index_template.template!.mappings?._meta?.managed).to.eql(true); + expect(indexTemplate.index_template.template!.mappings?._meta?.namespace).to.eql('default'); + expect(indexTemplate.index_template.template!.mappings?._meta?.kibana?.version).to.be.a( + 'string' + ); + expect(indexTemplate.index_template.template!.settings).to.eql({ + index: { + lifecycle: { + name: '.risk-score-ilm-policy', + }, + mapping: { + total_fields: { + limit: '1000', + }, + }, + hidden: 'true', + auto_expand_replicas: '0-1', + }, + }); + + const dsResponse = await es.indices.get({ + index: indexName, + }); + + const dataStream = Object.values(dsResponse).find((ds) => ds.data_stream === indexName); + + expect(dataStream?.mappings?._meta?.managed).to.eql(true); + expect(dataStream?.mappings?._meta?.namespace).to.eql('default'); + expect(dataStream?.mappings?._meta?.kibana?.version).to.be.a('string'); + expect(dataStream?.mappings?.dynamic).to.eql('false'); + + expect(dataStream?.settings?.index?.lifecycle).to.eql({ + name: '.risk-score-ilm-policy', + }); + + expect(dataStream?.settings?.index?.mapping).to.eql({ + total_fields: { + limit: '1000', + }, + }); + + expect(dataStream?.settings?.index?.hidden).to.eql('true'); + expect(dataStream?.settings?.index?.number_of_shards).to.eql(1); + expect(dataStream?.settings?.index?.auto_expand_replicas).to.eql('0-1'); + }); + + it('should create configuration saved object', async () => { + await initRiskEngine(); + const response = await kibanaServer.savedObjects.find({ + type: 'risk-engine-configuration', + }); + + expect(response?.saved_objects?.[0]?.attributes).to.eql({ + enabled: true, + last_updated_by: 'elastic', + }); + }); + + it('should create configuration saved object only once', async () => { + await initRiskEngine(); + const firstResponse = await kibanaServer.savedObjects.find({ + type: 'risk-engine-configuration', + }); + + await initRiskEngine(); + const secondResponse = await kibanaServer.savedObjects.find({ + type: 'risk-engine-configuration', + }); + + expect(secondResponse?.saved_objects?.length).to.eql(1); + expect(secondResponse?.saved_objects?.[0]?.id).to.eql( + firstResponse?.saved_objects?.[0]?.id + ); + }); + + it('should remove legacy risk score transform if it exists', async () => { + await createTransforms({ es }); + + for (const transformId of legacyTransformIds) { + const tr = await es.transform.getTransform({ + transform_id: transformId, + }); + + expect(tr?.transforms?.[0]?.id).to.eql(transformId); + } + + await initRiskEngine(); + + for (const transformId of legacyTransformIds) { + try { + await es.transform.getTransform({ + transform_id: transformId, + }); + } catch (err) { + expect(err).to.not.be(undefined); + } + } + }); + }); + + describe('status api', () => { + it('should disable / enable risk engige', async () => { + const status1 = await getRiskEngineStatus(); + + expect(status1.body).to.eql({ + risk_engine_status: 'NOT_INSTALLED', + legacy_risk_engine_status: 'NOT_INSTALLED', + last_updated_by: '', + }); + + await initRiskEngine(); + + const status2 = await getRiskEngineStatus(); + + expect(status2.body).to.eql({ + risk_engine_status: 'ENABLED', + legacy_risk_engine_status: 'NOT_INSTALLED', + last_updated_by: 'elastic', + }); + + await disableRiskEngine(); + const status3 = await getRiskEngineStatus(); + + expect(status3.body).to.eql({ + risk_engine_status: 'DISABLED', + legacy_risk_engine_status: 'NOT_INSTALLED', + last_updated_by: 'elastic', + }); + + await enableRiskEngine(); + const status4 = await getRiskEngineStatus(); + + expect(status4.body).to.eql({ + risk_engine_status: 'ENABLED', + legacy_risk_engine_status: 'NOT_INSTALLED', + last_updated_by: 'elastic', + }); + }); + + it('should return status of legacy risk engine', async () => { + await createTransforms({ es }); + const status1 = await getRiskEngineStatus(); + + expect(status1.body).to.eql({ + risk_engine_status: 'NOT_INSTALLED', + legacy_risk_engine_status: 'ENABLED', + last_updated_by: '', + }); + + await initRiskEngine(); + + const status2 = await getRiskEngineStatus(); + + expect(status2.body).to.eql({ + risk_engine_status: 'ENABLED', + legacy_risk_engine_status: 'NOT_INSTALLED', + last_updated_by: 'elastic', + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts index 418e0533bac62..ba9ac53fc74bf 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts @@ -13,7 +13,7 @@ import type { EcsRiskScore, RiskScore, } from '@kbn/security-solution-plugin/server/lib/risk_engine/types'; - +import type { KbnClient } from '@kbn/test'; import { createRule, waitForSignalsToBePresent, @@ -136,3 +136,78 @@ export const waitForRiskScoresToBePresent = async ( log ); }; + +export const getRiskEngineConfigSO = async ({ kibanaServer }: { kibanaServer: KbnClient }) => { + const soResponse = await kibanaServer.savedObjects.find({ + type: 'risk-engine-configuration', + }); + + return soResponse.saved_objects[0]; +}; + +export const cleanRiskEngineConfig = async ({ + kibanaServer, +}: { + kibanaServer: KbnClient; +}): Promise => { + const so = await getRiskEngineConfigSO({ kibanaServer }); + if (so) { + await kibanaServer.savedObjects.delete({ + type: 'risk-engine-configuration', + id: so.id, + }); + } +}; + +export const legacyTransformIds = [ + 'ml_hostriskscore_pivot_transform_default', + 'ml_hostriskscore_latest_transform_default', + 'ml_userriskscore_pivot_transform_default', + 'ml_userriskscore_latest_transform_default', +]; + +export const clearLegacyTranforms = async ({ es }: { es: Client }): Promise => { + const transforms = legacyTransformIds.map((transform) => + es.transform.deleteTransform({ + transform_id: transform, + }) + ); + try { + await Promise.all(transforms); + } catch (e) { + // + } +}; + +export const createTransforms = async ({ es }: { es: Client }): Promise => { + const transforms = legacyTransformIds.map((transform) => + es.transform.putTransform({ + transform_id: transform, + source: { + index: ['.alerts-security.alerts-default'], + }, + dest: { + index: 'ml_host_risk_score_default', + }, + pivot: { + group_by: { + 'host.name': { + terms: { + field: 'host.name', + }, + }, + }, + aggregations: { + '@timestamp': { + max: { + field: '@timestamp', + }, + }, + }, + }, + settings: {}, + }) + ); + + await Promise.all(transforms); +}; From a835c61681bad398498407e789e070e851899e90 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 31 Jul 2023 17:00:07 +0200 Subject: [PATCH 22/46] fix unit tests --- .../risk_engine_data_client.test.ts | 77 +++++-------------- 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 3d3d4e3355e43..c93879ff5b699 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -29,7 +29,7 @@ const getSavedObjectConfiguration = (attributes = {}) => ({ id: 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', namespaces: ['default'], attributes: { - enable: false, + enabled: false, last_updated_by: 'elastic', ...attributes, }, @@ -44,50 +44,13 @@ const getSavedObjectConfiguration = (attributes = {}) => ({ ], }); -const transformStats = { +const transformsMock = { count: 1, transforms: [ { id: 'ml_hostriskscore_pivot_transform_default', - state: 'started', - node: { - id: 'fc9o02bRTi-JPRU1626AaQ', - name: 'macbook.local', - ephemeral_id: 'y-Jx42RvTQi_h7npDlv7sQ', - transport_address: '127.0.0.1:9300', - attributes: {}, - }, - stats: { - pages_processed: 2, - documents_processed: 15, - documents_indexed: 15, - documents_deleted: 0, - trigger_count: 1, - index_time_in_ms: 55, - index_total: 1, - index_failures: 0, - search_time_in_ms: 3, - search_total: 2, - search_failures: 0, - processing_time_in_ms: 0, - processing_total: 2, - delete_time_in_ms: 0, - exponential_avg_checkpoint_duration_ms: 397, - exponential_avg_documents_indexed: 15, - exponential_avg_documents_processed: 15, - }, - checkpointing: { - last: { - checkpoint: 1, - timestamp_millis: 1690546732294, - time_upper_bound_millis: 1690546612294, - }, - changes_last_detected_at: 1690546732292, - last_search_time: 1690546732292, - }, - health: { - status: 'green' as const, - }, + dest: { index: '' }, + source: { index: '' }, }, ], }; @@ -369,11 +332,13 @@ describe('RiskEngineDataClient', () => { const error = new Error('There error'); (createOrUpdateIlmPolicy as jest.Mock).mockRejectedValue(error); - await riskEngineDataClient.initializeResources({ namespace: 'default' }); - - expect(logger.error).toHaveBeenCalledWith( - `Error initializing risk engine resources: ${error.message}` - ); + try { + await riskEngineDataClient.initializeResources({ namespace: 'default' }); + } catch (e) { + expect(logger.error).toHaveBeenCalledWith( + `Error initializing risk engine resources: ${error.message}` + ); + } }); }); @@ -402,7 +367,7 @@ describe('RiskEngineDataClient', () => { it('should return status with enabled true', async () => { mockSavedObjectClient.find.mockResolvedValue( getSavedObjectConfiguration({ - enable: true, + enabled: true, }) ); @@ -433,29 +398,29 @@ describe('RiskEngineDataClient', () => { }); describe('legacy transforms', () => { - it('should fetch transform stats', async () => { + it('should fetch transforms', async () => { await riskEngineDataClient.getStatus({ namespace: 'default', savedObjectsClient: mockSavedObjectClient, }); - expect(esClient.transform.getTransformStats).toHaveBeenCalledTimes(4); - expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(1, { + expect(esClient.transform.getTransform).toHaveBeenCalledTimes(4); + expect(esClient.transform.getTransform).toHaveBeenNthCalledWith(1, { transform_id: 'ml_hostriskscore_pivot_transform_default', }); - expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(2, { + expect(esClient.transform.getTransform).toHaveBeenNthCalledWith(2, { transform_id: 'ml_hostriskscore_latest_transform_default', }); - expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(3, { + expect(esClient.transform.getTransform).toHaveBeenNthCalledWith(3, { transform_id: 'ml_userriskscore_pivot_transform_default', }); - expect(esClient.transform.getTransformStats).toHaveBeenNthCalledWith(4, { + expect(esClient.transform.getTransform).toHaveBeenNthCalledWith(4, { transform_id: 'ml_userriskscore_latest_transform_default', }); }); it('should return that legacy transform enabled if at least on transform exist', async () => { - esClient.transform.getTransformStats.mockResolvedValueOnce(transformStats); + esClient.transform.getTransform.mockResolvedValueOnce(transformsMock); const status = await riskEngineDataClient.getStatus({ namespace: 'default', @@ -509,7 +474,7 @@ describe('RiskEngineDataClient', () => { 'risk-engine-configuration', 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', { - enable: true, + enabled: true, last_updated_by: 'elastic', }, { @@ -555,7 +520,7 @@ describe('RiskEngineDataClient', () => { 'risk-engine-configuration', 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', { - enable: false, + enabled: false, last_updated_by: 'elastic', }, { From e1ed39e776fb2298056a8b9ca56bb9115ee88050 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 31 Jul 2023 17:00:22 +0200 Subject: [PATCH 23/46] Try to enable feature in cypress tests --- .../entity_analytics_management_page.cy.ts | 114 +++++++++--------- .../test/security_solution_cypress/config.ts | 1 - 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index 2e6cd6db033ab..1277a329bda9f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -24,71 +24,75 @@ import { createRule } from '../../tasks/api_calls/rules'; import { updateDateRangeInLocalDatePickers } from '../../tasks/date_picker'; import { fillLocalSearchBar, submitLocalSearch } from '../../tasks/search_bar'; -describe('Entity analytics management page', () => { - before(() => { - cleanKibana(); - cy.task('esArchiverLoad', 'all_users'); - }); - - beforeEach(() => { - login(); - visitWithoutDateRange(ALERTS_URL); - createRule(getNewRule({ query: 'user.name:* or host.name:*', risk_score: 70 })); - visit(ENTITY_ANALYTICS_MANAGEMENT_URL); - }); - - after(() => { - cy.task('esArchiverUnload', 'all_users'); - }); - - it('renders page as expected', () => { - cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); - }); - - describe('Risk preview', () => { - it('risk scores reacts on change in datepicker', () => { - const START_DATE = 'Jan 18, 2019 @ 20:33:29.186'; - const END_DATE = 'Jan 19, 2019 @ 20:33:29.186'; - - cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); - cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); - - updateDateRangeInLocalDatePickers(LOCAL_QUERY_BAR_SELECTOR, START_DATE, END_DATE); - - cy.get(HOST_RISK_PREVIEW_TABLE).contains('No items found'); - cy.get(USER_RISK_PREVIEW_TABLE).contains('No items found'); +describe( + 'Entity analytics management page', + { env: { ftrConfig: { enableExperimental: ['riskScoringRoutesEnabled'] } } }, + () => { + before(() => { + cleanKibana(); + cy.task('esArchiverLoad', 'all_users'); }); - it('risk scores reacts on change in search bar query', () => { - cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); - cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); + beforeEach(() => { + login(); + visitWithoutDateRange(ALERTS_URL); + createRule(getNewRule({ query: 'user.name:* or host.name:*', risk_score: 70 })); + visit(ENTITY_ANALYTICS_MANAGEMENT_URL); + }); - fillLocalSearchBar('host.name: "test-host1"'); - submitLocalSearch(LOCAL_QUERY_BAR_SELECTOR); + after(() => { + cy.task('esArchiverUnload', 'all_users'); + }); - cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 1); - cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).contains('test-host1'); - cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 1); - cy.get(USER_RISK_PREVIEW_TABLE_ROWS).contains('test1'); + it('renders page as expected', () => { + cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); }); - it('show error panel if API returns error and then try to refetch data', () => { - cy.intercept('POST', '/internal/risk_score/preview', { - statusCode: 500, + describe('Risk preview', () => { + it('risk scores reacts on change in datepicker', () => { + const START_DATE = 'Jan 18, 2019 @ 20:33:29.186'; + const END_DATE = 'Jan 19, 2019 @ 20:33:29.186'; + + cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); + cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); + + updateDateRangeInLocalDatePickers(LOCAL_QUERY_BAR_SELECTOR, START_DATE, END_DATE); + + cy.get(HOST_RISK_PREVIEW_TABLE).contains('No items found'); + cy.get(USER_RISK_PREVIEW_TABLE).contains('No items found'); }); - cy.get(RISK_PREVIEW_ERROR).contains('Preview failed'); + it('risk scores reacts on change in search bar query', () => { + cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); + cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); - cy.intercept('POST', '/internal/risk_score/preview', { - statusCode: 200, - body: { - scores: { host: [], user: [] }, - }, + fillLocalSearchBar('host.name: "test-host1"'); + submitLocalSearch(LOCAL_QUERY_BAR_SELECTOR); + + cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 1); + cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).contains('test-host1'); + cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 1); + cy.get(USER_RISK_PREVIEW_TABLE_ROWS).contains('test1'); }); - cy.get(RISK_PREVIEW_ERROR_BUTTON).click(); + it('show error panel if API returns error and then try to refetch data', () => { + cy.intercept('POST', '/internal/risk_score/preview', { + statusCode: 500, + }); + + cy.get(RISK_PREVIEW_ERROR).contains('Preview failed'); + + cy.intercept('POST', '/internal/risk_score/preview', { + statusCode: 200, + body: { + scores: { host: [], user: [] }, + }, + }); - cy.get(RISK_PREVIEW_ERROR).should('not.exist'); + cy.get(RISK_PREVIEW_ERROR_BUTTON).click(); + + cy.get(RISK_PREVIEW_ERROR).should('not.exist'); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index dd9844abe03e3..c50a5403a166c 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -49,7 +49,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'alertDetailsPageEnabled', 'chartEmbeddablesEnabled', - 'riskScoringRoutesEnabled', ])}`, // mock cloud to enable the guided onboarding tour in e2e tests '--xpack.cloud.id=test', From e6dcad45cb47e1d2a1523bf47077980920754da5 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Mon, 31 Jul 2023 17:11:41 +0200 Subject: [PATCH 24/46] udpate mappings --- packages/kbn-check-mappings-update-cli/current_mappings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 491c262c5c61a..7b688f40d3796 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2937,7 +2937,7 @@ }, "risk-engine-configuration": { "properties": { - "enable": { + "enabled": { "type": "boolean" }, "last_updated_by": { From 8373a58a2be9fb2eb40c8b3cbca4c7485685f881 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 31 Jul 2023 17:32:56 +0000 Subject: [PATCH 25/46] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- x-pack/performance/journeys/apm_service_inventory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/performance/journeys/apm_service_inventory.ts b/x-pack/performance/journeys/apm_service_inventory.ts index d98b151c000d6..980c193f67bbb 100644 --- a/x-pack/performance/journeys/apm_service_inventory.ts +++ b/x-pack/performance/journeys/apm_service_inventory.ts @@ -35,7 +35,7 @@ export const journey = new Journey({ ); }, ftrConfigPath: 'x-pack/performance/configs/apm_config.ts', - skipped: true + skipped: true, }) .step('Navigate to Service Inventory Page', async ({ page, kbnUrl }) => { await page.goto(kbnUrl.get(`app/apm/services`)); From e72b7d4dff544262cb89e4d5202a86cde615f6a9 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Tue, 1 Aug 2023 10:25:10 +0200 Subject: [PATCH 26/46] Refactoring --- .../risk_engine_data_client.test.ts | 14 +- .../risk_engine/risk_engine_data_client.ts | 170 +++--------------- .../server/lib/risk_engine/types.ts | 16 ++ .../utils/risk_engine_transforms.ts | 73 ++++++++ .../utils/saved_object_configuration.ts | 82 +++++++++ 5 files changed, 196 insertions(+), 159 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts create mode 100644 x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index c93879ff5b699..eafdc68f7ea37 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -18,6 +18,7 @@ import { import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import { RiskEngineDataClient } from './risk_engine_data_client'; import { createDataStream } from './utils/create_datastream'; +import * as savedObjectConfig from './utils/saved_object_configuration'; const getSavedObjectConfiguration = (attributes = {}) => ({ page: 1, @@ -536,11 +537,7 @@ describe('RiskEngineDataClient', () => { 'initializeResources' ); const enableRiskEngineMock = jest.spyOn(RiskEngineDataClient.prototype, 'enableRiskEngine'); - const initSavedObjectsMock = jest.spyOn( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - RiskEngineDataClient.prototype as any, - 'initSavedObjects' - ); + const disableLegacyRiskEngineMock = jest.spyOn( RiskEngineDataClient.prototype, 'disableLegacyRiskEngine' @@ -556,15 +553,14 @@ describe('RiskEngineDataClient', () => { return Promise.resolve(getSavedObjectConfiguration().saved_objects[0]); }); - initSavedObjectsMock.mockImplementation(() => { - return Promise.resolve(); + jest.spyOn(savedObjectConfig, 'initSavedObjects').mockImplementation(() => { + return Promise.resolve(getSavedObjectConfiguration().saved_objects[0]); }); }); afterEach(() => { initializeResourcesMock.mockReset(); enableRiskEngineMock.mockReset(); - initSavedObjectsMock.mockReset(); disableLegacyRiskEngineMock.mockReset(); }); @@ -644,7 +640,7 @@ describe('RiskEngineDataClient', () => { }); it('should catch error for initSavedObjects and stop', async () => { - initSavedObjectsMock.mockImplementationOnce(() => { + jest.spyOn(savedObjectConfig, 'initSavedObjects').mockImplementationOnce(() => { throw new Error('Error initSavedObjects'); }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 8a61615c66380..5e51da0fb3ef0 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -7,11 +7,7 @@ import type { Metadata } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; -import type { - ClusterPutComponentTemplateRequest, - TransformGetTransformResponse, - TransformGetTransformTransformSummary, -} from '@elastic/elasticsearch/lib/api/types'; +import type { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; import { createOrUpdateComponentTemplate, createOrUpdateIlmPolicy, @@ -19,12 +15,7 @@ import { } from '@kbn/alerting-plugin/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; -import type { - Logger, - ElasticsearchClient, - SavedObjectsClientContract, - SavedObject, -} from '@kbn/core/server'; +import type { Logger, ElasticsearchClient } from '@kbn/core/server'; import { riskScoreFieldMap, @@ -37,26 +28,21 @@ import { import { createDataStream } from './utils/create_datastream'; import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer'; import { RiskEngineDataWriter } from './risk_engine_data_writer'; -import { riskEngineConfigurationTypeName } from './saved_object'; import type { InitRiskEngineResult } from '../../../common/risk_engine/types'; import { RiskEngineStatus } from '../../../common/risk_engine/types'; +import { getLegacyTransforms, removeLegacyTransofrms } from './utils/risk_engine_transforms'; import { - getRiskScorePivotTransformId, - getRiskScoreLatestTransformId, -} from '../../../common/utils/risk_score_modules'; -import { RiskScoreEntity } from '../../../common/search_strategy'; -interface SavedObjectsClients { - savedObjectsClient: SavedObjectsClientContract; -} + updateSavedObjectAttribute, + getConfiguration, + initSavedObjects, +} from './utils/saved_object_configuration'; +import type { UpdateConfigOpts, SavedObjectsClients } from './types'; + interface InitOpts extends SavedObjectsClients { namespace: string; user: AuthenticatedUser | null | undefined; } -interface UpdateConfigOpts extends SavedObjectsClients { - user: AuthenticatedUser | null | undefined; -} - interface InitializeRiskEngineResourcesOpts { namespace?: string; } @@ -67,11 +53,6 @@ interface RiskEngineDataClientOpts { elasticsearchClientPromise: Promise; } -interface Configuration { - enabled: boolean; - last_updated_by: string; -} - export class RiskEngineDataClient { private writerCache: Map = new Map(); constructor(private readonly options: RiskEngineDataClientOpts) {} @@ -101,7 +82,7 @@ export class RiskEngineDataClient { } try { - await this.initSavedObjects({ savedObjectsClient, user }); + await initSavedObjects({ savedObjectsClient, user }); result.riskEngineConfigurationCreated = true; } catch (e) { result.errors.push(e.message); @@ -155,7 +136,7 @@ export class RiskEngineDataClient { public async enableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to run task - return this.updateSavedObjectAttribute({ + return updateSavedObjectAttribute({ savedObjectsClient, user, attributes: { @@ -167,7 +148,7 @@ export class RiskEngineDataClient { public async disableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { // code to stop task - return this.updateSavedObjectAttribute({ + return updateSavedObjectAttribute({ savedObjectsClient, user, attributes: { @@ -176,38 +157,6 @@ export class RiskEngineDataClient { }); } - private async updateSavedObjectAttribute({ - savedObjectsClient, - attributes, - user, - }: UpdateConfigOpts & { - attributes: { - enabled: boolean; - }; - }) { - const savedObjectConfiguration = await this.getConfigurationSavedObject({ - savedObjectsClient, - }); - - if (!savedObjectConfiguration) { - throw new Error('There no saved object configuration for risk engine'); - } - - const result = await savedObjectsClient.update( - riskEngineConfigurationTypeName, - savedObjectConfiguration.id, - { - ...attributes, - last_updated_by: user?.username ?? '', - }, - { - refresh: 'wait_for', - } - ); - - return result; - } - public async disableLegacyRiskEngine({ namespace }: { namespace: string }) { const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); @@ -216,16 +165,10 @@ export class RiskEngineDataClient { } const esClient = await this.options.elasticsearchClientPromise; - const transforms = await this.getLegacyTransforms({ namespace }); - - const stopTransformRequests = transforms.map((t) => - esClient.transform.deleteTransform({ - transform_id: t.id, - force: true, - }) - ); - - await Promise.allSettled(stopTransformRequests); + await removeLegacyTransofrms({ + esClient, + namespace, + }); const newlegacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); @@ -233,7 +176,7 @@ export class RiskEngineDataClient { } private async getLastUpdatedBy({ savedObjectsClient }: SavedObjectsClients) { - const configuration = await this.getConfiguration({ savedObjectsClient }); + const configuration = await getConfiguration({ savedObjectsClient }); if (configuration) { return configuration.last_updated_by; @@ -243,7 +186,7 @@ export class RiskEngineDataClient { } private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) { - const configuration = await this.getConfiguration({ savedObjectsClient }); + const configuration = await getConfiguration({ savedObjectsClient }); if (configuration) { return configuration.enabled ? RiskEngineStatus.ENABLED : RiskEngineStatus.DISABLED; @@ -252,41 +195,9 @@ export class RiskEngineDataClient { return RiskEngineStatus.NOT_INSTALLED; } - private async getLegacyTransforms({ namespace }: { namespace: string }) { - const esClient = await this.options.elasticsearchClientPromise; - - const getTransformStatsRequests: Array> = []; - [RiskScoreEntity.host, RiskScoreEntity.user].forEach((entity) => { - getTransformStatsRequests.push( - esClient.transform.getTransform({ - transform_id: getRiskScorePivotTransformId(entity, namespace), - }) - ); - getTransformStatsRequests.push( - esClient.transform.getTransform({ - transform_id: getRiskScoreLatestTransformId(entity, namespace), - }) - ); - }); - - const result = await Promise.allSettled(getTransformStatsRequests); - - const fulfuletGetTransformStats = result - .filter((r) => r.status === 'fulfilled') - .map((r) => (r as PromiseFulfilledResult).value); - - const transforms = fulfuletGetTransformStats.reduce((acc, val) => { - if (val.transforms) { - return [...acc, ...val.transforms]; - } - return acc; - }, [] as TransformGetTransformTransformSummary[]); - - return transforms; - } - private async getLegacyStatus({ namespace }: { namespace: string }) { - const transforms = await this.getLegacyTransforms({ namespace }); + const esClient = await this.options.elasticsearchClientPromise; + const transforms = await getLegacyTransforms({ namespace, esClient }); if (transforms.length === 0) { return RiskEngineStatus.NOT_INSTALLED; @@ -295,47 +206,6 @@ export class RiskEngineDataClient { return RiskEngineStatus.ENABLED; } - private async getConfigurationSavedObject({ - savedObjectsClient, - }: SavedObjectsClients): Promise | undefined> { - const savedObjectsResponse = await savedObjectsClient.find({ - type: riskEngineConfigurationTypeName, - }); - return savedObjectsResponse.saved_objects?.[0]; - } - - private async getConfiguration({ - savedObjectsClient, - }: SavedObjectsClients): Promise { - try { - const savedObjectConfiguration = await this.getConfigurationSavedObject({ - savedObjectsClient, - }); - const configuration = savedObjectConfiguration?.attributes; - - if (configuration) { - return configuration; - } - - return null; - } catch (e) { - this.options.logger.error(`Can't get saved object configuration: ${e.message}`); - return null; - } - } - - private async initSavedObjects({ savedObjectsClient, user }: UpdateConfigOpts) { - const configuration = await this.getConfiguration({ savedObjectsClient }); - if (configuration) { - return configuration; - } - const result = await savedObjectsClient.create(riskEngineConfigurationTypeName, { - enabled: false, - last_updated_by: user?.username ?? '', - }); - return result; - } - public async initializeResources({ namespace = DEFAULT_NAMESPACE_STRING, }: InitializeRiskEngineResourcesOpts) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index 236a6c9f1ac35..f2cf7e8ba4893 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -5,7 +5,9 @@ * 2.0. */ +import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { MappingRuntimeFields, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { AfterKey, @@ -15,6 +17,7 @@ import type { RiskWeights, } from '../../../common/risk_engine'; import type { RiskEngineStatus } from '../../../common/risk_engine/types'; + export interface CalculateScoresParams { afterKeys: AfterKeys; debug?: boolean; @@ -160,3 +163,16 @@ export interface RiskScoreBucket { }; inputs: SearchResponse; } + +export interface RiskEngineConfiguration { + enabled: boolean; + last_updated_by: string; +} + +export interface SavedObjectsClients { + savedObjectsClient: SavedObjectsClientContract; +} + +export interface UpdateConfigOpts extends SavedObjectsClients { + user: AuthenticatedUser | null | undefined; +} diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts new file mode 100644 index 0000000000000..e2dd3a021753d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts @@ -0,0 +1,73 @@ +/* + * 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 { ElasticsearchClient } from '@kbn/core/server'; +import type { + TransformGetTransformResponse, + TransformGetTransformTransformSummary, +} from '@elastic/elasticsearch/lib/api/types'; +import { RiskScoreEntity } from '../../../../common/search_strategy'; +import { + getRiskScorePivotTransformId, + getRiskScoreLatestTransformId, +} from '../../../../common/utils/risk_score_modules'; + +export const getLegacyTransforms = async ({ + namespace, + esClient, +}: { + namespace: string; + esClient: ElasticsearchClient; +}) => { + const getTransformStatsRequests: Array> = []; + [RiskScoreEntity.host, RiskScoreEntity.user].forEach((entity) => { + getTransformStatsRequests.push( + esClient.transform.getTransform({ + transform_id: getRiskScorePivotTransformId(entity, namespace), + }) + ); + getTransformStatsRequests.push( + esClient.transform.getTransform({ + transform_id: getRiskScoreLatestTransformId(entity, namespace), + }) + ); + }); + + const result = await Promise.allSettled(getTransformStatsRequests); + + const fulfuletGetTransformStats = result + .filter((r) => r.status === 'fulfilled') + .map((r) => (r as PromiseFulfilledResult).value); + + const transforms = fulfuletGetTransformStats.reduce((acc, val) => { + if (val.transforms) { + return [...acc, ...val.transforms]; + } + return acc; + }, [] as TransformGetTransformTransformSummary[]); + + return transforms; +}; + +export const removeLegacyTransofrms = async ({ + namespace, + esClient, +}: { + namespace: string; + esClient: ElasticsearchClient; +}) => { + const transforms = await getLegacyTransforms({ namespace, esClient }); + + const stopTransformRequests = transforms.map((t) => + esClient.transform.deleteTransform({ + transform_id: t.id, + force: true, + }) + ); + + await Promise.allSettled(stopTransformRequests); +}; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts new file mode 100644 index 0000000000000..5410eea5578f2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts @@ -0,0 +1,82 @@ +/* + * 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 { SavedObject } from '@kbn/core/server'; + +import type { RiskEngineConfiguration, UpdateConfigOpts, SavedObjectsClients } from '../types'; +import { riskEngineConfigurationTypeName } from '../saved_object'; + +const getConfigurationSavedObject = async ({ + savedObjectsClient, +}: SavedObjectsClients): Promise | undefined> => { + const savedObjectsResponse = await savedObjectsClient.find({ + type: riskEngineConfigurationTypeName, + }); + return savedObjectsResponse.saved_objects?.[0]; +}; + +export const updateSavedObjectAttribute = async ({ + savedObjectsClient, + attributes, + user, +}: UpdateConfigOpts & { + attributes: { + enabled: boolean; + }; +}) => { + const savedObjectConfiguration = await getConfigurationSavedObject({ + savedObjectsClient, + }); + + if (!savedObjectConfiguration) { + throw new Error('There no saved object configuration for risk engine'); + } + + const result = await savedObjectsClient.update( + riskEngineConfigurationTypeName, + savedObjectConfiguration.id, + { + ...attributes, + last_updated_by: user?.username ?? '', + }, + { + refresh: 'wait_for', + } + ); + + return result; +}; + +export const initSavedObjects = async ({ savedObjectsClient, user }: UpdateConfigOpts) => { + const configuration = await getConfigurationSavedObject({ savedObjectsClient }); + if (configuration) { + return configuration; + } + const result = await savedObjectsClient.create(riskEngineConfigurationTypeName, { + enabled: false, + last_updated_by: user?.username ?? '', + }); + return result; +}; + +export const getConfiguration = async ({ + savedObjectsClient, +}: SavedObjectsClients): Promise => { + try { + const savedObjectConfiguration = await getConfigurationSavedObject({ + savedObjectsClient, + }); + const configuration = savedObjectConfiguration?.attributes; + + if (configuration) { + return configuration; + } + + return null; + } catch (e) { + return null; + } +}; From 38e23bccd3335c616fa02c32a49d18b0fd2203d6 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Tue, 1 Aug 2023 11:11:17 +0200 Subject: [PATCH 27/46] Fix jest tests --- .../migrations/group2/check_registered_types.test.ts | 1 + .../saved_objects/migrations/group3/type_registrations.test.ts | 1 + .../saved_objects/migrations/group5/dot_kibana_split.test.ts | 1 + .../security_and_spaces/group10/risk_engine/utils.ts | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index 063ed46c443f6..ab2ec7ccf2ffe 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -126,6 +126,7 @@ describe('checking migration metadata changes on all registered SO types', () => "osquery-pack-asset": "b14101d3172c4b60eb5404696881ce5275c84152", "osquery-saved-query": "44f1161e165defe3f9b6ad643c68c542a765fcdb", "query": "8db5d48c62d75681d80d82a42b5642f60d068202", + "risk-engine-configuration": "4dbbc5fd0d1bacc4d76e9c63dfb7887fb6d57079", "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", "sample-data-telemetry": "37441b12f5b0159c2d6d5138a494c9f440e950b5", "search": "8d5184dd5b986d57250b6ffd9ae48a1925e4c7a3", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts index feed5a05dcd31..3235c6cfa057e 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/type_registrations.test.ts @@ -104,6 +104,7 @@ const previouslyRegisteredTypes = [ 'search-telemetry', 'security-rule', 'security-solution-signals-migration', + 'risk-engine-configuration', 'server', 'siem-detection-engine-rule-actions', 'siem-detection-engine-rule-execution-info', diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts index 30888521d651e..32ac61f0d2711 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts @@ -246,6 +246,7 @@ describe('split .kibana index into multiple system indices', () => { "osquery-pack-asset", "osquery-saved-query", "query", + "risk-engine-configuration", "rules-settings", "sample-data-telemetry", "search-session", diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts index ba9ac53fc74bf..980615457323d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts @@ -142,7 +142,7 @@ export const getRiskEngineConfigSO = async ({ kibanaServer }: { kibanaServer: Kb type: 'risk-engine-configuration', }); - return soResponse.saved_objects[0]; + return soResponse?.saved_objects?.[0]; }; export const cleanRiskEngineConfig = async ({ From 450871a94df530d003039d72317ac6d924af10ae Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Tue, 1 Aug 2023 13:55:19 +0200 Subject: [PATCH 28/46] Try to fix types --- .../src/kbn_client/kbn_client_saved_objects.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index ca6fc03745fb8..0741890e0a5f7 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -8,7 +8,10 @@ import { chunk } from 'lodash'; import type { ToolingLog } from '@kbn/tooling-log'; -import type { SavedObjectsBulkDeleteResponse } from '@kbn/core-saved-objects-api-server'; +import type { + SavedObjectsBulkDeleteResponse, + SavedObjectsFindResponse, +} from '@kbn/core-saved-objects-api-server'; import { KbnClientRequester, uriencode } from './kbn_client_requester'; @@ -30,13 +33,6 @@ interface SavedObjectResponse> { version?: string; } -interface SavedObjectsFindResponse> { - page: number; - per_page: number; - total: number; - saved_objects: Array>; -} - interface GetFindOptions { type: string; space?: string; From 5dffa4d3839926658c09da80dd92961335d1ffed Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Wed, 2 Aug 2023 07:33:43 +0200 Subject: [PATCH 29/46] Change enable risk button --- .../risk_score_enable_button.tsx | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx index ec6c6ceb02192..1259b0a73755a 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx @@ -16,6 +16,9 @@ import type { inputsModel } from '../../../../common/store'; import { REQUEST_NAMES, useFetch } from '../../../../common/hooks/use_fetch'; import { useRiskScoreToastContent } from './use_risk_score_toast_content'; import { installRiskScoreModule } from './utils'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { SecuritySolutionLinkButton } from '../../../../common/components/links'; +import { SecurityPageName } from '../../../../../common/constants'; const RiskScoreEnableButtonComponent = ({ refetch, @@ -35,6 +38,7 @@ const RiskScoreEnableButtonComponent = ({ const { http, notifications, theme, dashboard } = useKibana().services; const { renderDocLink, renderDashboardLink } = useRiskScoreToastContent(riskScoreEntity); const { fetch, isLoading } = useFetch(REQUEST_NAMES.ENABLE_RISK_SCORE, installRiskScoreModule); + const isRiskEngineEnabled = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled') const onBoardingRiskScore = useCallback(() => { fetch({ @@ -64,19 +68,34 @@ const RiskScoreEnableButtonComponent = ({ ]); return ( - - - + <> + {isRiskEngineEnabled ? ( + + + + ) : ( + + + + )} + ); }; From ca8329b98dec3e34e1551d09bc96e0250eed8bff Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Wed, 2 Aug 2023 07:33:49 +0200 Subject: [PATCH 30/46] Add cy tests --- .../entity_analytics_management_page.cy.ts | 70 ++++++++++++++++++- .../screens/entity_analytics_management.ts | 12 ++++ .../cypress/tasks/entity_analytics.ts | 17 +++++ .../components/risk_score_enable_section.tsx | 27 +++++-- 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index 1277a329bda9f..cc2dc240d88c9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -14,15 +14,33 @@ import { RISK_PREVIEW_ERROR, RISK_PREVIEW_ERROR_BUTTON, LOCAL_QUERY_BAR_SELECTOR, + RISK_SCORE_ERROR_PANEL, + RISK_SCORE_STATUS, } from '../../screens/entity_analytics_management'; +import { + deleteRiskScore, + interceptInstallRiskScoreModule, + waitForInstallRiskScoreModule, +} from '../../tasks/api_calls/risk_scores'; +import { clickEnableRiskScore } from '../../tasks/risk_scores'; +import { RiskScoreEntity } from '../../tasks/risk_scores/common'; import { login, visit, visitWithoutDateRange } from '../../tasks/login'; import { cleanKibana } from '../../tasks/common'; -import { ENTITY_ANALYTICS_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; +import { + ENTITY_ANALYTICS_MANAGEMENT_URL, + ALERTS_URL, + ENTITY_ANALYTICS_URL, +} from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { createRule } from '../../tasks/api_calls/rules'; import { updateDateRangeInLocalDatePickers } from '../../tasks/date_picker'; import { fillLocalSearchBar, submitLocalSearch } from '../../tasks/search_bar'; +import { + riskEngineStatusChange, + updateRiskEngine, + updateRiskEngineConfirm, +} from '../../tasks/entity_analytics'; describe( 'Entity analytics management page', @@ -94,5 +112,55 @@ describe( cy.get(RISK_PREVIEW_ERROR).should('not.exist'); }); }); + + describe('Risk engine', () => { + it('should init, disable and enable risk engine', () => { + cy.get(RISK_SCORE_STATUS).should('have.text', 'Off'); + + // init + riskEngineStatusChange(); + + cy.get(RISK_SCORE_STATUS).should('have.text', 'On'); + + // disable + riskEngineStatusChange(); + + cy.get(RISK_SCORE_STATUS).should('have.text', 'Off'); + + // enable + riskEngineStatusChange(); + + cy.get(RISK_SCORE_STATUS).should('have.text', 'On'); + }); + + it('should show error panel if API returns error ', () => { + cy.get(RISK_SCORE_STATUS).should('have.text', 'Off'); + + cy.intercept('POST', '/internal/risk_score/engine/init', { + statusCode: 500, + }); + + cy.get(RISK_SCORE_STATUS).should('have.text', 'On'); + + cy.get(RISK_SCORE_ERROR_PANEL).contains('Error enabling risk engine'); + }); + + it('should update if there legacy risk score installed', () => { + visit(ENTITY_ANALYTICS_URL); + interceptInstallRiskScoreModule(); + clickEnableRiskScore(RiskScoreEntity.host); + waitForInstallRiskScoreModule(); + visit(ENTITY_ANALYTICS_MANAGEMENT_URL); + + cy.get(RISK_SCORE_STATUS).should('not.exist'); + + updateRiskEngine(); + updateRiskEngineConfirm(); + + cy.get(RISK_SCORE_STATUS).should('have.text', 'On'); + + deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId: 'default' }); + }); + }); } ); diff --git a/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts b/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts index f4c0fa83db1ea..c8dd52a570b18 100644 --- a/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts +++ b/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts @@ -22,3 +22,15 @@ export const RISK_PREVIEW_ERROR = '[data-test-subj="risk-preview-error"]'; export const RISK_PREVIEW_ERROR_BUTTON = '[data-test-subj="risk-preview-error-button"]'; export const LOCAL_QUERY_BAR_SELECTOR = getDataTestSubjectSelector('risk-score-preview-search-bar'); + +export const RISK_SCORE_ERROR_PANEL = '[data-test-subj="riskScoreErrorPanel"]'; + +export const RISK_SCORE_UPDATE_CANCEL = '[data-test-subj="riskScoreUpdateCancel"]'; + +export const RISK_SCORE_UPDATE_CONFIRM = '[data-test-subj="riskScoreUpdateConfirm"]'; + +export const RISK_SCORE_UDATE_BUTTON = '[data-test-subj="riskScoreUpdateButton"]'; + +export const RISK_SCORE_STATUS = '[data-test-subj="riskScoreStatus"]'; + +export const RISK_SCORE_SWITCH = '[data-test-subj="riskScoreSwitch"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts index 156b92df634d0..6f32ba06ef19b 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts @@ -12,6 +12,11 @@ import { ANOMALIES_TABLE_NEXT_PAGE_BUTTON, } from '../screens/entity_analytics'; import { ENTITY_ANALYTICS_URL } from '../urls/navigation'; +import { + RISK_SCORE_UPDATE_CONFIRM, + RISK_SCORE_UDATE_BUTTON, + RISK_SCORE_SWITCH, +} from '../screens/entity_analytics_management'; import { visit } from './login'; @@ -31,3 +36,15 @@ export const enableJob = () => { export const navigateToNextPage = () => { cy.get(ANOMALIES_TABLE_NEXT_PAGE_BUTTON).click(); }; + +export const riskEngineStatusChange = () => { + cy.get(RISK_SCORE_SWITCH).click(); +}; + +export const updateRiskEngine = () => { + cy.get(RISK_SCORE_UDATE_BUTTON).click(); +}; + +export const updateRiskEngineConfirm = () => { + cy.get(RISK_SCORE_UPDATE_CONFIRM).click(); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 7f5b6b79bd1f2..12271ff1efd08 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -60,7 +60,12 @@ const MIN_WIDTH_TO_PREVENT_LABEL_FROM_MOVING = '50px'; const RiskScoreErrorPanel = ({ errors }: { errors: string[] }) => ( <> - +

{i18n.ERROR_PANEL_MESSAGE}

@@ -149,10 +154,19 @@ export const RiskScoreEnableSection = () => { - + {i18n.UPDATE_RISK_ENGINE_MODAL_BUTTON_NO} - initRiskEngineMutation.mutate()} fill> + initRiskEngineMutation.mutate()} + fill + > {i18n.UPDATE_RISK_ENGINE_MODAL_BUTTON_YES} @@ -208,6 +222,7 @@ export const RiskScoreEnableSection = () => { disabled={initRiskEngineMutation.isLoading} color={'primary'} onClick={showModal} + data-test-subj="riskScoreUpdateButton" > {i18n.START_UPDATE} @@ -216,7 +231,10 @@ export const RiskScoreEnableSection = () => { {!isUpdateAvailable && ( {isLoading && } - + {currentRiskEngineStatus === RiskEngineStatus.ENABLED ? ( {i18n.RISK_SCORE_MODULE_STATUS_ON} ) : ( @@ -226,6 +244,7 @@ export const RiskScoreEnableSection = () => { Date: Wed, 2 Aug 2023 10:02:02 +0200 Subject: [PATCH 31/46] PR fixes --- .../src/kbn_client/kbn_client_saved_objects.ts | 4 ++-- .../public/entity_analytics/api/api.ts | 12 ++++++------ .../hooks/use_disable_risk_engine_mutation.ts | 6 +++--- .../api/hooks/use_enable_risk_engine_mutation.ts | 6 +++--- .../lib/risk_engine/risk_engine_data_client.ts | 6 +++--- .../risk_engine/routes/risk_engine_init_route.ts | 8 ++++---- .../risk_engine_configuration_type.ts | 1 + .../server/lib/risk_engine/types.ts | 16 +++------------- .../risk_engine/utils/risk_engine_transforms.ts | 16 ++++++---------- .../utils/saved_object_configuration.ts | 13 +++++++++++-- .../group10/risk_engine/risk_engine_status.ts | 11 ++++++----- .../group10/risk_engine/utils.ts | 7 ++++--- 12 files changed, 52 insertions(+), 54 deletions(-) diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 0741890e0a5f7..75e093b047158 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -33,7 +33,7 @@ interface SavedObjectResponse> { version?: string; } -interface GetFindOptions { +interface FindOptions { type: string; space?: string; } @@ -163,7 +163,7 @@ export class KbnClientSavedObjects { /** * Find saved objects */ - public async find>(options: GetFindOptions) { + public async find>(options: FindOptions) { this.log.debug('Find saved objects: %j', options); const { data } = await this.requester.request>({ diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index 4026f46a7d797..e6c261ab6588b 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -16,10 +16,10 @@ import { import { KibanaServices } from '../../common/lib/kibana'; import type { CalculateScoresResponse, - GetRiskEngineEnableResponse, + EnableRiskEngineResponse, GetRiskEngineStatusResponse, InitRiskEngineResponse, - GetRiskEngineDisableResponse, + DisableRiskEngineResponse, } from '../../../server/lib/risk_engine/types'; import type { RiskScorePreviewRequestSchema } from '../../../common/risk_engine/risk_score_preview/request_schema'; @@ -66,8 +66,8 @@ export const initRiskEngine = async (): Promise => { /** * Enable risk score engine */ -export const enableRiskEngine = async (): Promise => { - return KibanaServices.get().http.fetch(RISK_ENGINE_ENABLE_URL, { +export const enableRiskEngine = async (): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_ENABLE_URL, { method: 'POST', }); }; @@ -75,8 +75,8 @@ export const enableRiskEngine = async (): Promise = /** * Disable risk score engine */ -export const disableRiskEngine = async (): Promise => { - return KibanaServices.get().http.fetch(RISK_ENGINE_DISABLE_URL, { +export const disableRiskEngine = async (): Promise => { + return KibanaServices.get().http.fetch(RISK_ENGINE_DISABLE_URL, { method: 'POST', }); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts index 801677a47da70..997e93136339e 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_disable_risk_engine_mutation.ts @@ -9,8 +9,8 @@ import { useMutation } from '@tanstack/react-query'; import { disableRiskEngine } from '../api'; import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { - GetRiskEngineDisableResponse, - EnableDisableRiskEngineResponse, + EnableRiskEngineResponse, + EnableDisableRiskEngineErrorResponse, } from '../../../../server/lib/risk_engine/types'; export const DISABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'DISABLE_RISK_ENGINE']; @@ -18,7 +18,7 @@ export const DISABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'DISABLE_RISK_ENGINE']; export const useDisableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatusQuery(); - return useMutation( + return useMutation( () => disableRiskEngine(), { ...options, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts index 72a222939f225..3875a79399dcc 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/hooks/use_enable_risk_engine_mutation.ts @@ -9,15 +9,15 @@ import { useMutation } from '@tanstack/react-query'; import { enableRiskEngine } from '../api'; import { useInvalidateRiskEngineStatusQuery } from './use_risk_engine_status'; import type { - GetRiskEngineEnableResponse, - EnableDisableRiskEngineResponse, + EnableRiskEngineResponse, + EnableDisableRiskEngineErrorResponse, } from '../../../../server/lib/risk_engine/types'; export const ENABLE_RISK_ENGINE_MUTATION_KEY = ['POST', 'ENABLE_RISK_ENGINE']; export const useEnableRiskEngineMutation = (options?: UseMutationOptions<{}>) => { const invalidateRiskEngineStatusQuery = useInvalidateRiskEngineStatusQuery(); - return useMutation( + return useMutation( () => enableRiskEngine(), { ...options, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 5e51da0fb3ef0..6f113bc99aa01 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -30,13 +30,13 @@ import type { RiskEngineDataWriter as Writer } from './risk_engine_data_writer'; import { RiskEngineDataWriter } from './risk_engine_data_writer'; import type { InitRiskEngineResult } from '../../../common/risk_engine/types'; import { RiskEngineStatus } from '../../../common/risk_engine/types'; -import { getLegacyTransforms, removeLegacyTransofrms } from './utils/risk_engine_transforms'; +import { getLegacyTransforms, removeLegacyTransforms } from './utils/risk_engine_transforms'; import { updateSavedObjectAttribute, getConfiguration, initSavedObjects, } from './utils/saved_object_configuration'; -import type { UpdateConfigOpts, SavedObjectsClients } from './types'; +import type { UpdateConfigOpts, SavedObjectsClients } from './utils/saved_object_configuration'; interface InitOpts extends SavedObjectsClients { namespace: string; @@ -165,7 +165,7 @@ export class RiskEngineDataClient { } const esClient = await this.options.elasticsearchClientPromise; - await removeLegacyTransofrms({ + await removeLegacyTransforms({ esClient, namespace, }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index 4db9db372999d..575e04498a5be 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -41,7 +41,7 @@ export const riskEngineInitRoute = ( user, }); - const initResultSnakeCase = { + const initResultResponse = { risk_engine_enabled: initResult.riskEngineEnabled, risk_engine_resources_installed: initResult.riskEngineResourcesInstalled, risk_engine_configuration_created: initResult.riskEngineConfigurationCreated, @@ -57,12 +57,12 @@ export const riskEngineInitRoute = ( return siemResponse.error({ statusCode: 400, body: { - message: initResultSnakeCase.errors.join('\n'), - full_error: initResultSnakeCase, + message: initResultResponse.errors.join('\n'), + full_error: initResultResponse, }, }); } - return response.ok({ body: { result: initResultSnakeCase } }); + return response.ok({ body: { result: initResultResponse } }); } catch (e) { const error = transformError(e); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts index cb5c2f700b31c..d09d3170c4c3c 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts @@ -11,6 +11,7 @@ import type { SavedObjectsType } from '@kbn/core/server'; export const riskEngineConfigurationTypeName = 'risk-engine-configuration'; export const riskEngineConfigurationTypeMappings: SavedObjectsType['mappings'] = { + dynamic: false, properties: { enabled: { type: 'boolean', diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index f2cf7e8ba4893..9bd35bfa734cc 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -5,9 +5,7 @@ * 2.0. */ -import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { MappingRuntimeFields, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import type { AfterKey, @@ -86,7 +84,7 @@ export interface InitRiskEngineError { }; } -export interface EnableDisableRiskEngineResponse { +export interface EnableDisableRiskEngineErrorResponse { body: { message: { message: string; @@ -95,11 +93,11 @@ export interface EnableDisableRiskEngineResponse { }; } -export interface GetRiskEngineEnableResponse { +export interface EnableRiskEngineResponse { success: boolean; } -export interface GetRiskEngineDisableResponse { +export interface DisableRiskEngineResponse { success: boolean; } @@ -168,11 +166,3 @@ export interface RiskEngineConfiguration { enabled: boolean; last_updated_by: string; } - -export interface SavedObjectsClients { - savedObjectsClient: SavedObjectsClientContract; -} - -export interface UpdateConfigOpts extends SavedObjectsClients { - user: AuthenticatedUser | null | undefined; -} diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts index e2dd3a021753d..e79b93a5267b7 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/risk_engine_transforms.ts @@ -37,15 +37,11 @@ export const getLegacyTransforms = async ({ ); }); - const result = await Promise.allSettled(getTransformStatsRequests); + const results = await Promise.allSettled(getTransformStatsRequests); - const fulfuletGetTransformStats = result - .filter((r) => r.status === 'fulfilled') - .map((r) => (r as PromiseFulfilledResult).value); - - const transforms = fulfuletGetTransformStats.reduce((acc, val) => { - if (val.transforms) { - return [...acc, ...val.transforms]; + const transforms = results.reduce((acc, result) => { + if (result.status === 'fulfilled' && result.value?.transforms?.length > 0) { + acc.push(...result.value.transforms); } return acc; }, [] as TransformGetTransformTransformSummary[]); @@ -53,13 +49,13 @@ export const getLegacyTransforms = async ({ return transforms; }; -export const removeLegacyTransofrms = async ({ +export const removeLegacyTransforms = async ({ namespace, esClient, }: { namespace: string; esClient: ElasticsearchClient; -}) => { +}): Promise => { const transforms = await getLegacyTransforms({ namespace, esClient }); const stopTransformRequests = transforms.map((t) => diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts index 5410eea5578f2..72b130fc1670e 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts @@ -4,11 +4,20 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { SavedObject } from '@kbn/core/server'; +import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; -import type { RiskEngineConfiguration, UpdateConfigOpts, SavedObjectsClients } from '../types'; +import type { RiskEngineConfiguration } from '../types'; import { riskEngineConfigurationTypeName } from '../saved_object'; +export interface SavedObjectsClients { + savedObjectsClient: SavedObjectsClientContract; +} + +export interface UpdateConfigOpts extends SavedObjectsClients { + user: AuthenticatedUser | null | undefined; +} + const getConfigurationSavedObject = async ({ savedObjectsClient, }: SavedObjectsClients): Promise | undefined> => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts index f72a2db3f820b..29758b5fde1c4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts @@ -12,12 +12,13 @@ import { RISK_ENGINE_ENABLE_URL, RISK_ENGINE_STATUS_URL, } from '@kbn/security-solution-plugin/common/constants'; +import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/risk_engine/saved_object'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { cleanRiskEngineConfig, legacyTransformIds, createTransforms, - clearLegacyTranforms, + clearLegacyTransforms, } from './utils'; // eslint-disable-next-line import/no-default-export @@ -31,7 +32,7 @@ export default ({ getService }: FtrProviderContext) => { await cleanRiskEngineConfig({ kibanaServer, }); - await clearLegacyTranforms({ + await clearLegacyTransforms({ es, }); }); @@ -280,7 +281,7 @@ export default ({ getService }: FtrProviderContext) => { it('should create configuration saved object', async () => { await initRiskEngine(); const response = await kibanaServer.savedObjects.find({ - type: 'risk-engine-configuration', + type: riskEngineConfigurationTypeName, }); expect(response?.saved_objects?.[0]?.attributes).to.eql({ @@ -292,12 +293,12 @@ export default ({ getService }: FtrProviderContext) => { it('should create configuration saved object only once', async () => { await initRiskEngine(); const firstResponse = await kibanaServer.savedObjects.find({ - type: 'risk-engine-configuration', + type: riskEngineConfigurationTypeName, }); await initRiskEngine(); const secondResponse = await kibanaServer.savedObjects.find({ - type: 'risk-engine-configuration', + type: riskEngineConfigurationTypeName, }); expect(secondResponse?.saved_objects?.length).to.eql(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts index 980615457323d..e03aa1f843fe4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/utils.ts @@ -13,6 +13,7 @@ import type { EcsRiskScore, RiskScore, } from '@kbn/security-solution-plugin/server/lib/risk_engine/types'; +import { riskEngineConfigurationTypeName } from '@kbn/security-solution-plugin/server/lib/risk_engine/saved_object'; import type { KbnClient } from '@kbn/test'; import { createRule, @@ -139,7 +140,7 @@ export const waitForRiskScoresToBePresent = async ( export const getRiskEngineConfigSO = async ({ kibanaServer }: { kibanaServer: KbnClient }) => { const soResponse = await kibanaServer.savedObjects.find({ - type: 'risk-engine-configuration', + type: riskEngineConfigurationTypeName, }); return soResponse?.saved_objects?.[0]; @@ -153,7 +154,7 @@ export const cleanRiskEngineConfig = async ({ const so = await getRiskEngineConfigSO({ kibanaServer }); if (so) { await kibanaServer.savedObjects.delete({ - type: 'risk-engine-configuration', + type: riskEngineConfigurationTypeName, id: so.id, }); } @@ -166,7 +167,7 @@ export const legacyTransformIds = [ 'ml_userriskscore_latest_transform_default', ]; -export const clearLegacyTranforms = async ({ es }: { es: Client }): Promise => { +export const clearLegacyTransforms = async ({ es }: { es: Client }): Promise => { const transforms = legacyTransformIds.map((transform) => es.transform.deleteTransform({ transform_id: transform, From dac1f34d1df625e4d805229955c2ac1e788cce4c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 2 Aug 2023 12:56:19 +0000 Subject: [PATCH 32/46] [CI] Auto-commit changed files from 'node scripts/check_mappings_update --fix' --- .../current_mappings.json | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index ba65da1800b47..67ed6780d6d4e 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -42,6 +42,25 @@ } } }, + "url": { + "dynamic": false, + "properties": { + "slug": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + } + } + }, "usage-counters": { "dynamic": false, "properties": { @@ -131,25 +150,6 @@ } } }, - "url": { - "dynamic": false, - "properties": { - "slug": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword" - } - } - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - } - } - }, "index-pattern": { "dynamic": false, "properties": { @@ -1407,6 +1407,14 @@ "dynamic": false, "properties": {} }, + "infrastructure-monitoring-log-view": { + "dynamic": false, + "properties": { + "name": { + "type": "text" + } + } + }, "canvas-element": { "dynamic": false, "properties": { @@ -2262,14 +2270,6 @@ } } }, - "infrastructure-monitoring-log-view": { - "dynamic": false, - "properties": { - "name": { - "type": "text" - } - } - }, "ml-job": { "properties": { "job_id": { @@ -2939,6 +2939,7 @@ } }, "risk-engine-configuration": { + "dynamic": false, "properties": { "enabled": { "type": "boolean" From 6265ac36339574bfdeab8fd9218d92f3be8d6505 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:30:49 +0000 Subject: [PATCH 33/46] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../risk_score_onboarding/risk_score_enable_button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx index 1259b0a73755a..208d9a2c52a2b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_score_onboarding/risk_score_enable_button.tsx @@ -38,7 +38,7 @@ const RiskScoreEnableButtonComponent = ({ const { http, notifications, theme, dashboard } = useKibana().services; const { renderDocLink, renderDashboardLink } = useRiskScoreToastContent(riskScoreEntity); const { fetch, isLoading } = useFetch(REQUEST_NAMES.ENABLE_RISK_SCORE, installRiskScoreModule); - const isRiskEngineEnabled = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled') + const isRiskEngineEnabled = useIsExperimentalFeatureEnabled('riskScoringRoutesEnabled'); const onBoardingRiskScore = useCallback(() => { fetch({ From b31ab045bf7ae00cab5027a54d47e743dbc054fd Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 06:32:06 +0200 Subject: [PATCH 34/46] Remove last updated_by --- .../current_mappings.json | 3 --- .../lib/risk_engine/risk_engine_data_client.test.ts | 3 --- .../lib/risk_engine/risk_engine_data_client.ts | 13 +------------ .../risk_engine/routes/risk_engine_status_route.ts | 1 - .../saved_object/risk_engine_configuration_type.ts | 3 --- .../server/lib/risk_engine/types.ts | 1 - .../risk_engine/utils/saved_object_configuration.ts | 2 -- .../group10/risk_engine/risk_engine_status.ts | 7 ------- 8 files changed, 1 insertion(+), 32 deletions(-) diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 67ed6780d6d4e..748b27fe13dd1 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -2943,9 +2943,6 @@ "properties": { "enabled": { "type": "boolean" - }, - "last_updated_by": { - "type": "keyword" } } }, diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index eafdc68f7ea37..310476932c571 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -31,7 +31,6 @@ const getSavedObjectConfiguration = (attributes = {}) => ({ namespaces: ['default'], attributes: { enabled: false, - last_updated_by: 'elastic', ...attributes, }, references: [], @@ -476,7 +475,6 @@ describe('RiskEngineDataClient', () => { 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', { enabled: true, - last_updated_by: 'elastic', }, { refresh: 'wait_for', @@ -522,7 +520,6 @@ describe('RiskEngineDataClient', () => { 'de8ca330-2d26-11ee-bc86-f95bf6192ee6', { enabled: false, - last_updated_by: 'elastic', }, { refresh: 'wait_for', diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts index 6f113bc99aa01..f338686f3ceac 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.ts @@ -129,8 +129,7 @@ export class RiskEngineDataClient { }) { const riskEngineStatus = await this.getCurrentStatus({ savedObjectsClient }); const legacyRiskEngineStatus = await this.getLegacyStatus({ namespace }); - const lastUpdatedBy = await this.getLastUpdatedBy({ savedObjectsClient }); - return { riskEngineStatus, legacyRiskEngineStatus, lastUpdatedBy }; + return { riskEngineStatus, legacyRiskEngineStatus }; } public async enableRiskEngine({ savedObjectsClient, user }: UpdateConfigOpts) { @@ -175,16 +174,6 @@ export class RiskEngineDataClient { return newlegacyRiskEngineStatus === RiskEngineStatus.NOT_INSTALLED; } - private async getLastUpdatedBy({ savedObjectsClient }: SavedObjectsClients) { - const configuration = await getConfiguration({ savedObjectsClient }); - - if (configuration) { - return configuration.last_updated_by; - } - - return ''; - } - private async getCurrentStatus({ savedObjectsClient }: SavedObjectsClients) { const configuration = await getConfiguration({ savedObjectsClient }); diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts index d4cd4f2d6e16f..9433674c50266 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -38,7 +38,6 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg body: { risk_engine_status: result.riskEngineStatus, legacy_risk_engine_status: result.legacyRiskEngineStatus, - last_updated_by: result.lastUpdatedBy, }, }); } catch (e) { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts index d09d3170c4c3c..81171fde5e3b4 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/saved_object/risk_engine_configuration_type.ts @@ -16,9 +16,6 @@ export const riskEngineConfigurationTypeMappings: SavedObjectsType['mappings'] = enabled: { type: 'boolean', }, - last_updated_by: { - type: 'keyword', - }, }, }; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts index 9bd35bfa734cc..fff4f7c88ff7b 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/types.ts @@ -164,5 +164,4 @@ export interface RiskScoreBucket { export interface RiskEngineConfiguration { enabled: boolean; - last_updated_by: string; } diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts index 72b130fc1670e..13b3d54496fa8 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/utils/saved_object_configuration.ts @@ -49,7 +49,6 @@ export const updateSavedObjectAttribute = async ({ savedObjectConfiguration.id, { ...attributes, - last_updated_by: user?.username ?? '', }, { refresh: 'wait_for', @@ -66,7 +65,6 @@ export const initSavedObjects = async ({ savedObjectsClient, user }: UpdateConfi } const result = await savedObjectsClient.create(riskEngineConfigurationTypeName, { enabled: false, - last_updated_by: user?.username ?? '', }); return result; }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts index 29758b5fde1c4..dae7bd2cd3ab6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/risk_engine/risk_engine_status.ts @@ -286,7 +286,6 @@ export default ({ getService }: FtrProviderContext) => { expect(response?.saved_objects?.[0]?.attributes).to.eql({ enabled: true, - last_updated_by: 'elastic', }); }); @@ -339,7 +338,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status1.body).to.eql({ risk_engine_status: 'NOT_INSTALLED', legacy_risk_engine_status: 'NOT_INSTALLED', - last_updated_by: '', }); await initRiskEngine(); @@ -349,7 +347,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status2.body).to.eql({ risk_engine_status: 'ENABLED', legacy_risk_engine_status: 'NOT_INSTALLED', - last_updated_by: 'elastic', }); await disableRiskEngine(); @@ -358,7 +355,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status3.body).to.eql({ risk_engine_status: 'DISABLED', legacy_risk_engine_status: 'NOT_INSTALLED', - last_updated_by: 'elastic', }); await enableRiskEngine(); @@ -367,7 +363,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status4.body).to.eql({ risk_engine_status: 'ENABLED', legacy_risk_engine_status: 'NOT_INSTALLED', - last_updated_by: 'elastic', }); }); @@ -378,7 +373,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status1.body).to.eql({ risk_engine_status: 'NOT_INSTALLED', legacy_risk_engine_status: 'ENABLED', - last_updated_by: '', }); await initRiskEngine(); @@ -388,7 +382,6 @@ export default ({ getService }: FtrProviderContext) => { expect(status2.body).to.eql({ risk_engine_status: 'ENABLED', legacy_risk_engine_status: 'NOT_INSTALLED', - last_updated_by: 'elastic', }); }); }); From a77a8388009ca3500d372147d0b0880406fdbef1 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 09:14:01 +0200 Subject: [PATCH 35/46] fix hest integration tests --- .../migrations/group2/check_registered_types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index a17cf212cbfa9..c5ce6a38d9dc9 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -126,7 +126,7 @@ describe('checking migration metadata changes on all registered SO types', () => "osquery-pack-asset": "b14101d3172c4b60eb5404696881ce5275c84152", "osquery-saved-query": "44f1161e165defe3f9b6ad643c68c542a765fcdb", "query": "8db5d48c62d75681d80d82a42b5642f60d068202", - "risk-engine-configuration": "4dbbc5fd0d1bacc4d76e9c63dfb7887fb6d57079", + "risk-engine-configuration": "1b8b175e29ea5311408125c92c6247f502b2d79d", "rules-settings": "892a2918ebaeba809a612b8d97cec0b07c800b5f", "sample-data-telemetry": "37441b12f5b0159c2d6d5138a494c9f440e950b5", "search": "8d5184dd5b986d57250b6ffd9ae48a1925e4c7a3", From ba83b7bef4d59aae6774faa8a42561afd735a45f Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 09:17:45 +0200 Subject: [PATCH 36/46] Fix cypress tetss --- .../entity_analytics_management_page.cy.ts | 25 ++++++------------- .../screens/entity_analytics_management.ts | 12 ++++----- .../cypress/tasks/api_calls/risk_engine.ts | 25 +++++++++++++++++++ .../tasks/api_calls/risk_scores/index.ts | 13 ++++++++++ .../cypress/tasks/entity_analytics.ts | 1 + .../components/risk_score_enable_section.tsx | 17 +++++++------ 6 files changed, 62 insertions(+), 31 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index cc2dc240d88c9..5950bccafb43b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -18,22 +18,14 @@ import { RISK_SCORE_STATUS, } from '../../screens/entity_analytics_management'; -import { - deleteRiskScore, - interceptInstallRiskScoreModule, - waitForInstallRiskScoreModule, -} from '../../tasks/api_calls/risk_scores'; -import { clickEnableRiskScore } from '../../tasks/risk_scores'; +import { deleteRiskScore, installRiskScoreModule } from '../../tasks/api_calls/risk_scores'; import { RiskScoreEntity } from '../../tasks/risk_scores/common'; import { login, visit, visitWithoutDateRange } from '../../tasks/login'; import { cleanKibana } from '../../tasks/common'; -import { - ENTITY_ANALYTICS_MANAGEMENT_URL, - ALERTS_URL, - ENTITY_ANALYTICS_URL, -} from '../../urls/navigation'; +import { ENTITY_ANALYTICS_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { createRule } from '../../tasks/api_calls/rules'; +import { deleteConfiguration } from '../../tasks/api_calls/risk_engine'; import { updateDateRangeInLocalDatePickers } from '../../tasks/date_picker'; import { fillLocalSearchBar, submitLocalSearch } from '../../tasks/search_bar'; import { @@ -55,6 +47,7 @@ describe( login(); visitWithoutDateRange(ALERTS_URL); createRule(getNewRule({ query: 'user.name:* or host.name:*', risk_score: 70 })); + deleteConfiguration(); visit(ENTITY_ANALYTICS_MANAGEMENT_URL); }); @@ -140,16 +133,14 @@ describe( statusCode: 500, }); - cy.get(RISK_SCORE_STATUS).should('have.text', 'On'); + // init + riskEngineStatusChange(); - cy.get(RISK_SCORE_ERROR_PANEL).contains('Error enabling risk engine'); + cy.get(RISK_SCORE_ERROR_PANEL).contains('Sorry, there was an error'); }); it('should update if there legacy risk score installed', () => { - visit(ENTITY_ANALYTICS_URL); - interceptInstallRiskScoreModule(); - clickEnableRiskScore(RiskScoreEntity.host); - waitForInstallRiskScoreModule(); + installRiskScoreModule(); visit(ENTITY_ANALYTICS_MANAGEMENT_URL); cy.get(RISK_SCORE_STATUS).should('not.exist'); diff --git a/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts b/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts index c8dd52a570b18..00bac740fe3c5 100644 --- a/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts +++ b/x-pack/plugins/security_solution/cypress/screens/entity_analytics_management.ts @@ -23,14 +23,14 @@ export const RISK_PREVIEW_ERROR_BUTTON = '[data-test-subj="risk-preview-error-bu export const LOCAL_QUERY_BAR_SELECTOR = getDataTestSubjectSelector('risk-score-preview-search-bar'); -export const RISK_SCORE_ERROR_PANEL = '[data-test-subj="riskScoreErrorPanel"]'; +export const RISK_SCORE_ERROR_PANEL = '[data-test-subj="risk-score-error-panel"]'; -export const RISK_SCORE_UPDATE_CANCEL = '[data-test-subj="riskScoreUpdateCancel"]'; +export const RISK_SCORE_UPDATE_CANCEL = '[data-test-subj="risk-score-update-cancel"]'; -export const RISK_SCORE_UPDATE_CONFIRM = '[data-test-subj="riskScoreUpdateConfirm"]'; +export const RISK_SCORE_UPDATE_CONFIRM = '[data-test-subj="risk-score-update-confirm"]'; -export const RISK_SCORE_UDATE_BUTTON = '[data-test-subj="riskScoreUpdateButton"]'; +export const RISK_SCORE_UDATE_BUTTON = '[data-test-subj="risk-score-update-button"]'; -export const RISK_SCORE_STATUS = '[data-test-subj="riskScoreStatus"]'; +export const RISK_SCORE_STATUS = '[data-test-subj="risk-score-status"]'; -export const RISK_SCORE_SWITCH = '[data-test-subj="riskScoreSwitch"]'; +export const RISK_SCORE_SWITCH = '[data-test-subj="risk-score-switch"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts new file mode 100644 index 0000000000000..7c6756ef7db0a --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const deleteConfiguration = () => { + cy.request({ + method: 'GET', + url: `/api/saved_objects/_find?type=risk-engine-configuration`, + failOnStatusCode: false, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + }).then((res) => { + const savedObjectId = res?.body?.saved_objects?.[0]?.id; + if (savedObjectId) { + return cy.request({ + method: 'DELETE', + url: `/api/saved_objects/risk-engine-configuration/${savedObjectId}`, + failOnStatusCode: false, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + }); + } + }); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts index 50caf4353c096..30c5b5fccefd1 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_scores/index.ts @@ -290,3 +290,16 @@ export const interceptInstallRiskScoreModule = () => { export const waitForInstallRiskScoreModule = () => { cy.wait(['@install'], { requestTimeout: 50000 }); }; + +export const installRiskScoreModule = () => { + cy.request({ + url: RISK_SCORE_URL, + method: 'POST', + body: { + riskScoreEntity: 'host', + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }) + .its('status') + .should('eql', 200); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts index 6f32ba06ef19b..83146491ea8cb 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts @@ -38,6 +38,7 @@ export const navigateToNextPage = () => { }; export const riskEngineStatusChange = () => { + cy.get(RISK_SCORE_SWITCH).should('not.have.attr', 'disabled'); cy.get(RISK_SCORE_SWITCH).click(); }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx index 12271ff1efd08..62361ee3223db 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_enable_section.tsx @@ -64,7 +64,7 @@ const RiskScoreErrorPanel = ({ errors }: { errors: string[] }) => ( title={i18n.ERROR_PANEL_TITLE} color="danger" iconType="error" - data-test-subj="riskScoreErrorPanel" + data-test-subj="risk-score-error-panel" >

{i18n.ERROR_PANEL_MESSAGE}

@@ -105,9 +105,10 @@ export const RiskScoreEnableSection = () => { disableRiskEngineMutation.isLoading; const isUpdateAvailable = riskEngineStatus?.isUpdateAvailable; + const btnIsDisabled = !currentRiskEngineStatus || isLoading; const onSwitchClick = () => { - if (!currentRiskEngineStatus || isLoading) { + if (btnIsDisabled) { return; } @@ -156,14 +157,14 @@ export const RiskScoreEnableSection = () => { {i18n.UPDATE_RISK_ENGINE_MODAL_BUTTON_NO} initRiskEngineMutation.mutate()} fill > @@ -222,7 +223,7 @@ export const RiskScoreEnableSection = () => { disabled={initRiskEngineMutation.isLoading} color={'primary'} onClick={showModal} - data-test-subj="riskScoreUpdateButton" + data-test-subj="risk-score-update-button" > {i18n.START_UPDATE} @@ -233,7 +234,7 @@ export const RiskScoreEnableSection = () => { {isLoading && } {currentRiskEngineStatus === RiskEngineStatus.ENABLED ? ( {i18n.RISK_SCORE_MODULE_STATUS_ON} @@ -244,11 +245,11 @@ export const RiskScoreEnableSection = () => { From 9742878fc2bbae2e45e77b12283f28f0215dea30 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 10:20:11 +0200 Subject: [PATCH 37/46] Fix jest tests --- .../server/lib/risk_engine/risk_engine_data_client.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts index 310476932c571..3d38293626e16 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/risk_engine_data_client.test.ts @@ -351,7 +351,6 @@ describe('RiskEngineDataClient', () => { expect(status).toEqual({ riskEngineStatus: 'NOT_INSTALLED', legacyRiskEngineStatus: 'NOT_INSTALLED', - lastUpdatedBy: '', }); }); @@ -378,7 +377,6 @@ describe('RiskEngineDataClient', () => { expect(status).toEqual({ riskEngineStatus: 'ENABLED', legacyRiskEngineStatus: 'NOT_INSTALLED', - lastUpdatedBy: 'elastic', }); }); @@ -392,7 +390,6 @@ describe('RiskEngineDataClient', () => { expect(status).toEqual({ riskEngineStatus: 'DISABLED', legacyRiskEngineStatus: 'NOT_INSTALLED', - lastUpdatedBy: 'elastic', }); }); }); @@ -430,7 +427,6 @@ describe('RiskEngineDataClient', () => { expect(status).toEqual({ riskEngineStatus: 'NOT_INSTALLED', legacyRiskEngineStatus: 'ENABLED', - lastUpdatedBy: '', }); esClient.transform.getTransformStats.mockReset(); From c77a82ea39826f217364eba099c78f44ea00e98e Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 16:01:01 +0200 Subject: [PATCH 38/46] fix cypress tests --- .../entity_analytics_management_page.cy.ts | 26 ++++++++----------- .../cypress/tasks/entity_analytics.ts | 5 ++++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts index 5950bccafb43b..da06359fc789b 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/entity_analytics/entity_analytics_management_page.cy.ts @@ -12,7 +12,6 @@ import { USER_RISK_PREVIEW_TABLE, USER_RISK_PREVIEW_TABLE_ROWS, RISK_PREVIEW_ERROR, - RISK_PREVIEW_ERROR_BUTTON, LOCAL_QUERY_BAR_SELECTOR, RISK_SCORE_ERROR_PANEL, RISK_SCORE_STATUS, @@ -25,13 +24,19 @@ import { cleanKibana } from '../../tasks/common'; import { ENTITY_ANALYTICS_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation'; import { getNewRule } from '../../objects/rule'; import { createRule } from '../../tasks/api_calls/rules'; -import { deleteConfiguration } from '../../tasks/api_calls/risk_engine'; +import { + deleteConfiguration, + interceptRiskPreviewError, + interceptRiskPreviewSuccess, + interceptRiskInitError, +} from '../../tasks/api_calls/risk_engine'; import { updateDateRangeInLocalDatePickers } from '../../tasks/date_picker'; import { fillLocalSearchBar, submitLocalSearch } from '../../tasks/search_bar'; import { riskEngineStatusChange, updateRiskEngine, updateRiskEngineConfirm, + previewErrorButtonClick, } from '../../tasks/entity_analytics'; describe( @@ -87,20 +92,13 @@ describe( }); it('show error panel if API returns error and then try to refetch data', () => { - cy.intercept('POST', '/internal/risk_score/preview', { - statusCode: 500, - }); + interceptRiskPreviewError(); cy.get(RISK_PREVIEW_ERROR).contains('Preview failed'); - cy.intercept('POST', '/internal/risk_score/preview', { - statusCode: 200, - body: { - scores: { host: [], user: [] }, - }, - }); + interceptRiskPreviewSuccess(); - cy.get(RISK_PREVIEW_ERROR_BUTTON).click(); + previewErrorButtonClick(); cy.get(RISK_PREVIEW_ERROR).should('not.exist'); }); @@ -129,9 +127,7 @@ describe( it('should show error panel if API returns error ', () => { cy.get(RISK_SCORE_STATUS).should('have.text', 'Off'); - cy.intercept('POST', '/internal/risk_score/engine/init', { - statusCode: 500, - }); + interceptRiskInitError(); // init riskEngineStatusChange(); diff --git a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts index 83146491ea8cb..8bfc94e4f8f9c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/entity_analytics.ts @@ -16,6 +16,7 @@ import { RISK_SCORE_UPDATE_CONFIRM, RISK_SCORE_UDATE_BUTTON, RISK_SCORE_SWITCH, + RISK_PREVIEW_ERROR_BUTTON, } from '../screens/entity_analytics_management'; import { visit } from './login'; @@ -49,3 +50,7 @@ export const updateRiskEngine = () => { export const updateRiskEngineConfirm = () => { cy.get(RISK_SCORE_UPDATE_CONFIRM).click(); }; + +export const previewErrorButtonClick = () => { + cy.get(RISK_PREVIEW_ERROR_BUTTON).click(); +}; From 96b6ddc250ec99980b82f8658a543801b697cc04 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 16:05:35 +0200 Subject: [PATCH 39/46] Access for license and serverless --- .../cypress/tasks/api_calls/risk_engine.ts | 21 ++++++++++++ .../navigation/host_risk_score_tab_body.tsx | 32 +++++++++---------- .../public/management/links.ts | 9 +++++- .../routes/risk_engine_disable_route.ts | 4 +-- .../routes/risk_engine_enable_route.ts | 4 +-- .../routes/risk_engine_init_route.ts | 4 +-- .../routes/risk_engine_status_route.ts | 4 +-- 7 files changed, 52 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts index 7c6756ef7db0a..2564615f2ccda 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/risk_engine.ts @@ -23,3 +23,24 @@ export const deleteConfiguration = () => { } }); }; + +export const interceptRiskPreviewError = () => { + cy.intercept('POST', '/internal/risk_score/preview', { + statusCode: 500, + }); +}; + +export const interceptRiskPreviewSuccess = () => { + cy.intercept('POST', '/internal/risk_score/preview', { + statusCode: 200, + body: { + scores: { host: [], user: [] }, + }, + }); +}; + +export const interceptRiskInitError = () => { + cy.intercept('POST', '/internal/risk_score/engine/init', { + statusCode: 500, + }); +}; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx index af44c85786537..5511706a349fe 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx @@ -128,23 +128,21 @@ export const HostRiskScoreQueryTabBody = ({ } return ( - <> - - + ); }; diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index b8f14c237ac94..3f18e3528e824 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -49,7 +49,8 @@ import { IconPipeline } from '../common/icons/pipeline'; import { IconSavedObject } from '../common/icons/saved_object'; import { IconDashboards } from '../common/icons/dashboards'; import { IconEntityAnalytics } from '../common/icons/entity_analytics'; - +import { useMlCapabilities } from '../common/components/ml/hooks/use_ml_capabilities'; +import { useHasSecurityCapability } from '../helper_hooks'; import { HostIsolationExceptionsApiClient } from './pages/host_isolation_exceptions/host_isolation_exceptions_api_client'; const categories = [ @@ -218,6 +219,8 @@ export const getManagementFilteredLinks = async ( fleetAuthz && currentUser ? calculateEndpointAuthz(licenseService, fleetAuthz, currentUser.roles) : getEndpointAuthzInitialState(); + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); + const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; const showHostIsolationExceptions = canAccessHostIsolationExceptions || // access host isolation exceptions is a paid feature, always show the link. @@ -256,5 +259,9 @@ export const getManagementFilteredLinks = async ( linksToExclude.push(SecurityPageName.blocklist); } + if (!(hasEntityAnalyticsCapability && isPlatinumOrTrialLicense)) { + linksToExclude.push(SecurityPageName.entityAnalyticsManagement); + } + return excludeLinks(linksToExclude); }; diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts index b15bd81b40ad3..1df867a59c824 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_disable_route.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_DISABLE_URL } from '../../../../common/constants'; +import { RISK_ENGINE_DISABLE_URL, APP_ID } from '../../../../common/constants'; import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; @@ -22,7 +22,7 @@ export const riskEngineDisableRoute = ( path: RISK_ENGINE_DISABLE_URL, validate: {}, options: { - tags: ['access:securitySolution'], + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts index 267abdcc2d0d3..edc0ae7f0f64c 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_enable_route.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_ENABLE_URL } from '../../../../common/constants'; +import { RISK_ENGINE_ENABLE_URL, APP_ID } from '../../../../common/constants'; import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; @@ -22,7 +22,7 @@ export const riskEngineEnableRoute = ( path: RISK_ENGINE_ENABLE_URL, validate: {}, options: { - tags: ['access:securitySolution'], + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts index 575e04498a5be..5bc269901c61c 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_init_route.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_INIT_URL } from '../../../../common/constants'; +import { RISK_ENGINE_INIT_URL, APP_ID } from '../../../../common/constants'; import type { SetupPlugins } from '../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../types'; @@ -23,7 +23,7 @@ export const riskEngineInitRoute = ( path: RISK_ENGINE_INIT_URL, validate: {}, options: { - tags: ['access:securitySolution'], + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }, async (context, request, response) => { diff --git a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts index 9433674c50266..a6f59e558ea8d 100644 --- a/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/risk_engine/routes/risk_engine_status_route.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; -import { RISK_ENGINE_STATUS_URL } from '../../../../common/constants'; +import { RISK_ENGINE_STATUS_URL, APP_ID } from '../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../types'; @@ -18,7 +18,7 @@ export const riskEngineStatusRoute = (router: SecuritySolutionPluginRouter, logg path: RISK_ENGINE_STATUS_URL, validate: {}, options: { - tags: ['access:securitySolution'], + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }, async (context, request, response) => { From 3b1bb9f91e76206fd1746ff21e0cfdd9e77ca49c Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 16:18:14 +0200 Subject: [PATCH 40/46] Chaange approach for link with license and capabilities --- x-pack/plugins/security_solution/public/management/links.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 3f18e3528e824..cae07ed3f648f 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -178,6 +178,7 @@ export const links: LinkItem = { path: ENTITY_ANALYTICS_MANAGEMENT_PATH, skipUrlState: true, hideTimeline: true, + capabilities: [`'entity-analytics'`], experimentalKey: 'riskScoringRoutesEnabled', }, { @@ -219,8 +220,7 @@ export const getManagementFilteredLinks = async ( fleetAuthz && currentUser ? calculateEndpointAuthz(licenseService, fleetAuthz, currentUser.roles) : getEndpointAuthzInitialState(); - const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const isPlatinumOrTrialLicense = licenseService.isPlatinumPlus() || licenseService.isTrial(); const showHostIsolationExceptions = canAccessHostIsolationExceptions || // access host isolation exceptions is a paid feature, always show the link. @@ -259,7 +259,7 @@ export const getManagementFilteredLinks = async ( linksToExclude.push(SecurityPageName.blocklist); } - if (!(hasEntityAnalyticsCapability && isPlatinumOrTrialLicense)) { + if (!isPlatinumOrTrialLicense) { linksToExclude.push(SecurityPageName.entityAnalyticsManagement); } From f8cf0326478b264fa8c0fa29c32a6b8817718810 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 16:37:14 +0200 Subject: [PATCH 41/46] Fix links --- x-pack/plugins/security_solution/public/management/links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index cae07ed3f648f..c777d4fe66696 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -178,7 +178,7 @@ export const links: LinkItem = { path: ENTITY_ANALYTICS_MANAGEMENT_PATH, skipUrlState: true, hideTimeline: true, - capabilities: [`'entity-analytics'`], + capabilities: [`${SERVER_APP_ID}.entity-analytics`], experimentalKey: 'riskScoringRoutesEnabled', }, { From 7237b5fe3f4be77b2b38672176ef12d90d0cdfea Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Aug 2023 14:44:13 +0000 Subject: [PATCH 42/46] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/plugins/security_solution/public/management/links.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index c777d4fe66696..8a69514b445be 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -49,8 +49,6 @@ import { IconPipeline } from '../common/icons/pipeline'; import { IconSavedObject } from '../common/icons/saved_object'; import { IconDashboards } from '../common/icons/dashboards'; import { IconEntityAnalytics } from '../common/icons/entity_analytics'; -import { useMlCapabilities } from '../common/components/ml/hooks/use_ml_capabilities'; -import { useHasSecurityCapability } from '../helper_hooks'; import { HostIsolationExceptionsApiClient } from './pages/host_isolation_exceptions/host_isolation_exceptions_api_client'; const categories = [ From 7df7d227698a3a275dbc2d33561010919d64ad47 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Thu, 3 Aug 2023 17:35:19 +0200 Subject: [PATCH 43/46] fix ts problems --- x-pack/plugins/security_solution/public/management/links.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 8a69514b445be..34af59bbd4c00 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -218,8 +218,8 @@ export const getManagementFilteredLinks = async ( fleetAuthz && currentUser ? calculateEndpointAuthz(licenseService, fleetAuthz, currentUser.roles) : getEndpointAuthzInitialState(); - const isPlatinumOrTrialLicense = licenseService.isPlatinumPlus() || licenseService.isTrial(); - + const isPlatinumOrTrialLicense = + licenseService.isPlatinumPlus() || licenseService.getLicenseInformation()?.type === 'trial'; const showHostIsolationExceptions = canAccessHostIsolationExceptions || // access host isolation exceptions is a paid feature, always show the link. // read host isolation exceptions is not a paid feature, to allow deleting exceptions after a downgrade scenario. From 1bc331426d153b39beebec5b15063082795e2247 Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Thu, 3 Aug 2023 12:43:49 -0500 Subject: [PATCH 44/46] Simplify logic for showing EA management page Trial licenses are included in `.isPlatinumPlus`, we don't need to check that ourselves. --- x-pack/plugins/security_solution/public/management/links.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 34af59bbd4c00..1c2651de4e074 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -218,8 +218,7 @@ export const getManagementFilteredLinks = async ( fleetAuthz && currentUser ? calculateEndpointAuthz(licenseService, fleetAuthz, currentUser.roles) : getEndpointAuthzInitialState(); - const isPlatinumOrTrialLicense = - licenseService.isPlatinumPlus() || licenseService.getLicenseInformation()?.type === 'trial'; + const showEntityAnalytics = licenseService.isPlatinumPlus(); const showHostIsolationExceptions = canAccessHostIsolationExceptions || // access host isolation exceptions is a paid feature, always show the link. // read host isolation exceptions is not a paid feature, to allow deleting exceptions after a downgrade scenario. @@ -257,7 +256,7 @@ export const getManagementFilteredLinks = async ( linksToExclude.push(SecurityPageName.blocklist); } - if (!isPlatinumOrTrialLicense) { + if (!showEntityAnalytics) { linksToExclude.push(SecurityPageName.entityAnalyticsManagement); } From d9b03b0bb8de71142975d590ba6ca1ee94b683b6 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 4 Aug 2023 11:05:54 +0200 Subject: [PATCH 45/46] Fix tesxt --- .../security_solution/public/entity_analytics/translations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts index fb5c68a5f440a..5fc6a5893b512 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/translations.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/translations.ts @@ -163,7 +163,7 @@ export const UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_1 = i18n.translate( export const UPDATE_RISK_ENGINE_MODAL_EXISTING_USER_HOST_2 = i18n.translate( 'xpack.securitySolution.riskScore.updateRiskEngineModal.existingUserHost_2', { - defaultMessage: ', they are no longer required.', + defaultMessage: ', as they are no longer required.', } ); From b8b638ea9f521dd9f24aecbedbc06d54faa15434 Mon Sep 17 00:00:00 2001 From: Khristinin Nikita Date: Fri, 4 Aug 2023 11:10:55 +0200 Subject: [PATCH 46/46] Hide update panel for serverless --- .../public/overview/pages/entity_analytics.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx index f7f4ff592ab81..31cc7d0ae289c 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/entity_analytics.tsx @@ -27,11 +27,15 @@ import { InputsModelId } from '../../common/store/inputs/constants'; import { FiltersGlobal } from '../../common/components/filters_global'; import { useRiskEngineStatus } from '../../entity_analytics/api/hooks/use_risk_engine_status'; import { RiskScoreUpdatePanel } from '../../entity_analytics/components/risk_score_update_panel'; +import { useHasSecurityCapability } from '../../helper_hooks'; const EntityAnalyticsComponent = () => { const { data: riskScoreEngineStatus } = useRiskEngineStatus(); const { indicesExist, loading: isSourcererLoading, indexPattern } = useSourcererDataView(); const { isPlatinumOrTrialLicense, capabilitiesFetched } = useMlCapabilities(); + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); + const isRiskScoreModuleLicenseAvailable = + isPlatinumOrTrialLicense && hasEntityAnalyticsCapability; return ( <> @@ -51,7 +55,7 @@ const EntityAnalyticsComponent = () => { ) : ( - {riskScoreEngineStatus?.isUpdateAvailable && ( + {riskScoreEngineStatus?.isUpdateAvailable && isRiskScoreModuleLicenseAvailable && (