-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Entity Analytics] Add risk engine missing privileges callout (#171250)
## Summary _note: this is currently behind the experimental feature flag `riskEnginePrivilegesRouteEnabled`._ Add a callout to the Entity Risk Score Management page if the user doesn't have sufficient privileges. Here is the callout with a user with none of the privileges (missing privileges are dynamically shown) <img width="1177" alt="Screenshot 2023-11-21 at 12 52 21" src="https://github.com/elastic/kibana/assets/3315046/0c4a17ee-8856-45a5-8798-1cef0e7fe0ad"> as part of this I have added a route `GET /internal/risk_score/engine/privileges` the response payload looks like this: ``` { "privileges": { "kibana": { "feature_savedObjectsManagement.all": false }, "elasticsearch": { "cluster": { "manage_transform": false, "manage_index_templates": false }, "index": { "risk-score.risk-score-*": { "read": false, "write": false } } } }, "has_all_required": false // does the user have all privileges? } ``` Docs issue for associated documentation changes elastic/security-docs#4307 ### Testing - cypress tests added for the no banner case (user has all privs), and the worst case (user has none of the privs) - API Integration tests added for all of the granular cases - Manual test steps - 1. User has correct privileges - Create a user with all risk engine privileges - navigate to the Entity Risk Score Management page - missing privileges banner should not show - 2. User has missing privileges - Create a user with some or no risk engine privileges - navigate to the Entity Risk Score Management page - banner should show and describe all privileges missing ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) --------- Co-authored-by: kibanamachine <[email protected]>
- Loading branch information
1 parent
0b0110a
commit a4aa711
Showing
27 changed files
with
764 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
...plugins/security_solution/public/entity_analytics/api/hooks/use_risk_engine_privileges.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* 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 } from '@tanstack/react-query'; | ||
import { fetchRiskEnginePrivileges } from '../api'; | ||
|
||
export const useRiskEnginePrivileges = () => { | ||
return useQuery(['GET', 'FETCH_RISK_ENGINE_PRIVILEGES'], fetchRiskEnginePrivileges); | ||
}; |
8 changes: 8 additions & 0 deletions
8
...rity_solution/public/entity_analytics/components/risk_engine_privileges_callout/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { RiskEnginePrivilegesCallOut } from './risk_engine_privileges_callout'; |
31 changes: 31 additions & 0 deletions
31
...ty_analytics/components/risk_engine_privileges_callout/risk_engine_privileges_callout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* 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 type { CallOutMessage } from '../../../common/components/callouts'; | ||
import { CallOutSwitcher } from '../../../common/components/callouts'; | ||
import { MissingPrivilegesCallOutBody, MISSING_PRIVILEGES_CALLOUT_TITLE } from './translations'; | ||
import { useMissingPrivileges } from './use_missing_risk_engine_privileges'; | ||
|
||
export const RiskEnginePrivilegesCallOut = () => { | ||
const privileges = useMissingPrivileges(); | ||
|
||
if (privileges.isLoading || privileges.hasAllRequiredPrivileges) { | ||
return null; | ||
} | ||
|
||
const message: CallOutMessage = { | ||
type: 'primary', | ||
id: `missing-risk-engine-privileges`, | ||
title: MISSING_PRIVILEGES_CALLOUT_TITLE, | ||
description: <MissingPrivilegesCallOutBody {...privileges.missingPrivileges} />, | ||
}; | ||
|
||
return ( | ||
message && <CallOutSwitcher namespace="entity_analytics" condition={true} message={message} /> | ||
); | ||
}; |
97 changes: 97 additions & 0 deletions
97
...lution/public/entity_analytics/components/risk_engine_privileges_callout/translations.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* 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 { EuiCode, EuiLink } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
import React from 'react'; | ||
import { useKibana } from '../../../common/lib/kibana'; | ||
import { CommaSeparatedValues } from '../../../detections/components/callouts/missing_privileges_callout/comma_separated_values'; | ||
import type { MissingPrivileges } from './use_missing_risk_engine_privileges'; | ||
|
||
export const MISSING_PRIVILEGES_CALLOUT_TITLE = i18n.translate( | ||
'xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageTitle', | ||
{ | ||
defaultMessage: 'Insufficient privileges', | ||
} | ||
); | ||
|
||
export const MissingPrivilegesCallOutBody: React.FC<MissingPrivileges> = ({ | ||
indexPrivileges, | ||
clusterPrivileges, | ||
}) => { | ||
const { docLinks } = useKibana().services; | ||
|
||
return ( | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageBody.messageDetail" | ||
defaultMessage="{essence} {indexPrivileges} {clusterPrivileges} " | ||
values={{ | ||
essence: ( | ||
<p> | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageBody.essenceDescription" | ||
defaultMessage="You need the following privileges to fully access this functionality. Contact your administrator for further assistance. Read more about {docs}." | ||
values={{ | ||
docs: ( | ||
<EuiLink | ||
href={docLinks.links.securitySolution.entityAnalytics.riskScorePrerequisites} | ||
target="_blank" | ||
> | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.riskEngineRequirementsDocLink" | ||
defaultMessage="Risk Scoring prerequisites" | ||
/> | ||
</EuiLink> | ||
), | ||
}} | ||
/> | ||
</p> | ||
), | ||
indexPrivileges: | ||
indexPrivileges.length > 0 ? ( | ||
<> | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageBody.indexPrivilegesTitle" | ||
defaultMessage="Missing Elasticsearch index privileges:" | ||
/> | ||
<ul> | ||
{indexPrivileges.map(([index, missingPrivileges]) => ( | ||
<li key={index}>{missingIndexPrivileges(index, missingPrivileges)}</li> | ||
))} | ||
</ul> | ||
</> | ||
) : null, | ||
clusterPrivileges: | ||
clusterPrivileges.length > 0 ? ( | ||
<> | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageBody.clusterPrivilegesTitle" | ||
defaultMessage="Missing Elasticsearch cluster privileges:" | ||
/> | ||
<ul> | ||
{clusterPrivileges.map((privilege) => ( | ||
<li key={privilege}>{privilege}</li> | ||
))} | ||
</ul> | ||
</> | ||
) : null, | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
const missingIndexPrivileges = (index: string, privileges: string[]) => ( | ||
<FormattedMessage | ||
id="xpack.securitySolution.riskEngine.missingPrivilegesCallOut.messageBody.missingIndexPrivileges" | ||
defaultMessage="Missing {privileges} privileges for the {index} index." | ||
values={{ | ||
privileges: <CommaSeparatedValues values={privileges} />, | ||
index: <EuiCode>{index}</EuiCode>, | ||
}} | ||
/> | ||
); |
81 changes: 81 additions & 0 deletions
81
...analytics/components/risk_engine_privileges_callout/use_missing_risk_engine_privileges.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* 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 { useMemo } from 'react'; | ||
import type { RiskEnginePrivilegesResponse } from '../../../../server/lib/entity_analytics/risk_engine/types'; | ||
import { useRiskEnginePrivileges } from '../../api/hooks/use_risk_engine_privileges'; | ||
import { | ||
RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES, | ||
RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES, | ||
} from '../../../../common/risk_engine'; | ||
|
||
const getMissingIndexPrivileges = ( | ||
privileges: RiskEnginePrivilegesResponse['privileges']['elasticsearch']['index'] | ||
): MissingIndexPrivileges => { | ||
const missingIndexPrivileges: MissingIndexPrivileges = []; | ||
|
||
for (const [indexName, requiredPrivileges] of Object.entries( | ||
RISK_ENGINE_REQUIRED_ES_INDEX_PRIVILEGES | ||
)) { | ||
const missingPrivileges = requiredPrivileges.filter( | ||
(privilege) => !privileges[indexName][privilege] | ||
); | ||
|
||
if (missingPrivileges.length) { | ||
missingIndexPrivileges.push([indexName, missingPrivileges]); | ||
} | ||
} | ||
|
||
return missingIndexPrivileges; | ||
}; | ||
|
||
export type MissingClusterPrivileges = string[]; | ||
export type MissingIndexPrivileges = Array<[indexName: string, privileges: string[]]>; | ||
|
||
export interface MissingPrivileges { | ||
clusterPrivileges: MissingClusterPrivileges; | ||
indexPrivileges: MissingIndexPrivileges; | ||
} | ||
|
||
export type MissingPrivilegesResponse = | ||
| { isLoading: true } | ||
| { isLoading: false; hasAllRequiredPrivileges: true } | ||
| { isLoading: false; missingPrivileges: MissingPrivileges; hasAllRequiredPrivileges: false }; | ||
|
||
export const useMissingPrivileges = (): MissingPrivilegesResponse => { | ||
const { data: privilegesResponse, isLoading } = useRiskEnginePrivileges(); | ||
|
||
return useMemo<MissingPrivilegesResponse>(() => { | ||
if (isLoading || !privilegesResponse) { | ||
return { | ||
isLoading: true, | ||
}; | ||
} | ||
|
||
if (privilegesResponse.has_all_required) { | ||
return { | ||
isLoading: false, | ||
hasAllRequiredPrivileges: true, | ||
}; | ||
} | ||
|
||
const { privileges } = privilegesResponse; | ||
const missinIndexPrivileges = getMissingIndexPrivileges(privileges.elasticsearch.index); | ||
const missingClusterPrivileges = RISK_ENGINE_REQUIRED_ES_CLUSTER_PRIVILEGES.filter( | ||
(privilege) => !privileges.elasticsearch.cluster[privilege] | ||
); | ||
|
||
return { | ||
isLoading: false, | ||
hasAllRequiredPrivileges: false, | ||
missingPrivileges: { | ||
indexPrivileges: missinIndexPrivileges, | ||
clusterPrivileges: missingClusterPrivileges, | ||
}, | ||
}; | ||
}, [isLoading, privilegesResponse]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.