[Security Solution][Endpoint][Response Actions] Show Actions history on Endpoint Details for platinum users (#145837)

## Summary

Fixes a bug where response actions history was not shown for platinum
users with Actions Log RBAC privileges.

### Checklist
- [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

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Ashokaditya 2022-11-21 16:33:03 +01:00 committed by GitHub
parent 80af40e300
commit 7e5c361e38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 54 additions and 19 deletions

View file

@ -140,6 +140,7 @@ describe('Endpoint Authz service', () => {
['canReadPolicyManagement', 'readPolicyManagement'],
['canWriteActionsLogManagement', 'writeActionsLogManagement'],
['canReadActionsLogManagement', 'readActionsLogManagement'],
['canAccessEndpointActionsLogManagement', 'readActionsLogManagement'],
['canIsolateHost', 'writeHostIsolation'],
['canUnIsolateHost', 'writeHostIsolation'],
['canKillProcess', 'writeProcessOperations'],
@ -166,6 +167,10 @@ describe('Endpoint Authz service', () => {
['canReadPolicyManagement', ['writePolicyManagement', 'readPolicyManagement']],
['canWriteActionsLogManagement', ['writeActionsLogManagement']],
['canReadActionsLogManagement', ['writeActionsLogManagement', 'readActionsLogManagement']],
[
'canAccessEndpointActionsLogManagement',
['writeActionsLogManagement', 'readActionsLogManagement'],
],
['canIsolateHost', ['writeHostIsolation']],
['canUnIsolateHost', ['writeHostIsolation']],
['canKillProcess', ['writeProcessOperations']],
@ -218,6 +223,7 @@ describe('Endpoint Authz service', () => {
canWriteSecuritySolution: false,
canReadSecuritySolution: false,
canAccessFleet: false,
canAccessEndpointActionsLogManagement: false,
canAccessEndpointManagement: false,
canCreateArtifactsByPolicy: false,
canDeleteHostIsolationExceptions: false,

View file

@ -63,6 +63,8 @@ export function hasKibanaPrivilege(
* @param hasHostIsolationExceptionsItems if set to `true`, then Host Isolation Exceptions related authz properties
* may be adjusted to account for a license downgrade scenario
*/
// eslint-disable-next-line complexity
export const calculateEndpointAuthz = (
licenseService: LicenseService,
fleetAuthz: FleetAuthz,
@ -223,6 +225,7 @@ export const calculateEndpointAuthz = (
canReadPolicyManagement,
canWriteActionsLogManagement,
canReadActionsLogManagement: canReadActionsLogManagement && isEnterpriseLicense,
canAccessEndpointActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense,
// Response Actions
canIsolateHost: canIsolateHost && isPlatinumPlusLicense,
canUnIsolateHost: canIsolateHost,
@ -250,6 +253,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => {
return {
...defaultEndpointPermissions(),
canAccessFleet: false,
canAccessEndpointActionsLogManagement: false,
canAccessEndpointManagement: false,
canCreateArtifactsByPolicy: false,
canWriteEndpointList: false,

View file

@ -24,6 +24,8 @@ export interface EndpointAuthz extends EndpointPermissions {
canAccessFleet: boolean;
/** If user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */
canAccessEndpointManagement: boolean;
/** If user has permissions to access Actions Log management and also has a platinum license (used for endpoint details flyout) */
canAccessEndpointActionsLogManagement: boolean;
/** if user has permissions to create Artifacts by Policy */
canCreateArtifactsByPolicy: boolean;
/** if user has write permissions to endpoint list */

View file

@ -143,6 +143,7 @@ describe('When using useEndpointPrivileges hook', () => {
getEndpointPrivilegesInitialStateMock({
canCreateArtifactsByPolicy: false,
canIsolateHost: false,
canAccessEndpointActionsLogManagement: false,
canWriteHostIsolationExceptions: false,
canReadHostIsolationExceptions: hasHIE,
canDeleteHostIsolationExceptions: hasHIE,

View file

@ -27,6 +27,7 @@ import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../common/endpoint/
import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges';
import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks';
import { waitFor } from '@testing-library/react';
import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__';
let mockUseGetEndpointActionList: {
isFetched?: boolean;
@ -138,8 +139,7 @@ jest.mock('../../hooks/response_actions/use_get_file_info', () => {
const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock;
// FLAKY https://github.com/elastic/kibana/issues/145635
describe.skip('Response actions history', () => {
describe('Response actions history', () => {
const useUserPrivilegesMock = _useUserPrivileges as jest.Mock<
ReturnType<typeof _useUserPrivileges>
>;
@ -195,6 +195,7 @@ describe.skip('Response actions history', () => {
...baseMockedActionList,
};
jest.clearAllMocks();
useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue);
});
describe('When index does not exist yet', () => {

View file

@ -43,7 +43,7 @@ export const EndpointDetails = memo(() => {
const policyInfo = useEndpointSelector(policyVersionInfo);
const hostStatus = useEndpointSelector(hostStatusInfo);
const show = useEndpointSelector(showView);
const { canReadActionsLogManagement } = useUserPrivileges().endpointPrivileges;
const { canAccessEndpointActionsLogManagement } = useUserPrivileges().endpointPrivileges;
const ContentLoadingMarkup = useMemo(
() => (
@ -82,7 +82,7 @@ export const EndpointDetails = memo(() => {
// show the response actions history tab
// only when the user has the required permission
if (canReadActionsLogManagement) {
if (canAccessEndpointActionsLogManagement) {
tabs.push({
id: EndpointDetailsTabsTypes.activityLog,
name: i18.ACTIVITY_LOG.tabTitle,
@ -97,7 +97,7 @@ export const EndpointDetails = memo(() => {
return tabs;
},
[
canReadActionsLogManagement,
canAccessEndpointActionsLogManagement,
ContentLoadingMarkup,
hostDetails,
policyInfo,
@ -142,7 +142,7 @@ export const EndpointDetails = memo(() => {
hostname={hostDetails.host.hostname}
// show overview tab if forcing response actions history
// tab via URL without permission
show={!canReadActionsLogManagement ? 'details' : show}
show={!canAccessEndpointActionsLogManagement ? 'details' : show}
tabs={getTabs(hostDetails.agent.id)}
/>
)}

View file

@ -44,7 +44,7 @@ export const useEndpointActionItems = (
canAccessResponseConsole,
canIsolateHost,
canUnIsolateHost,
canReadActionsLogManagement,
canAccessEndpointActionsLogManagement,
} = useUserPrivileges().endpointPrivileges;
return useMemo<ContextMenuItemNavByRouterProps[]>(() => {
@ -141,7 +141,7 @@ export const useEndpointActionItems = (
},
]
: []),
...(options?.isEndpointList && canReadActionsLogManagement
...(options?.isEndpointList && canAccessEndpointActionsLogManagement
? [
{
'data-test-subj': 'actionsLink',
@ -253,7 +253,7 @@ export const useEndpointActionItems = (
}, [
allCurrentUrlParams,
canAccessResponseConsole,
canReadActionsLogManagement,
canAccessEndpointActionsLogManagement,
endpointMetadata,
fleetAgentPolicies,
getAppUrl,

View file

@ -13,6 +13,7 @@ import type { AppContextTestRender } from '../../../common/mock/endpoint';
import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
import { useUserPrivileges } from '../../../common/components/user_privileges';
import { endpointPageHttpMock } from '../endpoint_hosts/mocks';
import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__';
jest.mock('../../../common/components/user_privileges');
@ -29,7 +30,7 @@ describe('when in the Administration tab', () => {
});
afterEach(() => {
useUserPrivilegesMock.mockReset();
useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue);
});
describe('when the user has no permissions', () => {
@ -96,8 +97,7 @@ describe('when in the Administration tab', () => {
});
});
// FLAKY: https://github.com/elastic/kibana/issues/145204
describe.skip('when the user has permissions', () => {
describe('when the user has permissions', () => {
it('should display the Management view if user has privileges', async () => {
useUserPrivilegesMock.mockReturnValue({
endpointPrivileges: { loading: false, canReadEndpointList: true },

View file

@ -40,7 +40,13 @@ interface CallApiRouteInterface {
authz?: Partial<EndpointAuthz>;
}
const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } });
const Enterprise = licenseMock.createLicense({
license: { type: 'enterprise', mode: 'enterprise' },
});
const Platinum = licenseMock.createLicense({
license: { type: 'platinum', mode: 'platinum' },
});
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } });
describe('Action List Route', () => {
@ -94,7 +100,7 @@ describe('Action List Route', () => {
const ctx = createRouteHandlerContext(mockScopedClient, mockSavedObjectClient);
const withLicense = license ? license : Platinum;
const withLicense = license ? license : Enterprise;
licenseEmitter.next(withLicense);
ctx.securitySolution.getEndpointAuthz.mockResolvedValue({
@ -102,7 +108,8 @@ describe('Action List Route', () => {
// mimicking the behavior of the EndpointAuthz class
// just so we can test the license check here
// since getEndpointAuthzInitialStateMock sets all keys to true
canReadActionsLogManagement: licenseService.isPlatinumPlus(),
canReadActionsLogManagement: licenseService.isEnterprise(),
canAccessEndpointActionsLogManagement: licenseService.isPlatinumPlus(),
}),
...authz,
});
@ -135,13 +142,27 @@ describe('Action List Route', () => {
expect(mockResponse.ok).toBeCalled();
});
it('does not allow user without `canReadActionsLogManagement` access for API requests', async () => {
it('allows user with `canAccessEndpointActionsLogManagement` access for API requests', async () => {
await callApiRoute(ENDPOINTS_ACTION_LIST_ROUTE, {
authz: { canReadActionsLogManagement: false },
authz: { canAccessEndpointActionsLogManagement: true },
});
expect(mockResponse.ok).toBeCalled();
});
it('does not allow user without `canReadActionsLogManagement` or `canAccessEndpointActionsLogManagement` access for API requests', async () => {
await callApiRoute(ENDPOINTS_ACTION_LIST_ROUTE, {
authz: { canReadActionsLogManagement: false, canAccessEndpointActionsLogManagement: false },
});
expect(mockResponse.forbidden).toBeCalled();
});
it('does allow user access to API requests if license is at least platinum', async () => {
await callApiRoute(ENDPOINTS_ACTION_LIST_ROUTE, {
license: Platinum,
});
expect(mockResponse.ok).toBeCalled();
});
it('does not allow user access to API requests if license is below platinum', async () => {
await callApiRoute(ENDPOINTS_ACTION_LIST_ROUTE, {
license: Gold,

View file

@ -33,7 +33,7 @@ export function registerActionListRoutes(
options: { authRequired: true, tags: ['access:securitySolution'] },
},
withEndpointAuthz(
{ all: ['canReadActionsLogManagement'] },
{ any: ['canReadActionsLogManagement', 'canAccessEndpointActionsLogManagement'] },
endpointContext.logFactory.get('endpointActionList'),
actionListHandler(endpointContext)
)

View file

@ -38,7 +38,7 @@ jest.mock('../../services');
const mockGetActionList = getActionList as jest.Mock;
const mockGetActionListByStatus = getActionListByStatus as jest.Mock;
describe(' Action List Handler', () => {
describe('Action List Handler', () => {
let endpointAppContextService: EndpointAppContextService;
let mockResponse: jest.Mocked<KibanaResponseFactory>;