Skip to content

Commit

Permalink
[Security Solution][Endpoint][Response Actions] Allow access to respo…
Browse files Browse the repository at this point in the history
…nder for `execute` RBAC (elastic#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
  • Loading branch information
ashokaditya authored and bmorelli25 committed Mar 10, 2023
1 parent 2c03f7a commit 9508371
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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 = [];
});
Expand Down Expand Up @@ -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) => {
Expand All @@ -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);
}
);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ({
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 9508371

Please sign in to comment.