diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts index 61216afeea1ee..928327fc0e28d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/is_response_action_supported.ts @@ -110,7 +110,7 @@ const RESPONSE_ACTIONS_SUPPORT_MAP: SupportMap = { }, manual: { endpoint: true, - sentinel_one: false, + sentinel_one: true, crowdstrike: false, }, }, diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 01da8a39ee723..99c0cee932a11 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -9,10 +9,10 @@ import type { TypeOf } from '@kbn/config-schema'; import type { EcsError } from '@elastic/ecs'; import type { BaseFileMetadata, FileCompression, FileJSON } from '@kbn/files-plugin/common'; import type { - ResponseActionBodySchema, - UploadActionApiRequestBody, KillProcessRouteRequestSchema, + ResponseActionBodySchema, SuspendProcessRouteRequestSchema, + UploadActionApiRequestBody, } from '../../api/endpoint'; import type { ActionStatusRequestSchema } from '../../api/endpoint/actions/action_status_route'; import type { NoParametersRequestSchema } from '../../api/endpoint/actions/common/base'; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts b/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts index a15557617d9f0..3b0703f4e0c7b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts @@ -116,6 +116,14 @@ export interface SentinelOneGetFileResponseMeta { filename: string; } +export interface SentinelOneProcessesRequestMeta extends SentinelOneGetFileRequestMeta { + /** + * The Parent Task Is that is executing the kill process action in SentinelOne. + * Used to check on the status of that action + */ + parentTaskId: string; +} + export interface SentinelOneKillProcessRequestMeta extends SentinelOneIsolationRequestMeta { /** * The Parent Task Is that is executing the kill process action in SentinelOne. diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 8f6147102c441..47228fdc2924b 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -84,6 +84,9 @@ export const allowedExperimentalValues = Object.freeze({ /** Enables the `kill-process` response action for SentinelOne */ responseActionsSentinelOneKillProcessEnabled: false, + /** Enable the `processes` response actions for SentinelOne */ + responseActionsSentinelOneProcessesEnabled: false, + /** * Enables the ability to send Response actions to Crowdstrike and persist the results * in ES. diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx index 81d408e229bbd..387aed4af9060 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_processes_action.tsx @@ -45,17 +45,19 @@ const StyledEuiBasicTable = styled(EuiBasicTable)` export const GetProcessesActionResult = memo( ({ command, setStore, store, status, setStatus, ResultComponent }) => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { endpointId, agentType } = command.commandDefinition?.meta ?? {}; + const comment = command.args.args?.comment?.[0]; const actionCreator = useSendGetEndpointProcessesRequest(); const actionRequestBody = useMemo(() => { return endpointId ? { endpoint_ids: [endpointId], - comment: command.args.args?.comment?.[0], + comment, + agent_type: agentType, } : undefined; - }, [endpointId, command.args.args?.comment]); + }, [endpointId, comment, agentType]); const { result, actionDetails: completedActionDetails } = useConsoleActionSubmitter< ProcessesRequestBody, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx index 08c9ffd673f5d..423d0833e6078 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx @@ -14,49 +14,59 @@ import { import React from 'react'; import { getEndpointConsoleCommands } from '../../lib/console_commands_definition'; import { responseActionsHttpMocks } from '../../../../mocks/response_actions_http_mocks'; -import { enterConsoleCommand } from '../../../console/mocks'; +import { enterConsoleCommand, getConsoleSelectorsAndActionMock } from '../../../console/mocks'; import { waitFor } from '@testing-library/react'; import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/service/authz'; -import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { + EndpointCapabilities, + ResponseActionAgentType, +} from '../../../../../../common/endpoint/service/response_actions/constants'; import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants'; import { UPGRADE_AGENT_FOR_RESPONDER } from '../../../../../common/translations'; - -jest.mock('../../../../../common/experimental_features_service'); +import type { CommandDefinition } from '../../../console'; describe('When using processes action from response actions console', () => { - let render: ( - capabilities?: EndpointCapabilities[] - ) => Promise>; + let mockedContext: AppContextTestRender; + let render: () => Promise>; let renderResult: ReturnType; let apiMocks: ReturnType; let consoleManagerMockAccess: ReturnType< typeof getConsoleManagerMockRenderResultQueriesAndActions >; + let consoleSelectors: ReturnType; + let consoleCommands: CommandDefinition[]; + + const setConsoleCommands = ( + capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES], + agentType: ResponseActionAgentType = 'endpoint' + ): void => { + consoleCommands = getEndpointConsoleCommands({ + agentType, + endpointAgentId: 'a.b.c', + endpointCapabilities: capabilities, + endpointPrivileges: { + ...getEndpointAuthzInitialState(), + loading: false, + canKillProcess: true, + canSuspendProcess: true, + canGetRunningProcesses: true, + }, + }); + }; beforeEach(() => { - const mockedContext = createAppRootMockRenderer(); - + mockedContext = createAppRootMockRenderer(); apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http); + setConsoleCommands(); - render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => { + render = async () => { renderResult = mockedContext.render( { return { consoleProps: { 'data-test-subj': 'test', - commands: getEndpointConsoleCommands({ - agentType: 'endpoint', - endpointAgentId: 'a.b.c', - endpointCapabilities: [...capabilities], - endpointPrivileges: { - ...getEndpointAuthzInitialState(), - loading: false, - canKillProcess: true, - canSuspendProcess: true, - canGetRunningProcesses: true, - }, - }), + commands: consoleCommands, }, }; }} @@ -67,13 +77,15 @@ describe('When using processes action from response actions console', () => { await consoleManagerMockAccess.clickOnRegisterNewConsole(); await consoleManagerMockAccess.openRunningConsole(); + consoleSelectors = getConsoleSelectorsAndActionMock(renderResult); return renderResult; }; }); it('should show an error if the `running_processes` capability is not present in the endpoint', async () => { - await render([]); + setConsoleCommands([]); + await render(); enterConsoleCommand(renderResult, 'processes'); expect(renderResult.getByTestId('test-validationError-message').textContent).toEqual( @@ -228,4 +240,80 @@ describe('When using processes action from response actions console', () => { expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(1); }); }); + + describe('and when agent type is SentinelOne', () => { + beforeEach(() => { + mockedContext.setExperimentalFlag({ responseActionsSentinelOneProcessesEnabled: true }); + setConsoleCommands([], 'sentinel_one'); + }); + + it('should display processes command --help', async () => { + await render(); + enterConsoleCommand(renderResult, 'processes --help'); + + await waitFor(() => { + expect(renderResult.getByTestId('test-helpOutput').textContent).toEqual( + 'About' + + 'Show all running processes' + + 'Usage' + + 'processes [--comment]' + + 'Example' + + 'processes --comment "get the processes"' + + 'Optional parameters' + + '--comment - A comment to go along with the action' + ); + }); + }); + + it('should display correct entry in help panel', async () => { + await render(); + consoleSelectors.openHelpPanel(); + + expect( + renderResult.getByTestId('test-commandList-Responseactions-processes') + ).toHaveTextContent('processesShow all running processes'); + }); + + it('should call the api with agentType of SentinelOne', async () => { + await render(); + enterConsoleCommand(renderResult, 'processes'); + + await waitFor(() => { + expect(apiMocks.responseProvider.processes).toHaveBeenCalledWith({ + body: '{"endpoint_ids":["a.b.c"],"agent_type":"sentinel_one"}', + path: '/api/endpoint/action/running_procs', + version: '2023-10-31', + }); + }); + }); + + describe('and `responseActionsSentinelOneProcessesEnabled` feature flag is disabled', () => { + beforeEach(() => { + mockedContext.setExperimentalFlag({ responseActionsSentinelOneProcessesEnabled: false }); + setConsoleCommands([], 'sentinel_one'); + }); + + it('should not display `processes` command in console help', async () => { + await render(); + consoleSelectors.openHelpPanel(); + + expect(renderResult.queryByTestId('test-commandList-Responseactions-processes')).toBeNull(); + }); + + it('should error if user enters `process` command', async () => { + await render(); + enterConsoleCommand(renderResult, 'processes'); + + await waitFor(() => { + expect(renderResult.getByTestId('test-validationError')).toHaveTextContent( + 'Unsupported actionSupport for processes is not currently available for SentinelOne.' + ); + }); + + await waitFor(() => { + expect(apiMocks.responseProvider.processes).not.toHaveBeenCalled(); + }); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts index 80dce9de15435..74c43fb535f5f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts @@ -551,6 +551,7 @@ const adjustCommandsForSentinelOne = ({ }): CommandDefinition[] => { const featureFlags = ExperimentalFeaturesService.get(); const isKillProcessEnabled = featureFlags.responseActionsSentinelOneKillProcessEnabled; + const isProcessesEnabled = featureFlags.responseActionsSentinelOneProcessesEnabled; return commandList.map((command) => { // Kill-Process: adjust command to accept only `processName` @@ -574,6 +575,7 @@ const adjustCommandsForSentinelOne = ({ if ( command.name === 'status' || (command.name === 'kill-process' && !isKillProcessEnabled) || + (command.name === 'processes' && !isProcessesEnabled) || !isAgentTypeAndActionSupported( 'sentinel_one', RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[command.name as ConsoleResponseActionCommands], diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts index 6a5304f17b82e..409e3d59c7e3f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts @@ -37,7 +37,10 @@ import type { KillOrSuspendProcessRequestBody, } from '../../../../../../common/endpoint/types'; import type { SearchHit, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; -import type { ResponseActionGetFileRequestBody } from '../../../../../../common/api/endpoint'; +import type { + ResponseActionGetFileRequestBody, + GetProcessesRequestBody, +} from '../../../../../../common/api/endpoint'; import { SUB_ACTION } from '@kbn/stack-connectors-plugin/common/sentinelone/constants'; import { ACTIONS_SEARCH_PAGE_SIZE } from '../../constants'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; @@ -75,14 +78,15 @@ describe('SentinelOneActionsClient class', () => { s1ActionsClient = new SentinelOneActionsClient(classConstructorOptions); }); - it.each(['suspendProcess', 'runningProcesses', 'execute', 'upload', 'scan'] as Array< - keyof ResponseActionsClient - >)('should throw an un-supported error for %s', async (methodName) => { - // @ts-expect-error Purposely passing in empty object for options - await expect(s1ActionsClient[methodName]({})).rejects.toBeInstanceOf( - ResponseActionsNotSupportedError - ); - }); + it.each(['suspendProcess', 'execute', 'upload', 'scan'] as Array)( + 'should throw an un-supported error for %s', + async (methodName) => { + // @ts-expect-error Purposely passing in empty object for options + await expect(s1ActionsClient[methodName]({})).rejects.toBeInstanceOf( + ResponseActionsNotSupportedError + ); + } + ); it('should error if multiple agent ids are received', async () => { const payload = createS1IsolationOptions(); @@ -1294,4 +1298,163 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); }); + + describe('#runningProcesses()', () => { + let processesActionRequest: GetProcessesRequestBody; + + beforeEach(() => { + // @ts-expect-error readonly prop assignment + classConstructorOptions.endpointService.experimentalFeatures.responseActionsSentinelOneProcessesEnabled = + true; + + processesActionRequest = responseActionsClientMock.createRunningProcessesOptions(); + }); + + it('should error if feature flag is disabled', async () => { + // @ts-expect-error readonly prop assignment + classConstructorOptions.endpointService.experimentalFeatures.responseActionsSentinelOneProcessesEnabled = + false; + + await expect(s1ActionsClient.runningProcesses(processesActionRequest)).rejects.toThrow( + `processes not supported for sentinel_one agent type. Feature disabled` + ); + }); + + it('should error if host is running Windows', async () => { + connectorActionsMock.execute.mockResolvedValue( + responseActionsClientMock.createConnectorActionExecuteResponse({ + data: sentinelOneMock.createGetAgentsResponse([ + sentinelOneMock.createSentinelOneAgentDetails({ osType: 'windows' }), + ]), + }) + ); + + await expect(s1ActionsClient.runningProcesses(processesActionRequest)).rejects.toThrow( + 'Retrieval of running processes for Windows host is not supported by SentinelOne' + ); + }); + + it('should retrieve script execution information from S1 using host OS', async () => { + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(connectorActionsMock.execute).toHaveBeenCalledWith({ + params: { + subAction: 'getRemoteScripts', + subActionParams: { + osTypes: 'linux', + query: 'process list', + scriptType: 'dataCollection', + }, + }, + }); + }); + + it('should error if unable to get S1 script information', async () => { + const executeMockImplementation = connectorActionsMock.execute.getMockImplementation()!; + connectorActionsMock.execute.mockImplementation(async (options) => { + if (options.params.subAction === SUB_ACTION.GET_REMOTE_SCRIPTS) { + return responseActionsClientMock.createConnectorActionExecuteResponse({ + data: { data: [] }, + }); + } + return executeMockImplementation.call(connectorActionsMock, options); + }); + + await expect(s1ActionsClient.runningProcesses(processesActionRequest)).rejects.toThrow( + 'Unable to find a script from SentinelOne to handle [running-processes] response action for host running [linux])' + ); + }); + + it('should send execute script request to S1 for process list', async () => { + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(connectorActionsMock.execute).toHaveBeenCalledWith({ + params: { + subAction: 'executeScript', + subActionParams: { + filter: { uuids: '1-2-3' }, + script: { + inputParams: '', + outputDestination: 'SentinelCloud', + requiresApproval: false, + scriptId: '1466645476786791838', + taskDescription: expect.stringContaining( + 'Action triggered from Elastic Security by user [foo] for action [running-processes' + ), + }, + }, + }, + }); + }); + + it('should return action details on success', async () => { + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(getActionDetailsByIdMock).toHaveBeenCalled(); + }); + + it('should create action request doc with expected meta info', async () => { + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(classConstructorOptions.esClient.index).toHaveBeenCalledWith( + { + document: { + '@timestamp': expect.any(String), + EndpointActions: { + action_id: expect.any(String), + data: { + command: 'running-processes', + comment: 'test comment', + hosts: { '1-2-3': { name: 'sentinelone-1460' } }, + parameters: undefined, + }, + expiration: expect.any(String), + input_type: 'sentinel_one', + type: 'INPUT_ACTION', + }, + agent: { id: ['1-2-3'] }, + meta: { + agentId: '1845174760470303882', + agentUUID: '1-2-3', + hostName: 'sentinelone-1460', + parentTaskId: 'task-789', + }, + user: { id: 'foo' }, + }, + index: '.logs-endpoint.actions-default', + refresh: 'wait_for', + }, + { meta: true } + ); + }); + + it('should update cases', async () => { + processesActionRequest = { + ...processesActionRequest, + case_ids: ['case-1'], + }; + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); + }); + + it('should still create action request when running in automated mode', async () => { + classConstructorOptions.isAutomated = true; + classConstructorOptions.connectorActions = + responseActionsClientMock.createNormalizedExternalConnectorClient( + sentinelOneMock.createConnectorActionsClient() + ); + s1ActionsClient = new SentinelOneActionsClient(classConstructorOptions); + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect(classConstructorOptions.esClient.index).toHaveBeenCalledWith( + expect.objectContaining({ + document: expect.objectContaining({ + error: { message: 'Action [running-processes] not supported' }, + }), + }), + { meta: true } + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts index 54d2147cbe2e2..2945943438917 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts @@ -27,7 +27,11 @@ import type { } from '@elastic/elasticsearch/lib/api/types'; import type { Readable } from 'stream'; import type { Mutable } from 'utility-types'; -import type { SentinelOneKillProcessScriptArgs, SentinelOneScriptArgs } from './types'; +import type { + SentinelOneKillProcessScriptArgs, + SentinelOneScriptArgs, + SentinelOneProcessListScriptArgs, +} from './types'; import { ACTIONS_SEARCH_PAGE_SIZE } from '../../constants'; import type { NormalizedExternalConnectorClient } from '../lib/normalized_external_connector_client'; import { SENTINEL_ONE_ACTIVITY_INDEX_PATTERN } from '../../../../../../common'; @@ -65,8 +69,11 @@ import type { SentinelOneKillProcessRequestMeta, UploadedFileInfo, ResponseActionParametersWithProcessName, + GetProcessesActionOutputContent, + SentinelOneProcessesRequestMeta, } from '../../../../../../common/endpoint/types'; import type { + GetProcessesRequestBody, IsolationRouteRequestBody, ResponseActionGetFileRequestBody, } from '../../../../../../common/api/endpoint'; @@ -663,6 +670,80 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { return actionDetails; } + public async runningProcesses( + actionRequest: GetProcessesRequestBody, + options?: CommonResponseActionMethodOptions + ): Promise> { + if ( + !this.options.endpointService.experimentalFeatures.responseActionsSentinelOneProcessesEnabled + ) { + throw new ResponseActionsClientError( + `processes not supported for ${this.agentType} agent type. Feature disabled`, + 400 + ); + } + + const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + undefined, + GetProcessesActionOutputContent, + Partial + > = { + ...actionRequest, + ...this.getMethodOptions(options), + command: 'running-processes', + meta: { parentTaskId: '' }, + }; + + if (!reqIndexOptions.error) { + let error = (await this.validateRequest(reqIndexOptions)).error; + + if (!error) { + const s1AgentDetails = await this.getAgentDetails(reqIndexOptions.endpoint_ids[0]); + const processesScriptInfo = await this.fetchScriptInfo( + 'running-processes', + s1AgentDetails.osType + ); + + try { + const s1Response = await this.sendAction( + SUB_ACTION.EXECUTE_SCRIPT, + { + filter: { + uuids: actionRequest.endpoint_ids[0], + }, + script: { + scriptId: processesScriptInfo.scriptId, + taskDescription: this.buildExternalComment(reqIndexOptions), + requiresApproval: false, + outputDestination: 'SentinelCloud', + inputParams: processesScriptInfo.buildScriptArgs({}), + }, + } + ); + + reqIndexOptions.meta = { + parentTaskId: s1Response.data?.data?.parentTaskId ?? '', + }; + } catch (err) { + error = err; + } + } + + reqIndexOptions.error = error?.message; + + if (!this.options.isAutomated && error) { + throw error; + } + } + + const { actionDetails } = await this.handleResponseActionCreation< + undefined, + GetProcessesActionOutputContent + >(reqIndexOptions); + + return actionDetails; + } + async processPendingActions({ abortSignal, addToQueue, @@ -670,7 +751,6 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { if (abortSignal.aborted) { return; } - for await (const pendingActions of this.fetchAllPendingActions()) { if (abortSignal.aborted) { return; @@ -729,14 +809,15 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { private async fetchScriptInfo< TScriptOptions extends SentinelOneScriptArgs = SentinelOneScriptArgs >( - scriptType: Extract, + scriptType: Extract, osType: string | 'linux' | 'macos' | 'windows' ): Promise> { const searchQueryParams: Mutable> = { query: '', osTypes: osType, }; - let buildScriptArgs = NOOP_THROW as FetchScriptInfoResponse['buildScriptArgs']; + let buildScriptArgs: FetchScriptInfoResponse['buildScriptArgs'] = + NOOP_THROW as FetchScriptInfoResponse['buildScriptArgs']; let isDesiredScript: ( scriptInfo: SentinelOneGetRemoteScriptsResponse['data'][number] ) => boolean = () => false; @@ -746,7 +827,6 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { case 'kill-process': searchQueryParams.query = 'terminate'; searchQueryParams.scriptType = 'action'; - isDesiredScript = (scriptInfo) => { return ( scriptInfo.creator === 'SentinelOne' && @@ -758,6 +838,22 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { }; break; + case 'running-processes': + if (osType === 'windows') { + throw new ResponseActionsClientError( + `Retrieval of running processes for Windows host is not supported by SentinelOne`, + 405 + ); + } + + searchQueryParams.query = 'process list'; + searchQueryParams.scriptType = 'dataCollection'; + isDesiredScript = (scriptInfo) => { + return scriptInfo.creator === 'SentinelOne' && scriptInfo.creatorId === '-1'; + }; + + break; + default: throw new ResponseActionsClientError( `Unable to fetch SentinelOne script for OS [${osType}]. Unknown script type [${scriptType}]` @@ -780,9 +876,10 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { ); } + // Define the `buildScriptArgs` callback for the Script type switch (scriptType) { case 'kill-process': - buildScriptArgs = (args: SentinelOneKillProcessScriptArgs) => { + buildScriptArgs = ((args: SentinelOneKillProcessScriptArgs) => { if (!args.processName) { throw new ResponseActionsClientError( `'processName' missing while building script args for [${s1Script.scriptName} (id: ${s1Script.id})] script` @@ -795,8 +892,12 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { // Linux + Macos return `--terminate --processes "${args.processName}" --force`; - }; + }) as FetchScriptInfoResponse['buildScriptArgs']; + + break; + case 'running-processes': + buildScriptArgs = () => ''; break; } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/types.ts index a51a9ec981765..e3841d5a50a08 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/types.ts @@ -9,7 +9,11 @@ export interface SentinelOneKillProcessScriptArgs { processName: string; } +export type SentinelOneProcessListScriptArgs = Record; + /** * All the possible set of arguments running SentinelOne scripts that we support for response actions */ -export type SentinelOneScriptArgs = SentinelOneKillProcessScriptArgs; +export type SentinelOneScriptArgs = + | SentinelOneKillProcessScriptArgs + | SentinelOneProcessListScriptArgs;