mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint][Response Actions] Gate response actions history page based on license (#142470)
* Gate response actions history page based on license fixes elastic/security-team/issues/5118 * useUserPrivileges isntead review changes (@paul-tavares) * don't register the route if no access review changes (@paul-tavares) * reset mocked privilege review changes (@paul-tavares)
This commit is contained in:
parent
cd91e866ae
commit
93229592d7
10 changed files with 79 additions and 23 deletions
|
@ -121,6 +121,7 @@ describe('Endpoint Authz service', () => {
|
|||
canSuspendProcess: false,
|
||||
canGetRunningProcesses: false,
|
||||
canAccessResponseConsole: false,
|
||||
canAccessResponseActionsHistory: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ export const calculateEndpointAuthz = (
|
|||
canSuspendProcess: hasEndpointManagementAccess && isEnterpriseLicense,
|
||||
canGetRunningProcesses: hasEndpointManagementAccess && isEnterpriseLicense,
|
||||
canAccessResponseConsole: hasEndpointManagementAccess && isEnterpriseLicense,
|
||||
canAccessResponseActionsHistory: hasEndpointManagementAccess && isPlatinumPlusLicense,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -52,5 +53,6 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => {
|
|||
canSuspendProcess: false,
|
||||
canGetRunningProcesses: false,
|
||||
canAccessResponseConsole: false,
|
||||
canAccessResponseActionsHistory: false,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,6 +28,8 @@ export interface EndpointAuthz {
|
|||
canGetRunningProcesses: boolean;
|
||||
/** If user has permissions to use the Response Actions Console */
|
||||
canAccessResponseConsole: boolean;
|
||||
/** If user has permissions to access the Response Actions History page */
|
||||
canAccessResponseActionsHistory: boolean;
|
||||
}
|
||||
|
||||
export type EndpointAuthzKeyList = Array<keyof EndpointAuthz>;
|
||||
|
|
|
@ -68,7 +68,6 @@ export interface NavTab {
|
|||
}
|
||||
export const securityNavKeys = [
|
||||
SecurityPageName.alerts,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
SecurityPageName.blocklist,
|
||||
SecurityPageName.detectionAndResponse,
|
||||
SecurityPageName.case,
|
||||
|
@ -81,6 +80,7 @@ export const securityNavKeys = [
|
|||
SecurityPageName.hosts,
|
||||
SecurityPageName.network,
|
||||
SecurityPageName.overview,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
SecurityPageName.rules,
|
||||
SecurityPageName.timelines,
|
||||
SecurityPageName.trustedApps,
|
||||
|
|
|
@ -250,16 +250,6 @@ Object {
|
|||
"name": "Blocklist",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/response_actions_history",
|
||||
"data-test-subj": "navigation-response_actions_history",
|
||||
"disabled": false,
|
||||
"href": "securitySolutionUI/response_actions_history",
|
||||
"id": "response_actions_history",
|
||||
"isSelected": false,
|
||||
"name": "Response actions history",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolutionUI/cloud_security_posture-benchmarks",
|
||||
"data-test-subj": "navigation-cloud_security_posture-benchmarks",
|
||||
|
|
|
@ -17,6 +17,7 @@ import { TestProviders } from '../../../mock';
|
|||
import { CASES_FEATURE_ID } from '../../../../../common/constants';
|
||||
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
|
||||
import { useTourContext } from '../../guided_onboarding';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
import {
|
||||
noCasesPermissions,
|
||||
readCasesCapabilities,
|
||||
|
@ -38,6 +39,9 @@ jest.mock('../../../hooks/use_experimental_features');
|
|||
jest.mock('../../../utils/route/use_route_spy');
|
||||
jest.mock('../../../../management/pages/host_isolation_exceptions/view/hooks');
|
||||
jest.mock('../../guided_onboarding');
|
||||
jest.mock('../../user_privileges');
|
||||
|
||||
const mockUseUserPrivileges = useUserPrivileges as jest.Mock;
|
||||
|
||||
describe('useSecuritySolutionNavigation', () => {
|
||||
const mockRouteSpy = [
|
||||
|
@ -56,6 +60,9 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy);
|
||||
(useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValue(true);
|
||||
mockUseUserPrivileges.mockImplementation(() => ({
|
||||
endpointPrivileges: { canReadActionsLogManagement: true },
|
||||
}));
|
||||
(useTourContext as jest.Mock).mockReturnValue({ isTourShown: false });
|
||||
|
||||
const cases = mockCasesContract();
|
||||
|
@ -83,6 +90,10 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockUseUserPrivileges.mockReset();
|
||||
});
|
||||
|
||||
it('should create navigation config', async () => {
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
|
@ -117,6 +128,23 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should omit response actions history if hook reports false', () => {
|
||||
mockUseUserPrivileges.mockImplementation(() => ({
|
||||
endpointPrivileges: { canReadActionsLogManagement: false },
|
||||
}));
|
||||
const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>(
|
||||
() => useSecuritySolutionNavigation(),
|
||||
{ wrapper: TestProviders }
|
||||
);
|
||||
const items = result.current?.items;
|
||||
expect(items).toBeDefined();
|
||||
expect(
|
||||
items!
|
||||
.find((item) => item.id === 'manage')
|
||||
?.items?.find((item) => item.id === 'response_actions_history')
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
describe('Permission gated routes', () => {
|
||||
describe('cases', () => {
|
||||
it('should display the cases navigation item when the user has read permissions', () => {
|
||||
|
|
|
@ -21,6 +21,7 @@ import { SecurityPageName } from '../../../../../common/constants';
|
|||
import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
import { useGlobalQueryString } from '../../../utils/global_query_string';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
|
||||
export const usePrimaryNavigationItems = ({
|
||||
navTabs,
|
||||
|
@ -71,6 +72,8 @@ export const usePrimaryNavigationItems = ({
|
|||
function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
||||
const hasCasesReadPermissions = useGetUserCasesPermissions().read;
|
||||
const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu();
|
||||
const canSeeResponseActionsHistory =
|
||||
useUserPrivileges().endpointPrivileges.canAccessResponseActionsHistory;
|
||||
const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled');
|
||||
|
||||
const uiCapabilities = useKibana().services.application.capabilities;
|
||||
|
@ -138,7 +141,9 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
|||
? [navTabs[SecurityPageName.hostIsolationExceptions]]
|
||||
: []),
|
||||
navTabs[SecurityPageName.blocklist],
|
||||
navTabs[SecurityPageName.responseActionsHistory],
|
||||
...(canSeeResponseActionsHistory
|
||||
? [navTabs[SecurityPageName.responseActionsHistory]]
|
||||
: []),
|
||||
navTabs[SecurityPageName.cloudSecurityPostureBenchmarks],
|
||||
],
|
||||
},
|
||||
|
@ -156,6 +161,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
|||
navTabs,
|
||||
hasCasesReadPermissions,
|
||||
canSeeHostIsolationExceptions,
|
||||
canSeeResponseActionsHistory,
|
||||
isPolicyListEnabled,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -64,13 +64,30 @@ describe('links', () => {
|
|||
expect(filteredLinks).toEqual(links);
|
||||
});
|
||||
|
||||
it('it returns all but response actions history when no access privilege to either response actions history or HIE but have at least one HIE entry', async () => {
|
||||
fakeHttpServices.get.mockResolvedValue({ total: 1 });
|
||||
const filteredLinks = await getManagementFilteredLinks(
|
||||
coreMockStarted,
|
||||
getPlugins(['superuser'])
|
||||
);
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
|
||||
expect(filteredLinks).toEqual({
|
||||
...links,
|
||||
links: links.links?.filter((link) => link.id !== SecurityPageName.responseActionsHistory),
|
||||
});
|
||||
});
|
||||
|
||||
it('it returns filtered links when not having isolation permissions and no host isolation exceptions entry', async () => {
|
||||
fakeHttpServices.get.mockResolvedValue({ total: 0 });
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
|
||||
const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([]));
|
||||
expect(filteredLinks).toEqual({
|
||||
...links,
|
||||
links: links.links?.filter((link) => link.id !== SecurityPageName.hostIsolationExceptions),
|
||||
links: links.links?.filter(
|
||||
(link) =>
|
||||
link.id !== SecurityPageName.hostIsolationExceptions &&
|
||||
link.id !== SecurityPageName.responseActionsHistory
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -221,7 +221,7 @@ export const links: LinkItem = {
|
|||
],
|
||||
};
|
||||
|
||||
const getFilteredLinks = (linkIds: SecurityPageName[]) => ({
|
||||
const excludeLinks = (linkIds: SecurityPageName[]) => ({
|
||||
...links,
|
||||
links: links.links?.filter((link) => !linkIds.includes(link.id)),
|
||||
});
|
||||
|
@ -238,19 +238,26 @@ export const getManagementFilteredLinks = async (
|
|||
currentUserResponse.roles
|
||||
);
|
||||
if (!privileges.canAccessEndpointManagement) {
|
||||
return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
|
||||
return excludeLinks([
|
||||
SecurityPageName.hostIsolationExceptions,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
]);
|
||||
}
|
||||
if (!privileges.canIsolateHost) {
|
||||
if (!privileges.canIsolateHost || !privileges.canAccessResponseActionsHistory) {
|
||||
const hostIsolationExceptionsApiClientInstance = HostIsolationExceptionsApiClient.getInstance(
|
||||
core.http
|
||||
);
|
||||
const summaryResponse = await hostIsolationExceptionsApiClientInstance.summary();
|
||||
if (!summaryResponse.total) {
|
||||
return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
|
||||
return excludeLinks([
|
||||
SecurityPageName.hostIsolationExceptions,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
]);
|
||||
}
|
||||
return excludeLinks([SecurityPageName.responseActionsHistory]);
|
||||
}
|
||||
} catch {
|
||||
return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
|
||||
return excludeLinks([SecurityPageName.hostIsolationExceptions]);
|
||||
}
|
||||
|
||||
return links;
|
||||
|
|
|
@ -76,7 +76,8 @@ const ResponseActionsTelemetry = () => (
|
|||
);
|
||||
|
||||
export const ManagementContainer = memo(() => {
|
||||
const { loading, canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges;
|
||||
const { loading, canAccessEndpointManagement, canAccessResponseActionsHistory } =
|
||||
useUserPrivileges().endpointPrivileges;
|
||||
|
||||
// Lets wait until we can verify permissions
|
||||
if (loading) {
|
||||
|
@ -103,10 +104,12 @@ export const ManagementContainer = memo(() => {
|
|||
component={HostIsolationExceptionsTelemetry}
|
||||
/>
|
||||
<Route path={MANAGEMENT_ROUTING_BLOCKLIST_PATH} component={BlocklistContainer} />
|
||||
<Route
|
||||
path={MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH}
|
||||
component={ResponseActionsTelemetry}
|
||||
/>
|
||||
{canAccessResponseActionsHistory && (
|
||||
<Route
|
||||
path={MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH}
|
||||
component={ResponseActionsTelemetry}
|
||||
/>
|
||||
)}
|
||||
<Route path={MANAGEMENT_PATH} exact>
|
||||
<Redirect to={getEndpointListPath({ name: 'endpointList' })} />
|
||||
</Route>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue