mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint][Response Actions] Add license check to actions log management RBAC (#142482)
* Add license check to actions log management RBAC fixes elastic/security-team/issues/5118 refs elastic/kibana/pull/142470 * useUSerPrivileges instead review changes (@paul-tavares) * Don't register route if no access review changes (@paul-tavares) * reset mocked privilege review changes (@paul-tavares)
This commit is contained in:
parent
4753d7c170
commit
87dc1fa82f
7 changed files with 75 additions and 14 deletions
|
@ -160,7 +160,7 @@ export const calculateEndpointAuthz = (
|
|||
canWritePolicyManagement,
|
||||
canReadPolicyManagement,
|
||||
canWriteActionsLogManagement,
|
||||
canReadActionsLogManagement,
|
||||
canReadActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,
|
||||
// Response Actions
|
||||
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
|
||||
canUnIsolateHost: canIsolateHost,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.canReadActionsLogManagement;
|
||||
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,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -80,13 +80,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
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -226,7 +226,7 @@ export const links: LinkItem = {
|
|||
],
|
||||
};
|
||||
|
||||
const getFilteredLinks = (linkIds: SecurityPageName[]) => ({
|
||||
const excludeLinks = (linkIds: SecurityPageName[]) => ({
|
||||
...links,
|
||||
links: links.links?.filter((link) => !linkIds.includes(link.id)),
|
||||
});
|
||||
|
@ -249,19 +249,26 @@ export const getManagementFilteredLinks = async (
|
|||
)
|
||||
: getEndpointAuthzInitialState();
|
||||
if (!privileges.canAccessEndpointManagement) {
|
||||
return getFilteredLinks([SecurityPageName.hostIsolationExceptions]);
|
||||
return excludeLinks([
|
||||
SecurityPageName.hostIsolationExceptions,
|
||||
SecurityPageName.responseActionsHistory,
|
||||
]);
|
||||
}
|
||||
if (!privileges.canIsolateHost) {
|
||||
if (!privileges.canIsolateHost || !privileges.canReadActionsLogManagement) {
|
||||
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, canReadActionsLogManagement } =
|
||||
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}
|
||||
/>
|
||||
{canReadActionsLogManagement && (
|
||||
<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