[Security Solution][Endpoint][Response Actions] Allow access to responder for execute RBAC (#152004)

## Summary

Adds execute RBAC to the list of privileges that allow access to response action console (responder) 

### 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
This commit is contained in:
Ashokaditya 2023-03-07 17:57:24 +01:00 committed by GitHub
parent b6cff1ad72
commit 31bd2ef37a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 21 deletions

View file

@ -10,6 +10,11 @@ import type { FleetAuthz } from '@kbn/fleet-plugin/common';
import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks';
import { createLicenseServiceMock } from '../../../license/mocks';
import type { EndpointAuthzKeyList } from '../../types/authz';
import {
commandToRBACMap,
CONSOLE_RESPONSE_ACTION_COMMANDS,
type ResponseConsoleRbacControls,
} from '../response_actions/constants';
describe('Endpoint Authz service', () => {
let licenseService: ReturnType<typeof createLicenseServiceMock>;
@ -121,6 +126,16 @@ describe('Endpoint Authz service', () => {
});
describe('and endpoint rbac is enabled', () => {
const responseConsolePrivileges = CONSOLE_RESPONSE_ACTION_COMMANDS.slice().reduce<
ResponseConsoleRbacControls[]
>((acc, e) => {
const item = commandToRBACMap[e];
if (!acc.includes(item)) {
acc.push(item);
}
return acc;
}, []);
beforeEach(() => {
userRoles = [];
});
@ -182,6 +197,8 @@ describe('Endpoint Authz service', () => {
['canReadBlocklist', ['writeBlocklist', 'readBlocklist']],
['canWriteEventFilters', ['writeEventFilters']],
['canReadEventFilters', ['writeEventFilters', 'readEventFilters']],
// all dependent privileges are false and so it should be false
['canAccessResponseConsole', responseConsolePrivileges],
])('%s should be false if `packagePrivilege.%s` is `false`', (auth, privileges) => {
// read permission checks for write || read so we need to set both to false
privileges.forEach((privilege) => {
@ -190,6 +207,23 @@ describe('Endpoint Authz service', () => {
const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true);
expect(authz[auth]).toBe(false);
});
it.each(responseConsolePrivileges)(
'canAccessResponseConsole should be true if %s for CONSOLE privileges is true',
(responseConsolePrivilege) => {
// set all to false
responseConsolePrivileges.forEach((p) => {
fleetAuthz.packagePrivileges!.endpoint.actions[p].executePackageAction = false;
});
// set one of them to true
fleetAuthz.packagePrivileges!.endpoint.actions[
responseConsolePrivilege
].executePackageAction = true;
const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles, true);
expect(authz.canAccessResponseConsole).toBe(true);
}
);
});
});

View file

@ -241,7 +241,10 @@ export const calculateEndpointAuthz = (
canGetRunningProcesses: canWriteProcessOperations && isEnterpriseLicense,
canAccessResponseConsole:
isEnterpriseLicense &&
(canIsolateHost || canWriteProcessOperations || canWriteFileOperations),
(canIsolateHost ||
canWriteProcessOperations ||
canWriteFileOperations ||
canWriteExecuteOperations),
canWriteExecuteOperations: canWriteExecuteOperations && isEnterpriseLicense,
canWriteFileOperations: canWriteFileOperations && isEnterpriseLicense,
// artifacts

View file

@ -52,6 +52,26 @@ export const CONSOLE_RESPONSE_ACTION_COMMANDS = [
export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMANDS[number];
export type ResponseConsoleRbacControls =
| 'writeHostIsolation'
| 'writeProcessOperations'
| 'writeFileOperations'
| 'writeExecuteOperations';
/**
* maps the console command to the RBAC control that is required to access it via console
*/
export const commandToRBACMap: Record<ConsoleResponseActionCommands, ResponseConsoleRbacControls> =
Object.freeze({
isolate: 'writeHostIsolation',
release: 'writeHostIsolation',
'kill-process': 'writeProcessOperations',
'suspend-process': 'writeProcessOperations',
processes: 'writeProcessOperations',
'get-file': 'writeFileOperations',
execute: 'writeExecuteOperations',
});
// 4 hrs in milliseconds
// 4 * 60 * 60 * 1000
export const DEFAULT_EXECUTE_ACTION_TIMEOUT = 14400000;

View file

@ -64,14 +64,59 @@ const executeTimeoutValidator = (argData: ParsedArgData): true | string => {
}
};
const commandToCapabilitiesMap = new Map<ConsoleResponseActionCommands, EndpointCapabilities>([
['isolate', 'isolation'],
['release', 'isolation'],
['kill-process', 'kill_process'],
['suspend-process', 'suspend_process'],
['processes', 'running_processes'],
['get-file', 'get_file'],
['execute', 'execute'],
const commandToCapabilitiesPrivilegesMap = new Map<
ConsoleResponseActionCommands,
{ capability: EndpointCapabilities; privilege: (privileges: EndpointPrivileges) => boolean }
>([
[
'isolate',
{
capability: 'isolation',
privilege: (privileges: EndpointPrivileges) => privileges.canIsolateHost,
},
],
[
'release',
{
capability: 'isolation',
privilege: (privileges: EndpointPrivileges) => privileges.canUnIsolateHost,
},
],
[
'kill-process',
{
capability: 'kill_process',
privilege: (privileges: EndpointPrivileges) => privileges.canKillProcess,
},
],
[
'suspend-process',
{
capability: 'suspend_process',
privilege: (privileges: EndpointPrivileges) => privileges.canSuspendProcess,
},
],
[
'processes',
{
capability: 'running_processes',
privilege: (privileges: EndpointPrivileges) => privileges.canGetRunningProcesses,
},
],
[
'get-file',
{
capability: 'get_file',
privilege: (privileges: EndpointPrivileges) => privileges.canWriteFileOperations,
},
],
[
'execute',
{
capability: 'execute',
privilege: (privileges: EndpointPrivileges) => privileges.canWriteExecuteOperations,
},
],
]);
const getRbacControl = ({
@ -81,23 +126,14 @@ const getRbacControl = ({
commandName: ConsoleResponseActionCommands;
privileges: EndpointPrivileges;
}): boolean => {
const commandToPrivilegeMap = new Map<ConsoleResponseActionCommands, boolean>([
['isolate', privileges.canIsolateHost],
['release', privileges.canUnIsolateHost],
['kill-process', privileges.canKillProcess],
['suspend-process', privileges.canSuspendProcess],
['processes', privileges.canGetRunningProcesses],
['get-file', privileges.canWriteFileOperations],
['execute', privileges.canWriteExecuteOperations],
]);
return commandToPrivilegeMap.get(commandName as ConsoleResponseActionCommands) ?? false;
return Boolean(commandToCapabilitiesPrivilegesMap.get(commandName)?.privilege(privileges));
};
const capabilitiesAndPrivilegesValidator = (command: Command): true | string => {
const privileges = command.commandDefinition.meta.privileges;
const endpointCapabilities: EndpointCapabilities[] = command.commandDefinition.meta.capabilities;
const commandName = command.commandDefinition.name as ConsoleResponseActionCommands;
const responderCapability = commandToCapabilitiesMap.get(commandName);
const responderCapability = commandToCapabilitiesPrivilegesMap.get(commandName)?.capability;
let errorMessage = '';
if (!responderCapability) {
errorMessage = errorMessage.concat(UPGRADE_ENDPOINT_FOR_RESPONDER);
@ -155,7 +191,7 @@ export const getEndpointConsoleCommands = ({
const isExecuteEnabled = ExperimentalFeaturesService.get().responseActionExecuteEnabled;
const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => {
const responderCapability = commandToCapabilitiesMap.get(commandName);
const responderCapability = commandToCapabilitiesPrivilegesMap.get(commandName)?.capability;
if (responderCapability) {
return endpointCapabilities.includes(responderCapability);
}