mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Security Solution][Endpoint][Response Actions] execute
endpoint response action API (#149589)
## Summary Adds an API route for `execute` endpoint response action. **RBAC control**  **successful req/res**  <details> <summary>**.logs-endpoint.actions-default doc source**</summary> ```json5 { "EndpointActions": { "data": { "comment": " get list of files", "parameters": { "command": "ls -al", "timeout": 2500 }, "command": "execute" }, "action_id": "dae148b2-aaaf-4a7e-b5e3-0c530dafc974", "input_type": "endpoint", "expiration": "2023-02-10T15:54:45.768Z", "type": "INPUT_ACTION" }, "agent": { "id": [ "cef48f14-d4ae-4bd6-a281-d5aba6b9c88a" ] }, "@timestamp": "2023-01-27T15:54:45.768Z", "event": { "agent_id_status": "auth_metadata_missing", "ingested": "2023-01-27T15:54:45Z" }, "user": { "id": "elastic" } } ``` </details> <details> <summary>*.fleet-actions doc source*</summary> ```json5 { "action_id": "dae148b2-aaaf-4a7e-b5e3-0c530dafc974", "expiration": "2023-02-10T15:54:45.768Z", "type": "INPUT_ACTION", "input_type": "endpoint", "data": { "command": "execute", "comment": " get list of files", "parameters": { "command": "ls -al", "timeout": 2500 } }, "@timestamp": "2023-01-27T15:54:45.768Z", "agents": [ "cef48f14-d4ae-4bd6-a281-d5aba6b9c88a" ], "timeout": 300, "user_id": "elastic" } ``` </details> ### 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 - [ ] 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:
parent
772d564aa7
commit
06179742f7
32 changed files with 569 additions and 36 deletions
|
@ -138,4 +138,10 @@ export const ENDPOINT_PRIVILEGES: Record<string, PrivilegeMapObject> = deepFreez
|
|||
privilegeType: 'api',
|
||||
privilegeName: 'writeFileOperations',
|
||||
},
|
||||
writeExecuteOperations: {
|
||||
appId: DEFAULT_APP_CATEGORIES.security.id,
|
||||
privilegeSplit: '-',
|
||||
privilegeType: 'api',
|
||||
privilegeName: 'writeExecuteOperations',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -85,6 +85,9 @@ describe('When using calculateRouteAuthz()', () => {
|
|||
writeFileOperations: {
|
||||
executePackageAction: false,
|
||||
},
|
||||
writeExecuteOperations: {
|
||||
executePackageAction: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ export const GET_PROCESSES_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/running_procs`
|
|||
export const KILL_PROCESS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/kill_process`;
|
||||
export const SUSPEND_PROCESS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/suspend_process`;
|
||||
export const GET_FILE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/get_file`;
|
||||
export const EXECUTE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/execute`;
|
||||
|
||||
/** Endpoint Actions Routes */
|
||||
export const ENDPOINT_ACTION_LOG_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_log/{agent_id}`;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
EndpointActionListRequestSchema,
|
||||
NoParametersRequestSchema,
|
||||
KillOrSuspendProcessRequestSchema,
|
||||
ExecuteActionRequestSchema,
|
||||
} from './actions';
|
||||
|
||||
describe('actions schemas', () => {
|
||||
|
@ -235,12 +236,36 @@ describe('actions schemas', () => {
|
|||
});
|
||||
|
||||
describe('NoParametersRequestSchema', () => {
|
||||
it('should require at least 1 Endpoint ID', () => {
|
||||
it('should not accept when no endpoint_ids', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should require at least 1 endpoint id', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: [],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty endpoint id', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: [''],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept any empty endpoint_ids in the array', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: ['x', ' ', 'y'],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should accept an Endpoint ID as the only required field', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
|
@ -258,6 +283,15 @@ describe('actions schemas', () => {
|
|||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty alert IDs', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: ['ABC-XYZ-000'],
|
||||
alert_ids: [' '],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should accept alert IDs', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
|
@ -267,6 +301,15 @@ describe('actions schemas', () => {
|
|||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty case IDs', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: ['ABC-XYZ-000'],
|
||||
case_ids: [' '],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should accept case IDs', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
|
@ -278,9 +321,33 @@ describe('actions schemas', () => {
|
|||
});
|
||||
|
||||
describe('KillOrSuspendProcessRequestSchema', () => {
|
||||
it('should require at least 1 Endpoint ID', () => {
|
||||
it('should not accept when no endpoint_ids', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({});
|
||||
KillOrSuspendProcessRequestSchema.body.validate({});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty endpoint_ids array', () => {
|
||||
expect(() => {
|
||||
KillOrSuspendProcessRequestSchema.body.validate({
|
||||
endpoint_ids: [],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty string as endpoint id', () => {
|
||||
expect(() => {
|
||||
KillOrSuspendProcessRequestSchema.body.validate({
|
||||
endpoint_ids: [' '],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept any empty string in endpoint_ids array', () => {
|
||||
expect(() => {
|
||||
KillOrSuspendProcessRequestSchema.body.validate({
|
||||
endpoint_ids: ['x', ' ', 'y'],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
|
@ -340,4 +407,106 @@ describe('actions schemas', () => {
|
|||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExecuteActionRequestSchema', () => {
|
||||
it('should not accept when no endpoint_ids', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty endpoint_ids array', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: [],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept empty string as endpoint id', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: [' '],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept any empty string in endpoint_ids array', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: ['x', ' ', 'y'],
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should not accept an empty command with a valid endpoint_id', () => {
|
||||
expect(() => {
|
||||
NoParametersRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: ' ',
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should accept at least 1 valid endpoint id and a command', () => {
|
||||
expect(() => {
|
||||
ExecuteActionRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: 'ls -al',
|
||||
},
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should accept at least one endpoint_id and a command parameter', () => {
|
||||
expect(() => {
|
||||
ExecuteActionRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: 'ls -al',
|
||||
},
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not accept optional invalid timeout with at least one endpoint_id and a command parameter', () => {
|
||||
expect(() => {
|
||||
ExecuteActionRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: 'ls -al',
|
||||
timeout: '',
|
||||
},
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should also accept a valid timeout with at least one endpoint_id and a command parameter', () => {
|
||||
expect(() => {
|
||||
ExecuteActionRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: 'ls -al',
|
||||
timeout: 1000,
|
||||
},
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should also accept an optional comment', () => {
|
||||
expect(() => {
|
||||
ExecuteActionRequestSchema.body.validate({
|
||||
endpoint_ids: ['endpoint_id'],
|
||||
parameters: {
|
||||
command: 'ls -al',
|
||||
timeout: 1000,
|
||||
},
|
||||
comment: 'a user comment',
|
||||
});
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,11 +15,36 @@ import {
|
|||
|
||||
const BaseActionRequestSchema = {
|
||||
/** A list of endpoint IDs whose hosts will be isolated (Fleet Agent IDs will be retrieved for these) */
|
||||
endpoint_ids: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
endpoint_ids: schema.arrayOf(schema.string({ minLength: 1 }), {
|
||||
minSize: 1,
|
||||
validate: (endpointIds) => {
|
||||
if (endpointIds.map((v) => v.trim()).some((v) => !v.length)) {
|
||||
return 'endpoint_ids cannot contain empty strings';
|
||||
}
|
||||
},
|
||||
}),
|
||||
/** If defined, any case associated with the given IDs will be updated */
|
||||
alert_ids: schema.maybe(schema.arrayOf(schema.string())),
|
||||
alert_ids: schema.maybe(
|
||||
schema.arrayOf(schema.string({ minLength: 1 }), {
|
||||
minSize: 1,
|
||||
validate: (alertIds) => {
|
||||
if (alertIds.map((v) => v.trim()).some((v) => !v.length)) {
|
||||
return 'alert_ids cannot contain empty strings';
|
||||
}
|
||||
},
|
||||
})
|
||||
),
|
||||
/** Case IDs to be updated */
|
||||
case_ids: schema.maybe(schema.arrayOf(schema.string())),
|
||||
case_ids: schema.maybe(
|
||||
schema.arrayOf(schema.string({ minLength: 1 }), {
|
||||
minSize: 1,
|
||||
validate: (caseIds) => {
|
||||
if (caseIds.map((v) => v.trim()).some((v) => !v.length)) {
|
||||
return 'case_ids cannot contain empty strings';
|
||||
}
|
||||
},
|
||||
})
|
||||
),
|
||||
comment: schema.maybe(schema.string()),
|
||||
parameters: schema.maybe(schema.object({})),
|
||||
};
|
||||
|
@ -149,3 +174,20 @@ export const EndpointActionFileInfoSchema = {
|
|||
};
|
||||
|
||||
export type EndpointActionFileInfoParams = TypeOf<typeof EndpointActionFileInfoSchema.params>;
|
||||
|
||||
export const ExecuteActionRequestSchema = {
|
||||
body: schema.object({
|
||||
...BaseActionRequestSchema,
|
||||
parameters: schema.object({
|
||||
command: schema.string({
|
||||
minLength: 1,
|
||||
validate: (value) => {
|
||||
if (!value.trim().length) {
|
||||
return 'command cannot be an empty string';
|
||||
}
|
||||
},
|
||||
}),
|
||||
timeout: schema.maybe(schema.number({ min: 1 })),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -138,6 +138,7 @@ describe('Endpoint Authz service', () => {
|
|||
['canKillProcess', 'writeProcessOperations'],
|
||||
['canSuspendProcess', 'writeProcessOperations'],
|
||||
['canGetRunningProcesses', 'writeProcessOperations'],
|
||||
['canWriteExecuteOperations', 'writeExecuteOperations'],
|
||||
['canWriteFileOperations', 'writeFileOperations'],
|
||||
['canWriteTrustedApplications', 'writeTrustedApplications'],
|
||||
['canReadTrustedApplications', 'readTrustedApplications'],
|
||||
|
@ -168,6 +169,7 @@ describe('Endpoint Authz service', () => {
|
|||
['canKillProcess', ['writeProcessOperations']],
|
||||
['canSuspendProcess', ['writeProcessOperations']],
|
||||
['canGetRunningProcesses', ['writeProcessOperations']],
|
||||
['canWriteExecuteOperations', ['writeExecuteOperations']],
|
||||
['canWriteFileOperations', ['writeFileOperations']],
|
||||
['canWriteTrustedApplications', ['writeTrustedApplications']],
|
||||
['canReadTrustedApplications', ['writeTrustedApplications', 'readTrustedApplications']],
|
||||
|
@ -213,6 +215,7 @@ describe('Endpoint Authz service', () => {
|
|||
canSuspendProcess: false,
|
||||
canGetRunningProcesses: false,
|
||||
canAccessResponseConsole: false,
|
||||
canWriteExecuteOperations: false,
|
||||
canWriteFileOperations: false,
|
||||
canWriteTrustedApplications: false,
|
||||
canReadTrustedApplications: false,
|
||||
|
|
|
@ -213,6 +213,13 @@ export const calculateEndpointAuthz = (
|
|||
'writeFileOperations'
|
||||
);
|
||||
|
||||
const canWriteExecuteOperations = hasKibanaPrivilege(
|
||||
fleetAuthz,
|
||||
isEndpointRbacEnabled,
|
||||
hasEndpointManagementAccess,
|
||||
'writeExecuteOperations'
|
||||
);
|
||||
|
||||
return {
|
||||
canWriteSecuritySolution,
|
||||
canReadSecuritySolution,
|
||||
|
@ -235,6 +242,7 @@ export const calculateEndpointAuthz = (
|
|||
canAccessResponseConsole:
|
||||
isEnterpriseLicense &&
|
||||
(canIsolateHost || canWriteProcessOperations || canWriteFileOperations),
|
||||
canWriteExecuteOperations: canWriteExecuteOperations && isEnterpriseLicense,
|
||||
canWriteFileOperations: canWriteFileOperations && isEnterpriseLicense,
|
||||
// artifacts
|
||||
canWriteTrustedApplications,
|
||||
|
@ -270,6 +278,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => {
|
|||
canGetRunningProcesses: false,
|
||||
canAccessResponseConsole: false,
|
||||
canWriteFileOperations: false,
|
||||
canWriteExecuteOperations: false,
|
||||
canWriteTrustedApplications: false,
|
||||
canReadTrustedApplications: false,
|
||||
canWriteHostIsolationExceptions: false,
|
||||
|
|
|
@ -17,6 +17,7 @@ export const RESPONSE_ACTION_API_COMMANDS_NAMES = [
|
|||
'suspend-process',
|
||||
'running-processes',
|
||||
'get-file',
|
||||
'execute',
|
||||
] as const;
|
||||
|
||||
export type ResponseActionsApiCommandNames = typeof RESPONSE_ACTION_API_COMMANDS_NAMES[number];
|
||||
|
@ -30,6 +31,7 @@ export const ENDPOINT_CAPABILITIES = [
|
|||
'suspend_process',
|
||||
'running_processes',
|
||||
'get_file',
|
||||
'execute',
|
||||
] as const;
|
||||
|
||||
export type EndpointCapabilities = typeof ENDPOINT_CAPABILITIES[number];
|
||||
|
@ -45,6 +47,7 @@ export const CONSOLE_RESPONSE_ACTION_COMMANDS = [
|
|||
'suspend-process',
|
||||
'processes',
|
||||
'get-file',
|
||||
'execute',
|
||||
] as const;
|
||||
|
||||
export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMANDS[number];
|
||||
|
|
|
@ -149,9 +149,15 @@ export interface ResponseActionGetFileParameters {
|
|||
path: string;
|
||||
}
|
||||
|
||||
export interface ResponseActionsExecuteParameters {
|
||||
command: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export type EndpointActionDataParameterTypes =
|
||||
| undefined
|
||||
| ResponseActionParametersWithPidOrEntityId
|
||||
| ResponseActionsExecuteParameters
|
||||
| ResponseActionGetFileParameters;
|
||||
|
||||
export interface EndpointActionData<
|
||||
|
|
|
@ -46,6 +46,8 @@ export interface EndpointAuthz {
|
|||
canGetRunningProcesses: boolean;
|
||||
/** If user has permissions to use the Response Actions Console */
|
||||
canAccessResponseConsole: boolean;
|
||||
/** If user has write permissions to use execute action */
|
||||
canWriteExecuteOperations: boolean;
|
||||
/** If user has write permissions to use file operations */
|
||||
canWriteFileOperations: boolean;
|
||||
/** if user has write permissions for trusted applications */
|
||||
|
|
|
@ -81,6 +81,11 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
*/
|
||||
responseActionGetFileEnabled: true,
|
||||
|
||||
/**
|
||||
* Enables the `execute` endpoint response action
|
||||
*/
|
||||
responseActionExecuteEnabled: false,
|
||||
|
||||
/**
|
||||
* Enables top charts on Alerts Page
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,7 @@ const ExperimentalFeaturesServiceMock = {
|
|||
get: jest.fn(() => {
|
||||
const ff: ExperimentalFeatures = {
|
||||
...allowedExperimentalValues,
|
||||
responseActionExecuteEnabled: true,
|
||||
};
|
||||
|
||||
return ff;
|
||||
|
|
|
@ -379,7 +379,7 @@ describe('Response actions history page', () => {
|
|||
});
|
||||
|
||||
expect(history.location.search).toEqual(
|
||||
'?commands=isolate%2Crelease%2Ckill-process%2Csuspend-process%2Cprocesses%2Cget-file'
|
||||
'?commands=isolate%2Crelease%2Ckill-process%2Csuspend-process%2Cprocesses%2Cget-file%2Cexecute'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ export const getWithResponseActionsRole: () => Omit<Role, 'name'> = () => {
|
|||
...noResponseActionsRole.kibana[0].feature,
|
||||
siem: [
|
||||
...noResponseActionsRole.kibana[0].feature.siem,
|
||||
'file_operations_all',
|
||||
'execute_operations_all',
|
||||
'host_isolation_all',
|
||||
'process_operations_all',
|
||||
'file_operations_all',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -14,6 +14,8 @@ export const createMockConfig = (): ConfigType => {
|
|||
const enableExperimental: Array<keyof ExperimentalFeatures> = [
|
||||
// Remove property below once `get-file` FF is enabled or removed
|
||||
'responseActionGetFileEnabled',
|
||||
// remove property below once `execute` FF is enabled or removed
|
||||
'responseActionExecuteEnabled',
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
ISOLATE_HOST_ROUTE,
|
||||
UNISOLATE_HOST_ROUTE,
|
||||
GET_FILE_ROUTE,
|
||||
EXECUTE_ROUTE,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import type {
|
||||
ActionDetails,
|
||||
|
@ -49,6 +50,7 @@ import type {
|
|||
HostMetadata,
|
||||
LogsEndpointAction,
|
||||
ResponseActionRequestBody,
|
||||
ResponseActionsExecuteParameters,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
|
||||
import type { EndpointAuthz } from '../../../../common/endpoint/types/authz';
|
||||
|
@ -564,6 +566,65 @@ describe('Response actions', () => {
|
|||
expect(responseBody.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles execute', async () => {
|
||||
const ctx = await callRoute(
|
||||
EXECUTE_ROUTE,
|
||||
{
|
||||
body: { endpoint_ids: ['XYZ'], parameters: { command: 'ls -al', timeout: 1000 } },
|
||||
},
|
||||
{ endpointDsExists: true }
|
||||
);
|
||||
const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index;
|
||||
const actionDocs: [
|
||||
{ index: string; body?: LogsEndpointAction },
|
||||
{ index: string; body?: EndpointAction }
|
||||
] = [
|
||||
indexDoc.mock.calls[0][0] as estypes.IndexRequest<LogsEndpointAction>,
|
||||
indexDoc.mock.calls[1][0] as estypes.IndexRequest<EndpointAction>,
|
||||
];
|
||||
|
||||
expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX);
|
||||
expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX);
|
||||
expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('execute');
|
||||
const parameters = actionDocs[1].body!.data.parameters as ResponseActionsExecuteParameters;
|
||||
expect(parameters.command).toEqual('ls -al');
|
||||
expect(parameters.timeout).toEqual(1000);
|
||||
expect(actionDocs[1].body!.data.command).toEqual('execute');
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse;
|
||||
expect(responseBody.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles execute without optional `timeout`', async () => {
|
||||
const ctx = await callRoute(
|
||||
EXECUTE_ROUTE,
|
||||
{
|
||||
body: { endpoint_ids: ['XYZ'], parameters: { command: 'ls -al' } },
|
||||
},
|
||||
{ endpointDsExists: true }
|
||||
);
|
||||
const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index;
|
||||
const actionDocs: [
|
||||
{ index: string; body?: LogsEndpointAction },
|
||||
{ index: string; body?: EndpointAction }
|
||||
] = [
|
||||
indexDoc.mock.calls[0][0] as estypes.IndexRequest<LogsEndpointAction>,
|
||||
indexDoc.mock.calls[1][0] as estypes.IndexRequest<EndpointAction>,
|
||||
];
|
||||
|
||||
expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX);
|
||||
expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX);
|
||||
expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('execute');
|
||||
const parameters = actionDocs[1].body!.data.parameters as ResponseActionsExecuteParameters;
|
||||
expect(parameters.command).toEqual('ls -al');
|
||||
expect(actionDocs[1].body!.data.command).toEqual('execute');
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse;
|
||||
expect(responseBody.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
const ErrMessage = 'Uh oh!';
|
||||
await callRoute(
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
NoParametersRequestSchema,
|
||||
KillOrSuspendProcessRequestSchema,
|
||||
EndpointActionGetFileSchema,
|
||||
ExecuteActionRequestSchema,
|
||||
} from '../../../../common/endpoint/schema/actions';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import {
|
||||
|
@ -34,6 +35,7 @@ import {
|
|||
UNISOLATE_HOST_ROUTE,
|
||||
ENDPOINT_ACTIONS_INDEX,
|
||||
GET_FILE_ROUTE,
|
||||
EXECUTE_ROUTE,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import type {
|
||||
EndpointAction,
|
||||
|
@ -43,6 +45,7 @@ import type {
|
|||
LogsEndpointAction,
|
||||
LogsEndpointActionResponse,
|
||||
ResponseActionParametersWithPidOrEntityId,
|
||||
ResponseActionsExecuteParameters,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type {
|
||||
|
@ -175,6 +178,22 @@ export function registerResponseActionRoutes(
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
// `execute` currently behind FF (planned for 8.8)
|
||||
if (endpointContext.experimentalFeatures.responseActionExecuteEnabled) {
|
||||
router.post(
|
||||
{
|
||||
path: EXECUTE_ROUTE,
|
||||
validate: ExecuteActionRequestSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canWriteExecuteOperations'] },
|
||||
logger,
|
||||
responseActionRequestHandler<ResponseActionsExecuteParameters>(endpointContext, 'execute')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const commandToFeatureKeyMap = new Map<ResponseActionsApiCommandNames, FeatureKeys>([
|
||||
|
@ -184,6 +203,7 @@ const commandToFeatureKeyMap = new Map<ResponseActionsApiCommandNames, FeatureKe
|
|||
['suspend-process', 'SUSPEND_PROCESS'],
|
||||
['running-processes', 'RUNNING_PROCESSES'],
|
||||
['get-file', 'GET_FILE'],
|
||||
['execute', 'EXECUTE'],
|
||||
]);
|
||||
|
||||
const returnActionIdCommands: ResponseActionsApiCommandNames[] = ['isolate', 'unisolate'];
|
||||
|
|
|
@ -68,7 +68,7 @@ describe('When using `getActionDetailsById()', () => {
|
|||
).resolves.toEqual({
|
||||
agents: ['agent-a'],
|
||||
hosts: { 'agent-a': { name: 'Host-agent-a' } },
|
||||
command: 'unisolate',
|
||||
command: 'kill-process',
|
||||
completedAt: '2022-04-30T16:08:47.449Z',
|
||||
wasSuccessful: true,
|
||||
errors: undefined,
|
||||
|
|
|
@ -91,7 +91,7 @@ describe('When using `getActionList()', () => {
|
|||
{
|
||||
agents: ['agent-a'],
|
||||
hosts: { 'agent-a': { name: 'Host-agent-a' } },
|
||||
command: 'unisolate',
|
||||
command: 'kill-process',
|
||||
completedAt: '2022-04-30T16:08:47.449Z',
|
||||
wasSuccessful: true,
|
||||
errors: undefined,
|
||||
|
@ -171,7 +171,7 @@ describe('When using `getActionList()', () => {
|
|||
'agent-b': { name: 'Host-agent-b' },
|
||||
'agent-x': { name: '' },
|
||||
},
|
||||
command: 'unisolate',
|
||||
command: 'kill-process',
|
||||
completedAt: undefined,
|
||||
wasSuccessful: false,
|
||||
errors: undefined,
|
||||
|
|
|
@ -72,7 +72,7 @@ describe('When using Actions service utilities', () => {
|
|||
)
|
||||
).toEqual({
|
||||
agents: ['6e6796b0-af39-4f12-b025-fcb06db499e5'],
|
||||
command: 'unisolate',
|
||||
command: 'kill-process',
|
||||
comment: expect.any(String),
|
||||
createdAt: '2022-04-27T16:08:47.449Z',
|
||||
createdBy: 'elastic',
|
||||
|
@ -92,7 +92,7 @@ describe('When using Actions service utilities', () => {
|
|||
)
|
||||
).toEqual({
|
||||
agents: ['90d62689-f72d-4a05-b5e3-500cad0dc366'],
|
||||
command: 'unisolate',
|
||||
command: 'kill-process',
|
||||
comment: expect.any(String),
|
||||
createdAt: '2022-04-27T16:08:47.449Z',
|
||||
createdBy: 'Shanel',
|
||||
|
@ -441,24 +441,7 @@ describe('When using Actions service utilities', () => {
|
|||
completedAt: COMPLETED_AT,
|
||||
wasSuccessful: true,
|
||||
errors: undefined,
|
||||
outputs: {
|
||||
'456': {
|
||||
content: {
|
||||
code: 'ra_get-file_success_done',
|
||||
contents: [
|
||||
{
|
||||
file_name: 'bad_file.txt',
|
||||
path: '/some/path/bad_file.txt',
|
||||
sha256: '9558c5cb39622e9b3653203e772b129d6c634e7dbd7af1b244352fc1d704601f',
|
||||
size: 1234,
|
||||
type: 'file',
|
||||
},
|
||||
],
|
||||
zip_size: 123,
|
||||
},
|
||||
type: 'json',
|
||||
},
|
||||
},
|
||||
outputs: {},
|
||||
agentState: {
|
||||
'123': {
|
||||
completedAt: '2022-01-05T19:27:23.816Z',
|
||||
|
|
|
@ -22,6 +22,7 @@ const FEATURES = {
|
|||
SUSPEND_PROCESS: 'Suspend process',
|
||||
RUNNING_PROCESSES: 'Get running processes',
|
||||
GET_FILE: 'Get file',
|
||||
EXECUTE: 'Execute command',
|
||||
ALERTS_BY_PROCESS_ANCESTRY: 'Get related alerts by process ancestry',
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -215,7 +215,7 @@ const responseActionSubFeatures: SubFeatureConfig[] = [
|
|||
defaultMessage: 'All Spaces is required for File Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate('xpack.securitySolution.featureRegistr.subFeatures.fileOperations', {
|
||||
name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.fileOperations', {
|
||||
defaultMessage: 'File Operations',
|
||||
}),
|
||||
privilegeGroups: [
|
||||
|
@ -237,6 +237,43 @@ const responseActionSubFeatures: SubFeatureConfig[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Execute Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.executeOperations', {
|
||||
defaultMessage: 'Execute Operations',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description',
|
||||
{
|
||||
// TODO: Update this description before 8.8 FF
|
||||
defaultMessage: 'Perform script execution on the endpoint.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
api: [`${APP_ID}-writeExecuteOperations`],
|
||||
id: 'execute_operations_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeExecuteOperations'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const subFeatures: SubFeatureConfig[] = [
|
||||
|
@ -531,6 +568,13 @@ function getSubFeatures(experimentalFeatures: ConfigType['experimentalFeatures']
|
|||
});
|
||||
}
|
||||
|
||||
// behind FF (planned for 8.8)
|
||||
if (!experimentalFeatures.responseActionExecuteEnabled) {
|
||||
filteredSubFeatures = filteredSubFeatures.filter((subFeat) => {
|
||||
return subFeat.name !== 'Execute operations';
|
||||
});
|
||||
}
|
||||
|
||||
return filteredSubFeatures;
|
||||
}
|
||||
|
||||
|
|
|
@ -29729,7 +29729,6 @@
|
|||
"xpack.securitySolution.expandedValue.links.viewUserSummary": "Afficher le résumé de l'utilisateur",
|
||||
"xpack.securitySolution.expandedValue.showTopN.showTopValues": "Afficher les valeurs les plus élevées",
|
||||
"xpack.securitySolution.featureCatalogueDescription": "Prévenez, collectez, détectez et traitez les menaces pour une protection unifiée dans toute votre infrastructure.",
|
||||
"xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "Opérations de fichier",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "Supprimer les cas et les commentaires",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureName": "Supprimer",
|
||||
"xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "Cas",
|
||||
|
|
|
@ -29698,7 +29698,6 @@
|
|||
"xpack.securitySolution.expandedValue.links.viewUserSummary": "ユーザー概要を表示",
|
||||
"xpack.securitySolution.expandedValue.showTopN.showTopValues": "上位の値を表示",
|
||||
"xpack.securitySolution.featureCatalogueDescription": "インフラストラクチャ全体の統合保護のため、脅威を防止、収集、検出し、それに対応します。",
|
||||
"xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "ファイル操作",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "ケースとコメントを削除",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureName": "削除",
|
||||
"xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "ケース",
|
||||
|
|
|
@ -29732,7 +29732,6 @@
|
|||
"xpack.securitySolution.expandedValue.links.viewUserSummary": "查看用户摘要",
|
||||
"xpack.securitySolution.expandedValue.showTopN.showTopValues": "显示排名最前值",
|
||||
"xpack.securitySolution.featureCatalogueDescription": "预防、收集、检测和响应威胁,以对整个基础架构提供统一的保护。",
|
||||
"xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "文件操作",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "删除案例和注释",
|
||||
"xpack.securitySolution.featureRegistry.deleteSubFeatureName": "删除",
|
||||
"xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "案例",
|
||||
|
|
|
@ -51,6 +51,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'host_isolation_all',
|
||||
'process_operations_all',
|
||||
'file_operations_all',
|
||||
'execute_operations_all',
|
||||
],
|
||||
uptime: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'],
|
||||
|
|
|
@ -121,6 +121,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'trusted_applications_all',
|
||||
'trusted_applications_read',
|
||||
'file_operations_all',
|
||||
'execute_operations_all',
|
||||
],
|
||||
uptime: ['all', 'read', 'minimal_all', 'minimal_read'],
|
||||
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'],
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
BASE_POLICY_RESPONSE_ROUTE,
|
||||
ENDPOINTS_ACTION_LIST_ROUTE,
|
||||
GET_PROCESSES_ROUTE,
|
||||
GET_FILE_ROUTE,
|
||||
HOST_METADATA_GET_ROUTE,
|
||||
HOST_METADATA_LIST_ROUTE,
|
||||
ISOLATE_HOST_ROUTE_V2,
|
||||
|
@ -20,6 +21,7 @@ import {
|
|||
METADATA_TRANSFORMS_STATUS_ROUTE,
|
||||
SUSPEND_PROCESS_ROUTE,
|
||||
UNISOLATE_HOST_ROUTE_V2,
|
||||
EXECUTE_ROUTE,
|
||||
} from '@kbn/security-solution-plugin/common/endpoint/constants';
|
||||
import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
@ -113,6 +115,22 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
];
|
||||
|
||||
const canWriteFileOperationsApiList: ApiCallsInterface[] = [
|
||||
{
|
||||
method: 'post',
|
||||
path: GET_FILE_ROUTE,
|
||||
body: { endpoint_ids: ['one'], parameters: { path: '/opt/file/doc.txt' } },
|
||||
},
|
||||
];
|
||||
|
||||
const canWriteExecuteOperationsApiList: ApiCallsInterface[] = [
|
||||
{
|
||||
method: 'post',
|
||||
path: EXECUTE_ROUTE,
|
||||
body: { endpoint_ids: ['one'], parameters: { command: 'ls -la' } },
|
||||
},
|
||||
];
|
||||
|
||||
const superuserApiList: ApiCallsInterface[] = [
|
||||
{
|
||||
method: 'get',
|
||||
|
@ -251,6 +269,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
...canReadActionsLogManagementApiList,
|
||||
...canIsolateHostApiList,
|
||||
...canWriteProcessOperationsApiList,
|
||||
...canWriteExecuteOperationsApiList,
|
||||
...canWriteFileOperationsApiList,
|
||||
...superuserApiList,
|
||||
]) {
|
||||
it(`should return 200 when [${apiListItem.method.toUpperCase()} ${
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { wrapErrorAndRejectPromise } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/utils';
|
||||
import expect from '@kbn/expect';
|
||||
import { EXECUTE_ROUTE } from '@kbn/security-solution-plugin/common/endpoint/constants';
|
||||
import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { ROLE } from '../../services/roles_users';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const endpointTestResources = getService('endpointTestResources');
|
||||
|
||||
describe('Endpoint `execute` response action', () => {
|
||||
let indexedData: IndexedHostsAndAlertsResponse;
|
||||
let agentId = '';
|
||||
|
||||
before(async () => {
|
||||
indexedData = await endpointTestResources.loadEndpointData();
|
||||
agentId = indexedData.hosts[0].agent.id;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
endpointTestResources.unloadEndpointData(indexedData);
|
||||
});
|
||||
|
||||
it('should not allow `execute` action without required privilege', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.t1_analyst, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } })
|
||||
.expect(403, {
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Endpoint authorization failure',
|
||||
})
|
||||
.catch(wrapErrorAndRejectPromise);
|
||||
});
|
||||
|
||||
it('should error on invalid endpoint id', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [' '], parameters: { command: 'ls -la' } })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '[request body.endpoint_ids]: endpoint_ids cannot contain empty strings',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on missing endpoint id', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ parameters: { command: 'ls -la' } })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'[request body.endpoint_ids]: expected value of type [array] but got [undefined]',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on invalid `command` parameter', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId], parameters: { command: ' ' } })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '[request body.parameters.command]: command cannot be an empty string',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on missing `command` parameter', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId] })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'[request body.parameters.command]: expected value of type [string] but got [undefined]',
|
||||
});
|
||||
});
|
||||
|
||||
it('should error on invalid `timeout` parameter', async () => {
|
||||
await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 'too' } })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'[request body.parameters.timeout]: expected value of type [number] but got [string]',
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed with valid endpoint id and command', async () => {
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } })
|
||||
.expect(200);
|
||||
|
||||
expect(data.agents[0]).to.eql(agentId);
|
||||
expect(data.command).to.eql('execute');
|
||||
expect(data.parameters.command).to.eql('ls -la');
|
||||
});
|
||||
|
||||
it('should succeed with valid endpoint id, command and an optional timeout', async () => {
|
||||
const {
|
||||
body: { data },
|
||||
} = await supertestWithoutAuth
|
||||
.post(EXECUTE_ROUTE)
|
||||
.auth(ROLE.response_actions_role, 'changeme')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 2000 } })
|
||||
.expect(200);
|
||||
|
||||
expect(data.agents[0]).to.eql(agentId);
|
||||
expect(data.command).to.eql('execute');
|
||||
expect(data.parameters.command).to.eql('ls -la');
|
||||
expect(data.parameters.timeout).to.eql(2000);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -47,6 +47,7 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider
|
|||
loadTestFile(require.resolve('./policy'));
|
||||
loadTestFile(require.resolve('./package'));
|
||||
loadTestFile(require.resolve('./endpoint_authz'));
|
||||
loadTestFile(require.resolve('./endpoint_response_actions/execute'));
|
||||
loadTestFile(require.resolve('./file_upload_index'));
|
||||
loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps'));
|
||||
loadTestFile(require.resolve('./endpoint_artifacts/event_filters'));
|
||||
|
|
|
@ -32,7 +32,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
// this will be removed in 8.7 when the file upload feature is released
|
||||
`--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`,
|
||||
// this will be removed in 8.7 when the artifacts RBAC is released
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify(['endpointRbacEnabled'])}`,
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||
'endpointRbacEnabled',
|
||||
'responseActionGetFileEnabled',
|
||||
'responseActionExecuteEnabled',
|
||||
])}`,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import { getSocManager } from '@kbn/security-solution-plugin/scripts/endpoint/co
|
|||
import { getPlatformEngineer } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/platform_engineer';
|
||||
import { getEndpointOperationsAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_operations_analyst';
|
||||
import { getEndpointSecurityPolicyManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_security_policy_manager';
|
||||
import { getWithResponseActionsRole } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/with_response_actions_role';
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
|
@ -28,6 +29,7 @@ export enum ROLE {
|
|||
platform_engineer = 'platformEngineer',
|
||||
endpoint_operations_analyst = 'endpointOperationsAnalyst',
|
||||
endpoint_security_policy_manager = 'endpointSecurityPolicyManager',
|
||||
response_actions_role = 'executeResponseActions',
|
||||
}
|
||||
|
||||
const rolesMapping: { [id: string]: Omit<Role, 'name'> } = {
|
||||
|
@ -39,6 +41,7 @@ const rolesMapping: { [id: string]: Omit<Role, 'name'> } = {
|
|||
platformEngineer: getPlatformEngineer(),
|
||||
endpointOperationsAnalyst: getEndpointOperationsAnalyst(),
|
||||
endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(),
|
||||
executeResponseActions: getWithResponseActionsRole(),
|
||||
};
|
||||
|
||||
export function RolesUsersProvider({ getService }: FtrProviderContext) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue