[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:
Ashokaditya 2022-10-04 13:57:00 +02:00 committed by GitHub
parent 4753d7c170
commit 87dc1fa82f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 14 deletions

View file

@ -160,7 +160,7 @@ export const calculateEndpointAuthz = (
canWritePolicyManagement,
canReadPolicyManagement,
canWriteActionsLogManagement,
canReadActionsLogManagement,
canReadActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,
// Response Actions
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
canUnIsolateHost: canIsolateHost,

View file

@ -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,

View file

@ -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', () => {

View file

@ -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,
]
);

View file

@ -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
),
});
});
});

View file

@ -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;

View file

@ -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>