From 1bd2d3e05af02ba5e3ae32d6c1bb6db51571d599 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Tue, 4 Mar 2025 15:18:05 +0530 Subject: [PATCH 01/17] Adding code to check privileges for enabling the Run Engine button --- .../pages/entity_analytics_management_page.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index 84ce908d94ee5..9cd42863846b2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -47,12 +47,20 @@ export const EntityAnalyticsManagementPage = () => { const [isLoading, setIsLoading] = useState(false); const { mutate: scheduleNowRiskEngine } = useScheduleNowRiskEngineMutation(); const { addSuccess, addError } = useAppToasts(); + const userHasRequiredPrivileges = + 'hasAllRequiredPrivileges' in privileges && privileges.hasAllRequiredPrivileges; + const btnIsDisabled = !currentRiskEngineStatus || isLoading || !userHasRequiredPrivileges; const handleRunEngineClick = async () => { + if (!userHasRequiredPrivileges) { + addError(i18n.CHECK_PRIVILEGES, { + title: i18n.CHECK_PRIVILEGES, + }); + return; + } setIsLoading(true); try { scheduleNowRiskEngine(); - if (!isLoading) { addSuccess(i18n.RISK_SCORE_ENGINE_RUN_SUCCESS, { toastLifeTimeMs: 5000 }); } @@ -115,6 +123,7 @@ export const EntityAnalyticsManagementPage = () => { From c7e9df06aef0d2b05da88e290b0c3c7b749f85d6 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Tue, 4 Mar 2025 19:07:42 +0530 Subject: [PATCH 02/17] Addressing review comments --- .../pages/entity_analytics_management_page.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index 9cd42863846b2..f303454c9d875 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -49,15 +49,10 @@ export const EntityAnalyticsManagementPage = () => { const { addSuccess, addError } = useAppToasts(); const userHasRequiredPrivileges = 'hasAllRequiredPrivileges' in privileges && privileges.hasAllRequiredPrivileges; - const btnIsDisabled = !currentRiskEngineStatus || isLoading || !userHasRequiredPrivileges; + const RunEngineBtnIsDisabled = + !currentRiskEngineStatus || isLoading || !userHasRequiredPrivileges; const handleRunEngineClick = async () => { - if (!userHasRequiredPrivileges) { - addError(i18n.CHECK_PRIVILEGES, { - title: i18n.CHECK_PRIVILEGES, - }); - return; - } setIsLoading(true); try { scheduleNowRiskEngine(); @@ -123,7 +118,7 @@ export const EntityAnalyticsManagementPage = () => { From c41dbe096829f305622953c36cf8db010ad644e3 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Tue, 4 Mar 2025 19:32:12 +0530 Subject: [PATCH 03/17] lowercase variable name --- .../pages/entity_analytics_management_page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index f303454c9d875..70f4e784bee91 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -49,7 +49,7 @@ export const EntityAnalyticsManagementPage = () => { const { addSuccess, addError } = useAppToasts(); const userHasRequiredPrivileges = 'hasAllRequiredPrivileges' in privileges && privileges.hasAllRequiredPrivileges; - const RunEngineBtnIsDisabled = + const runEngineBtnIsDisabled = !currentRiskEngineStatus || isLoading || !userHasRequiredPrivileges; const handleRunEngineClick = async () => { @@ -118,7 +118,7 @@ export const EntityAnalyticsManagementPage = () => { From 237d650dcbd345413cca39739da41119aeeaeb35 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Wed, 19 Mar 2025 19:02:08 +0530 Subject: [PATCH 04/17] Look for minimum permissions to enable Run Engine button --- .../pages/entity_analytics_management_page.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index 0b9754217780c..20826003192db 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -21,6 +21,7 @@ import { RiskScoreEnableSection } from '../components/risk_score_enable_section' import { ENTITY_ANALYTICS_RISK_SCORE } from '../../app/translations'; import { RiskEnginePrivilegesCallOut } from '../components/risk_engine_privileges_callout'; import { useMissingRiskEnginePrivileges } from '../hooks/use_missing_risk_engine_privileges'; +import { useRiskEnginePrivileges } from '../api/hooks/use_risk_engine_privileges'; import { RiskScoreUsefulLinksSection } from '../components/risk_score_useful_links_section'; import { RiskScoreConfigurationSection } from '../components/risk_score_configuration_section'; import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status'; @@ -35,6 +36,7 @@ export const EntityAnalyticsManagementPage = () => { const { euiTheme } = useEuiTheme(); const styles = getEntityAnalyticsRiskScorePageStyles(euiTheme); const privileges = useMissingRiskEnginePrivileges(); + const { data: riskEnginePrivileges } = useRiskEnginePrivileges(); const [includeClosedAlerts, setIncludeClosedAlerts] = useState(false); const [from, setFrom] = useState(localStorage.getItem('dateStart') || 'now-30d'); const [to, setTo] = useState(localStorage.getItem('dateEnd') || 'now'); @@ -47,10 +49,9 @@ export const EntityAnalyticsManagementPage = () => { const [isLoading, setIsLoading] = useState(false); const { mutate: scheduleNowRiskEngine } = useScheduleNowRiskEngineMutation(); const { addSuccess, addError } = useAppToasts(); - const userHasRequiredPrivileges = - 'hasAllRequiredPrivileges' in privileges && privileges.hasAllRequiredPrivileges; - const runEngineBtnIsDisabled = - !currentRiskEngineStatus || isLoading || !userHasRequiredPrivileges; + const userCanRunEngine = + riskEnginePrivileges?.privileges?.elasticsearch?.cluster?.manage_transform || false; + const runEngineBtnIsDisabled = !currentRiskEngineStatus || isLoading || !userCanRunEngine; const handleRunEngineClick = async () => { setIsLoading(true); From 031626c58f5a08d41936724617aff249f83d7b18 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 21 Mar 2025 15:06:16 +0530 Subject: [PATCH 05/17] Fix schedule_now API privilege check & disable button when 'Now running' --- .../pages/entity_analytics_management_page.tsx | 4 +++- .../entity_analytics/risk_engine/risk_engine_privileges.ts | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index 20826003192db..d934a6004998e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -51,7 +51,6 @@ export const EntityAnalyticsManagementPage = () => { const { addSuccess, addError } = useAppToasts(); const userCanRunEngine = riskEnginePrivileges?.privileges?.elasticsearch?.cluster?.manage_transform || false; - const runEngineBtnIsDisabled = !currentRiskEngineStatus || isLoading || !userCanRunEngine; const handleRunEngineClick = async () => { setIsLoading(true); @@ -87,6 +86,9 @@ export const EntityAnalyticsManagementPage = () => { const isRunning = status === 'running' || (!!runAt && new Date(runAt) < new Date()); + const runEngineBtnIsDisabled = + !currentRiskEngineStatus || isLoading || !userCanRunEngine || isRunning; + const formatTimeFromNow = (time: string | undefined): string => { if (!time) { return ''; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index 56f6fb80fe859..b312010555048 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -97,7 +97,10 @@ export const withRiskEnginePrivilegeCheck = ( ) => { const [_, { security }] = await getStartServices(); const privileges = await getUserRiskEnginePrivileges(request, security); - if (!privileges.has_all_required) { + if ( + !privileges.has_all_required && + !privileges.privileges.elasticsearch.cluster?.manage_transform + ) { const siemResponse = buildSiemResponse(response); return siemResponse.error({ statusCode: 403, From 0668686ef2541c65ac17ccd3324207254dad6838 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Mon, 24 Mar 2025 16:51:11 +0530 Subject: [PATCH 06/17] Addressing review comments : Using different methods for enable and run risk engine for privilege check --- .../entity_analytics/risk_engine/constants.ts | 6 +++- .../risk_engine/privileges.ts | 11 ++++--- .../risk_engine/risk_engine_privileges.ts | 33 ++++++++++++++----- .../risk_engine/routes/privileges.ts | 4 +-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts index 9d71e984021f8..c7ed6174f3017 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts @@ -21,7 +21,11 @@ export const RISK_ENGINE_CONFIGURE_SO_URL = `${PUBLIC_RISK_ENGINE_URL}/saved_object/configure` as const; type ClusterPrivilege = 'manage_index_templates' | 'manage_transform'; -export const RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ +export const TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ + 'manage_transform', +] as ClusterPrivilege[]; + +export const TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ 'manage_index_templates', 'manage_transform', ] as ClusterPrivilege[]; diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts index b03b9e2921325..40ae11caa050d 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts @@ -9,7 +9,7 @@ import type { NonEmptyArray } from 'fp-ts/NonEmptyArray'; import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics'; import type { RiskEngineIndexPrivilege } from './constants'; import { - RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES, } from './constants'; @@ -54,12 +54,13 @@ export const getMissingRiskEnginePrivileges = ( privileges.elasticsearch.index, required ); - const missingClusterPrivileges = RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES.filter( - (privilege) => !privileges.elasticsearch.cluster?.[privilege] - ); + const missingClusterPrivilegesToEnableEngine = + TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES.filter( + (privilege) => !privileges.elasticsearch.cluster?.[privilege] + ); return { indexPrivileges: missingIndexPrivileges, - clusterPrivileges: missingClusterPrivileges, + clusterPrivileges: missingClusterPrivilegesToEnableEngine, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index b312010555048..a9460dee64917 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -18,13 +18,14 @@ import type { EntityAnalyticsPrivileges } from '../../../../common/api/entity_an import type { SecuritySolutionPluginStartDependencies } from '../../../plugin_contract'; import type { SecuritySolutionRequestHandlerContext } from '../../../types'; import { - RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES, getMissingRiskEnginePrivileges, } from '../../../../common/entity_analytics/risk_engine'; import { checkAndFormatPrivileges } from '../utils/check_and_format_privileges'; -export const getUserRiskEnginePrivileges = async ( +export const getRunRiskEnginePrivileges = async ( request: KibanaRequest, security: SecurityPluginStart ) => { @@ -33,7 +34,23 @@ export const getUserRiskEnginePrivileges = async ( security, privilegesToCheck: { elasticsearch: { - cluster: RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + cluster: TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + index: {}, + }, + }, + }); +}; + +export const getEnableRiskEnginePrivileges = async ( + request: KibanaRequest, + security: SecurityPluginStart +) => { + return checkAndFormatPrivileges({ + request, + security, + privilegesToCheck: { + elasticsearch: { + cluster: TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, index: RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES, }, }, @@ -96,11 +113,11 @@ export const withRiskEnginePrivilegeCheck = ( response: KibanaResponseFactory ) => { const [_, { security }] = await getStartServices(); - const privileges = await getUserRiskEnginePrivileges(request, security); - if ( - !privileges.has_all_required && - !privileges.privileges.elasticsearch.cluster?.manage_transform - ) { + const privilegeCheckFn = request.route.path.includes('/risk_score/engine/schedule_now') + ? getRunRiskEnginePrivileges + : getEnableRiskEnginePrivileges; + const privileges = await privilegeCheckFn(request, security); + if (!privileges.has_all_required) { const siemResponse = buildSiemResponse(response); return siemResponse.error({ statusCode: 403, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts index 307da6980da50..600f78770b03a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/privileges.ts @@ -14,7 +14,7 @@ import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import { RiskScoreAuditActions } from '../../risk_score/audit'; import type { EntityAnalyticsRoutesDeps } from '../../types'; -import { getUserRiskEnginePrivileges } from '../risk_engine_privileges'; +import { getEnableRiskEnginePrivileges } from '../risk_engine_privileges'; export const riskEnginePrivilegesRoute = ( router: EntityAnalyticsRoutesDeps['router'], @@ -41,7 +41,7 @@ export const riskEnginePrivilegesRoute = ( const [_, { security }] = await getStartServices(); const securitySolution = await context.securitySolution; - const body = await getUserRiskEnginePrivileges(request, security); + const body = await getEnableRiskEnginePrivileges(request, security); securitySolution.getAuditLogger()?.log({ message: 'User checked if they have the required privileges to configure the risk engine', From c3cdd0013b584832d71a04dbd80bd192a87f3fbc Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 12:11:57 +0530 Subject: [PATCH 07/17] Adding changes to re-use the missingRiskEnginePrivileges function --- .../risk_engine/privileges.ts | 15 ++++++- .../translations.tsx | 44 +++++++++++++------ .../use_missing_risk_engine_privileges.ts | 11 +---- .../entity_analytics_management_page.tsx | 8 ++-- .../risk_engine/risk_engine_privileges.ts | 31 +++++++++---- 5 files changed, 73 insertions(+), 36 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts index 40ae11caa050d..1d3c726e9a958 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.ts @@ -10,10 +10,14 @@ import type { EntityAnalyticsPrivileges } from '../../api/entity_analytics'; import type { RiskEngineIndexPrivilege } from './constants'; import { TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, + TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES, } from './constants'; -export type MissingClusterPrivileges = string[]; +export interface MissingClusterPrivileges { + enable: string[]; + run: string[]; +} export type MissingIndexPrivileges = Array; export interface MissingPrivileges { @@ -58,9 +62,16 @@ export const getMissingRiskEnginePrivileges = ( TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES.filter( (privilege) => !privileges.elasticsearch.cluster?.[privilege] ); + const missingClusterPrivilegesToRunEngine = + TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES.filter( + (privilege) => !privileges.elasticsearch.cluster?.[privilege] + ); return { indexPrivileges: missingIndexPrivileges, - clusterPrivileges: missingClusterPrivilegesToEnableEngine, + clusterPrivileges: { + enable: missingClusterPrivilegesToEnableEngine, + run: missingClusterPrivilegesToRunEngine, + }, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/risk_engine_privileges_callout/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/risk_engine_privileges_callout/translations.tsx index d7e45c0ff80f3..f6bf05f9c01c5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/risk_engine_privileges_callout/translations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/risk_engine_privileges_callout/translations.tsx @@ -66,20 +66,36 @@ export const MissingPrivilegesCallOutBody: React.FC = ({ ) : null, - clusterPrivileges: - clusterPrivileges.length > 0 ? ( - <> - -
    - {clusterPrivileges.map((privilege) => ( -
  • {privilege}
  • - ))} -
- - ) : null, + clusterPrivileges: ( + <> + {clusterPrivileges.enable.length > 0 && ( + <> + +
    + {clusterPrivileges.enable.map((privilege) => ( +
  • {privilege}
  • + ))} +
+ + )} + {clusterPrivileges.run.length > 0 && ( + <> + +
    + {clusterPrivileges.run.map((privilege) => ( +
  • {privilege}
  • + ))} +
+ + )} + + ), }} /> ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts index f9a07684bbb9c..c495867299af2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts @@ -40,13 +40,6 @@ export const useMissingRiskEnginePrivileges = ( }; } - if (privilegesResponse.has_all_required) { - return { - isLoading: false, - hasAllRequiredPrivileges: true, - }; - } - const requiredIndexPrivileges: NonEmptyArray = readonly ? ['read'] : ['read', 'write']; @@ -60,7 +53,7 @@ export const useMissingRiskEnginePrivileges = ( // Here we check if there are no missing privileges of the provided set of required privileges if ( indexPrivileges.every(([_, missingPrivileges]) => missingPrivileges.length === 0) && - (readonly || clusterPrivileges.length === 0) // cluster privileges check is required for write operations + (readonly || (clusterPrivileges.run.length === 0 && clusterPrivileges.enable.length === 0)) // cluster privileges check is required for write operations ) { return { isLoading: false, @@ -73,7 +66,7 @@ export const useMissingRiskEnginePrivileges = ( hasAllRequiredPrivileges: false, missingPrivileges: { indexPrivileges, - clusterPrivileges: readonly ? [] : clusterPrivileges, // cluster privileges are not required for readonly + clusterPrivileges: readonly ? { enable: ['N/A'], run: ['N/A'] } : clusterPrivileges, // cluster privileges are not required for readonly }, }; }, [isLoading, privilegesResponse, readonly]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx index f502dffbc7ee6..bd1b9854fed1c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/pages/entity_analytics_management_page.tsx @@ -21,7 +21,6 @@ import { RiskScoreEnableSection } from '../components/risk_score_enable_section' import { ENTITY_ANALYTICS_RISK_SCORE } from '../../app/translations'; import { RiskEnginePrivilegesCallOut } from '../components/risk_engine_privileges_callout'; import { useMissingRiskEnginePrivileges } from '../hooks/use_missing_risk_engine_privileges'; -import { useRiskEnginePrivileges } from '../api/hooks/use_risk_engine_privileges'; import { RiskScoreUsefulLinksSection } from '../components/risk_score_useful_links_section'; import { RiskScoreConfigurationSection } from '../components/risk_score_configuration_section'; import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status'; @@ -37,7 +36,6 @@ export const EntityAnalyticsManagementPage = () => { const { euiTheme } = useEuiTheme(); const styles = getEntityAnalyticsRiskScorePageStyles(euiTheme); const privileges = useMissingRiskEnginePrivileges(); - const { data: riskEnginePrivileges } = useRiskEnginePrivileges(); const { data: riskEngineSettings } = useRiskEngineSettings(); const includeClosedAlerts = riskEngineSettings?.includeClosedAlerts ?? false; const from = riskEngineSettings?.range?.start ?? 'now-30d'; @@ -52,7 +50,11 @@ export const EntityAnalyticsManagementPage = () => { const { mutate: scheduleNowRiskEngine } = useScheduleNowRiskEngineMutation(); const { addSuccess, addError } = useAppToasts(); const userCanRunEngine = - riskEnginePrivileges?.privileges?.elasticsearch?.cluster?.manage_transform || false; + (!privileges.isLoading && + (privileges.hasAllRequiredPrivileges || + (!privileges.hasAllRequiredPrivileges && + privileges.missingPrivileges?.clusterPrivileges?.run?.length === 0))) || + false; const handleRunEngineClick = async () => { setIsLoading(true); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index a9460dee64917..bf8452d998c73 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -74,14 +74,29 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti ) .join('\n'); - const clusterPrivilegesMessage = !clusterPrivileges.length + const clusterRunPrivilegesMessage = !clusterPrivileges.run.length ? '' - : i18n.translate('xpack.securitySolution.entityAnalytics.riskEngine.missingClusterPrivilege', { - defaultMessage: 'Missing cluster privileges: {privileges}.', - values: { - privileges: clusterPrivileges.join(', '), - }, - }); + : i18n.translate( + 'xpack.securitySolution.entityAnalytics.riskEngine.missingClusterRunPrivilege', + { + defaultMessage: 'Missing cluster privileges to run risk score engine: {privileges}.', + values: { + privileges: clusterPrivileges.run.join(', '), + }, + } + ); + + const clusterEnablePrivilegesMessage = !clusterPrivileges.enable.length + ? '' + : i18n.translate( + 'xpack.securitySolution.entityAnalytics.riskEngine.missingClusterEnablePrivilege', + { + defaultMessage: 'Missing cluster privileges to enable risk score engine: {privileges}.', + values: { + privileges: clusterPrivileges.enable.join(', '), + }, + } + ); const unauthorizedMessage = i18n.translate( 'xpack.securitySolution.entityAnalytics.riskEngine.unauthorized', @@ -90,7 +105,7 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti } ); - return `${unauthorizedMessage} ${indexPrivilegesMessage} ${clusterPrivilegesMessage}`; + return `${unauthorizedMessage} ${indexPrivilegesMessage} ${clusterRunPrivilegesMessage} ${clusterEnablePrivilegesMessage}`; }; /** From 6ef43bc5fa24062e38d5a0e67369af1d4f2432f8 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 13:21:26 +0530 Subject: [PATCH 08/17] Adding comments in the constants.ts file --- .../common/entity_analytics/risk_engine/constants.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts index 036e4de9c381a..6d4b936dcfec1 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/constants.ts @@ -20,13 +20,14 @@ export const RISK_ENGINE_CLEANUP_URL = `${PUBLIC_RISK_ENGINE_URL}/dangerously_de export const RISK_ENGINE_CONFIGURE_SO_URL = `${PUBLIC_RISK_ENGINE_URL}/saved_object/configure` as const; -type ClusterPrivilege = 'manage_index_templates' | 'manage_transform' | 'manage_ingest_pipelines'; +type ClusterPrivilege = 'manage_index_templates' | 'manage_transform' | 'manage_ingest_pipelines'; +// These are the required privileges to install the risk engine - enabling and running require less privileges +// However, we check the full set for simplicity, since the UI does not distinguish between installing and enabling export const TO_RUN_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ 'manage_transform', ] as ClusterPrivilege[]; export const TO_ENABLE_RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES = [ - = [ 'manage_index_templates', 'manage_transform', 'manage_ingest_pipelines', From d73c4ea361f0fac919de39974dec18a86e9eb884 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 13:51:38 +0530 Subject: [PATCH 09/17] Adding changes related to translations --- .../plugins/private/translations/translations/fr-FR.json | 3 +-- .../plugins/private/translations/translations/ja-JP.json | 3 +-- .../plugins/private/translations/translations/zh-CN.json | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 7a25c64bcf3b6..14a773f51d0f4 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -39179,7 +39179,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.noEntity.riskLevelTitle": "Niveau de risque", "xpack.securitySolution.entityAnalytics.riskDashboard.riskLevelTitle": "Score de risque de {riskEntity}", "xpack.securitySolution.entityAnalytics.riskDashboard.viewAllLabel": "Afficher tout", - "xpack.securitySolution.entityAnalytics.riskEngine.missingClusterPrivilege": "Privilèges de cluster manquants : {privileges}.", "xpack.securitySolution.entityAnalytics.riskEngine.missingIndexPrivilege": "Privilèges d'index manquants pour l'index \"{indexName}\" : {privileges}.", "xpack.securitySolution.entityAnalytics.riskEngine.unauthorized": "L'utilisateur ne dispose pas des privilèges requis pour modifier le moteur de risque.", "xpack.securitySolution.entityAnalytics.riskScore.chart.totalLabel": "Total", @@ -49202,4 +49201,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.", "xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes." } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 65feb9c097a61..2d6a12876f891 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -39149,7 +39149,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.noEntity.riskLevelTitle": "リスクレベル", "xpack.securitySolution.entityAnalytics.riskDashboard.riskLevelTitle": "{riskEntity}リスクレベル", "xpack.securitySolution.entityAnalytics.riskDashboard.viewAllLabel": "すべて表示", - "xpack.securitySolution.entityAnalytics.riskEngine.missingClusterPrivilege": "クラスター権限が不足しています:{privileges}。", "xpack.securitySolution.entityAnalytics.riskEngine.missingIndexPrivilege": "インデックス\"{indexName}\"のインデックス権限が不足しています:{privileges}。", "xpack.securitySolution.entityAnalytics.riskEngine.unauthorized": "ユーザーにはリスクエンジン権限がありません。", "xpack.securitySolution.entityAnalytics.riskScore.chart.totalLabel": "合計", @@ -49165,4 +49164,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 2f584023723e6..ce1241cb0a584 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -39214,7 +39214,6 @@ "xpack.securitySolution.entityAnalytics.riskDashboard.noEntity.riskLevelTitle": "风险级别", "xpack.securitySolution.entityAnalytics.riskDashboard.riskLevelTitle": "{riskEntity} 风险级别", "xpack.securitySolution.entityAnalytics.riskDashboard.viewAllLabel": "查看全部", - "xpack.securitySolution.entityAnalytics.riskEngine.missingClusterPrivilege": "缺少集群权限:{privileges}。", "xpack.securitySolution.entityAnalytics.riskEngine.missingIndexPrivilege": "缺少索引“{indexName}”的索引权限:{privileges}。", "xpack.securitySolution.entityAnalytics.riskEngine.unauthorized": "用户缺少风险引擎权限。", "xpack.securitySolution.entityAnalytics.riskScore.chart.totalLabel": "合计", @@ -49241,4 +49240,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} From a32605d3971698ffca45f02039732d4a3c477213 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 14:30:45 +0530 Subject: [PATCH 10/17] Adding the correct type in the test --- .../common/user_has_risk_engine_read_permissions.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/common/user_has_risk_engine_read_permissions.test.ts b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/common/user_has_risk_engine_read_permissions.test.ts index 6425b68175935..55146abde1776 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/common/user_has_risk_engine_read_permissions.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/common/user_has_risk_engine_read_permissions.test.ts @@ -22,7 +22,7 @@ describe('userHasRiskEngineReadPermissions', () => { isLoading: false, hasAllRequiredPrivileges: false, missingPrivileges: { - clusterPrivileges: [], + clusterPrivileges: { enable: ['N/A'], run: ['N/A'] }, indexPrivileges: [['risk-score.risk-score-*', ['read']]], }, }) @@ -35,7 +35,7 @@ describe('userHasRiskEngineReadPermissions', () => { isLoading: false, hasAllRequiredPrivileges: false, missingPrivileges: { - clusterPrivileges: [], + clusterPrivileges: { enable: ['N/A'], run: ['N/A'] }, indexPrivileges: [['other-index.other-index-*', ['read']]], }, }) @@ -48,7 +48,7 @@ describe('userHasRiskEngineReadPermissions', () => { isLoading: false, hasAllRequiredPrivileges: false, missingPrivileges: { - clusterPrivileges: [], + clusterPrivileges: { enable: ['N/A'], run: ['N/A'] }, indexPrivileges: [['risk-score.risk-score-*', ['write']]], }, }) From 6adff37d0245a6565e34f3e802b55846e6f63a1c Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 15:07:14 +0530 Subject: [PATCH 11/17] Correcting the missingPrivilegesTypes in tests --- .../common/entity_analytics/risk_engine/privileges.test.ts | 7 +++++-- .../entity_store/components/enablement_modal.test.tsx | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts index 34048149a7c58..a3a20f4c7f560 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/risk_engine/privileges.test.ts @@ -29,7 +29,10 @@ describe('getMissingRiskEnginePrivileges', () => { const missingPrivileges = getMissingRiskEnginePrivileges(noClusterPrivileges); expect(missingPrivileges).toEqual({ - clusterPrivileges: ['manage_index_templates', 'manage_transform', 'manage_ingest_pipelines'], + clusterPrivileges: { + enable: ['manage_index_templates', 'manage_transform', 'manage_ingest_pipelines'], + run: ['manage_transform'], + }, indexPrivileges: [], }); }); @@ -54,7 +57,7 @@ describe('getMissingRiskEnginePrivileges', () => { const missingPrivileges = getMissingRiskEnginePrivileges(noIndexPrivileges); expect(missingPrivileges).toEqual({ - clusterPrivileges: [], + clusterPrivileges: { enable: [], run: [] }, indexPrivileges: [['risk-score.risk-score-*', ['read', 'write']]], }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.test.tsx index 4537fdb81dceb..b479d854a423e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/components/entity_store/components/enablement_modal.test.tsx @@ -83,7 +83,7 @@ const missingRiskEnginePrivileges: RiskEngineMissingPrivilegesResponse = { isLoading: false, hasAllRequiredPrivileges: false, missingPrivileges: { - clusterPrivileges: [], + clusterPrivileges: { enable: [], run: [] }, indexPrivileges: [], }, }; From 42e5a1620558b849a0ac131ea729b88c948f8f43 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 20:01:09 +0530 Subject: [PATCH 12/17] Snapshot update --- .../risk_engine/risk_engine_privileges.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts index d8cc305a29b77..23739ceb4624d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts @@ -32,7 +32,7 @@ describe('_getMissingPrivilegesMessage', () => { const result = _getMissingPrivilegesMessage(noClusterPrivileges); expect(result).toMatchInlineSnapshot( - `"User is missing risk engine privileges. Missing cluster privileges: manage_index_templates, manage_transform, manage_ingest_pipelines."` + `"User is missing risk engine privileges. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` ); }); @@ -59,7 +59,7 @@ describe('_getMissingPrivilegesMessage', () => { const result = _getMissingPrivilegesMessage(noIndexPrivileges); expect(result).toMatchInlineSnapshot( - `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. "` + `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. "` ); }); @@ -86,7 +86,7 @@ describe('_getMissingPrivilegesMessage', () => { const result = _getMissingPrivilegesMessage(noClusterOrIndexPrivileges); expect(result).toMatchInlineSnapshot( - `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. Missing cluster privileges: manage_index_templates, manage_transform, manage_ingest_pipelines."` + `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` ); }); }); From 11057bf3061ba662d44fac4c4c08bf25fe46f720 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 28 Mar 2025 21:46:44 +0530 Subject: [PATCH 13/17] Coorecting the respopnse message in tests for missing risk engine privileges --- .../lib/entity_analytics/risk_engine/routes/delete.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts index 29e43de5d8fb7..d9db528fad9d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts @@ -178,7 +178,7 @@ describe('risk engine cleanup route', () => { expect(response.status).toBe(403); expect(response.body).toEqual({ message: - 'User is missing risk engine privileges. Missing cluster privileges: manage_index_templates, manage_transform, manage_ingest_pipelines.', + 'User is missing risk engine privileges. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines.', status_code: 403, }); }); From e18673ab01b7f2a9a5d4f213b755d0367256ca38 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Thu, 17 Apr 2025 12:08:46 +0530 Subject: [PATCH 14/17] Adding change to have a parameter in withRiskEnginePrivilegeCheck --- .../risk_engine/risk_engine_privileges.ts | 8 +-- .../risk_engine/routes/schedule_now.ts | 70 ++++++++++--------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index bf8452d998c73..5e7ddbd01383d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -120,7 +120,8 @@ export const withRiskEnginePrivilegeCheck = ( context: SecuritySolutionRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory - ) => Promise + ) => Promise, + privilegeType: 'run' | 'enable' = 'enable' ) => { return async ( context: SecuritySolutionRequestHandlerContext, @@ -128,9 +129,8 @@ export const withRiskEnginePrivilegeCheck = ( response: KibanaResponseFactory ) => { const [_, { security }] = await getStartServices(); - const privilegeCheckFn = request.route.path.includes('/risk_score/engine/schedule_now') - ? getRunRiskEnginePrivileges - : getEnableRiskEnginePrivileges; + const privilegeCheckFn = + privilegeType === 'run' ? getRunRiskEnginePrivileges : getEnableRiskEnginePrivileges; const privileges = await privilegeCheckFn(request, security); if (!privileges.has_all_required) { const siemResponse = buildSiemResponse(response); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts index 99ec60b281293..687e30c2a139d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts @@ -35,44 +35,48 @@ export const riskEngineScheduleNowRoute = ( }) .addVersion( { version: API_VERSIONS.public.v1, validate: {} }, - withRiskEnginePrivilegeCheck(getStartServices, async (context, request, response) => { - const securitySolution = await context.securitySolution; + withRiskEnginePrivilegeCheck( + getStartServices, + async (context, request, response) => { + const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to schedule the risk engine.', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_SCHEDULE_NOW, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to schedule the risk engine.', + event: { + action: RiskEngineAuditActions.RISK_ENGINE_SCHEDULE_NOW, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CHANGE, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); - const siemResponse = buildSiemResponse(response); - const [_, { taskManager }] = await getStartServices(); + const siemResponse = buildSiemResponse(response); + const [_, { taskManager }] = await getStartServices(); - const riskEngineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); - if (!taskManager) { - return siemResponse.error({ - statusCode: 400, - body: TASK_MANAGER_UNAVAILABLE_ERROR, - }); - } + if (!taskManager) { + return siemResponse.error({ + statusCode: 400, + body: TASK_MANAGER_UNAVAILABLE_ERROR, + }); + } - try { - await riskEngineClient.scheduleNow({ taskManager }); - const body: RiskEngineScheduleNowResponse = { success: true }; - return response.ok({ body }); - } catch (e) { - const error = transformError(e); + try { + await riskEngineClient.scheduleNow({ taskManager }); + const body: RiskEngineScheduleNowResponse = { success: true }; + return response.ok({ body }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - }) + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + }, + 'run' + ) ); }; From 916781d0edf10c0b0b1f2ba73da1bcccd35ac2d0 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Thu, 24 Apr 2025 20:06:30 +0530 Subject: [PATCH 15/17] Addressing review comments --- .../use_missing_risk_engine_privileges.ts | 2 +- .../risk_engine/risk_engine_privileges.ts | 8 +-- .../risk_engine/routes/schedule_now.ts | 70 +++++++++---------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts index 5308540aa4fd7..8b1da9098baa3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/entity_analytics/hooks/use_missing_risk_engine_privileges.ts @@ -66,7 +66,7 @@ export const useMissingRiskEnginePrivileges = ( hasAllRequiredPrivileges: false, missingPrivileges: { indexPrivileges, - clusterPrivileges: readonly ? { enable: ['N/A'], run: ['N/A'] } : clusterPrivileges, // cluster privileges are not required for readonly + clusterPrivileges: readonly ? { enable: [], run: [] } : clusterPrivileges, // cluster privileges are not required for readonly }, }; }, [isLoading, privilegesResponse, readonly]); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index 5e7ddbd01383d..d926800191eba 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -79,7 +79,7 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti : i18n.translate( 'xpack.securitySolution.entityAnalytics.riskEngine.missingClusterRunPrivilege', { - defaultMessage: 'Missing cluster privileges to run risk score engine: {privileges}.', + defaultMessage: 'Missing cluster privileges to run the risk engine: {privileges}.', values: { privileges: clusterPrivileges.run.join(', '), }, @@ -91,7 +91,7 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti : i18n.translate( 'xpack.securitySolution.entityAnalytics.riskEngine.missingClusterEnablePrivilege', { - defaultMessage: 'Missing cluster privileges to enable risk score engine: {privileges}.', + defaultMessage: 'Missing cluster privileges to enable the risk engine: {privileges}.', values: { privileges: clusterPrivileges.enable.join(', '), }, @@ -115,13 +115,13 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti * @param handler - The route handler to wrap **/ export const withRiskEnginePrivilegeCheck = ( + privilegeType: 'run' | 'enable' = 'enable', getStartServices: StartServicesAccessor, handler: ( context: SecuritySolutionRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory - ) => Promise, - privilegeType: 'run' | 'enable' = 'enable' + ) => Promise ) => { return async ( context: SecuritySolutionRequestHandlerContext, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts index 687e30c2a139d..207266a48e575 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/schedule_now.ts @@ -35,48 +35,44 @@ export const riskEngineScheduleNowRoute = ( }) .addVersion( { version: API_VERSIONS.public.v1, validate: {} }, - withRiskEnginePrivilegeCheck( - getStartServices, - async (context, request, response) => { - const securitySolution = await context.securitySolution; + withRiskEnginePrivilegeCheck('run', getStartServices, async (context, request, response) => { + const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to schedule the risk engine.', - event: { - action: RiskEngineAuditActions.RISK_ENGINE_SCHEDULE_NOW, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CHANGE, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to schedule the risk engine.', + event: { + action: RiskEngineAuditActions.RISK_ENGINE_SCHEDULE_NOW, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CHANGE, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); - const siemResponse = buildSiemResponse(response); - const [_, { taskManager }] = await getStartServices(); + const siemResponse = buildSiemResponse(response); + const [_, { taskManager }] = await getStartServices(); - const riskEngineClient = securitySolution.getRiskEngineDataClient(); + const riskEngineClient = securitySolution.getRiskEngineDataClient(); - if (!taskManager) { - return siemResponse.error({ - statusCode: 400, - body: TASK_MANAGER_UNAVAILABLE_ERROR, - }); - } + if (!taskManager) { + return siemResponse.error({ + statusCode: 400, + body: TASK_MANAGER_UNAVAILABLE_ERROR, + }); + } - try { - await riskEngineClient.scheduleNow({ taskManager }); - const body: RiskEngineScheduleNowResponse = { success: true }; - return response.ok({ body }); - } catch (e) { - const error = transformError(e); + try { + await riskEngineClient.scheduleNow({ taskManager }); + const body: RiskEngineScheduleNowResponse = { success: true }; + return response.ok({ body }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - }, - 'run' - ) + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + }) ); }; From a728810f67830ef3030b4c6f868da71a4e0d8a9e Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 25 Apr 2025 11:44:54 +0530 Subject: [PATCH 16/17] Adding privilegeType as the first parameter --- .../risk_engine/risk_engine_privileges.ts | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts index d926800191eba..eba5cc7a50201 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.ts @@ -115,14 +115,53 @@ export const _getMissingPrivilegesMessage = (riskEnginePrivileges: EntityAnalyti * @param handler - The route handler to wrap **/ export const withRiskEnginePrivilegeCheck = ( - privilegeType: 'run' | 'enable' = 'enable', - getStartServices: StartServicesAccessor, - handler: ( + privilegeTypeOrServices: + | 'run' + | 'enable' + | StartServicesAccessor, + handlerOrServices: + | (( + context: SecuritySolutionRequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => Promise) + | StartServicesAccessor, + optionalHandler?: ( context: SecuritySolutionRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory ) => Promise ) => { + // Determine if privilegeType is provided or if it's the default case + let privilegeType: 'run' | 'enable' = 'enable'; + let getStartServices: StartServicesAccessor; + let handler: ( + context: SecuritySolutionRequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => Promise; + + if (typeof privilegeTypeOrServices === 'string') { + // First parameter is the privilegeType + privilegeType = privilegeTypeOrServices; + getStartServices = handlerOrServices as StartServicesAccessor< + SecuritySolutionPluginStartDependencies, + unknown + >; + if (optionalHandler === undefined) { + throw new Error('Handler is required when using privilege type parameter'); + } + handler = optionalHandler; + } else { + // First parameter is getStartServices, privilegeType is default 'enable' + getStartServices = privilegeTypeOrServices; + handler = handlerOrServices as ( + context: SecuritySolutionRequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) => Promise; + } + return async ( context: SecuritySolutionRequestHandlerContext, request: KibanaRequest, From 4143afb4d44b272fa1540dd3284895194f2bc236 Mon Sep 17 00:00:00 2001 From: abhishekbhatia1710 Date: Fri, 25 Apr 2025 14:02:42 +0530 Subject: [PATCH 17/17] Changes for correcting the error messages in tests and snapshot --- .../risk_engine/risk_engine_privileges.test.ts | 4 ++-- .../lib/entity_analytics/risk_engine/routes/delete.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts index 23739ceb4624d..394a5a241cf1d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/risk_engine_privileges.test.ts @@ -32,7 +32,7 @@ describe('_getMissingPrivilegesMessage', () => { const result = _getMissingPrivilegesMessage(noClusterPrivileges); expect(result).toMatchInlineSnapshot( - `"User is missing risk engine privileges. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` + `"User is missing risk engine privileges. Missing cluster privileges to run the risk engine: manage_transform. Missing cluster privileges to enable the risk engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` ); }); @@ -86,7 +86,7 @@ describe('_getMissingPrivilegesMessage', () => { const result = _getMissingPrivilegesMessage(noClusterOrIndexPrivileges); expect(result).toMatchInlineSnapshot( - `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` + `"User is missing risk engine privileges. Missing index privileges for index \\"risk-score.risk-score-*\\": read, write. Missing cluster privileges to run the risk engine: manage_transform. Missing cluster privileges to enable the risk engine: manage_index_templates, manage_transform, manage_ingest_pipelines."` ); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts index 74f6ebfa59deb..f7b0882a02b1c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/risk_engine/routes/delete.test.ts @@ -176,7 +176,7 @@ describe('risk engine cleanup route', () => { expect(response.status).toBe(403); expect(response.body).toEqual({ message: - 'User is missing risk engine privileges. Missing cluster privileges to run risk score engine: manage_transform. Missing cluster privileges to enable risk score engine: manage_index_templates, manage_transform, manage_ingest_pipelines.', + 'User is missing risk engine privileges. Missing cluster privileges to run the risk engine: manage_transform. Missing cluster privileges to enable the risk engine: manage_index_templates, manage_transform, manage_ingest_pipelines.', status_code: 403, }); });