diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.test.ts index cba00e7645e9f..0e45db597fa22 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.test.ts @@ -19,6 +19,10 @@ import { } from '../../../../../../common/endpoint/constants'; import { SUB_ACTION } from '@kbn/stack-connectors-plugin/common/crowdstrike/constants'; import type { NormalizedExternalConnectorClient } from '../../..'; +import { + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, + ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT, +} from '../../../../../lib/telemetry/event_based/events'; jest.mock('../../action_details_by_id', () => { const originalMod = jest.requireActual('../../action_details_by_id'); @@ -43,6 +47,14 @@ describe('CrowdstrikeActionsClient class', () => { > = {} ) => responseActionsClientMock.createIsolateOptions({ ...overrides, agent_type: 'crowdstrike' }); + const createCrowdstrikeRunscrtiptOptions = ( + overrides: Omit< + Parameters[0], + 'agent_type' + > = {} + ) => + responseActionsClientMock.createRunScriptOptions({ ...overrides, agent_type: 'crowdstrike' }); + beforeEach(() => { classConstructorOptions = CrowdstrikeMock.createConstructorOptions(); connectorActionsMock = classConstructorOptions.connectorActions; @@ -217,6 +229,84 @@ describe('CrowdstrikeActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `isolate` action creation telemetry event', async () => { + await crowdstrikeActionsClient.isolate(createCrowdstrikeIsolationOptions()); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'crowdstrike', + command: 'isolate', + isAutomated: false, + }, + }); + }); + + it('should send `isolate` action response telemetry event for successful action', async () => { + const actionResponse = { + data: { + errors: [], + action_id: '123-345-456', + action_status: 'successful', + command: 'isolate', + agent_type: 'crowdstrike', + agent_id: '1-2-3', + }, + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + await crowdstrikeActionsClient.isolate( + createCrowdstrikeIsolationOptions({ actionId: '123-345-456' }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '123-345-456', + actionStatus: 'successful', + agentType: 'crowdstrike', + command: 'isolate', + }, + }); + }); + + it('should send `isolate` action response telemetry event for failed action', async () => { + const actionResponse = { + data: { + errors: [ + { + message: 'Failed to isolate host', + }, + ], + action_id: '123-456-678', + action_status: 'failed', + command: 'isolate', + agent_type: 'crowdstrike', + agent_id: '1-2-3', + }, + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + + await crowdstrikeActionsClient.isolate( + createCrowdstrikeIsolationOptions({ actionId: '123-456-678' }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '123-456-678', + actionStatus: 'failed', + agentType: 'crowdstrike', + command: 'isolate', + }, + }); + }); + }); }); describe('#release()', () => { @@ -313,5 +403,291 @@ describe('CrowdstrikeActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `release` action creation telemetry event', async () => { + await crowdstrikeActionsClient.release(createCrowdstrikeIsolationOptions()); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'crowdstrike', + command: 'unisolate', + isAutomated: false, + }, + }); + }); + + it('should send `release` action response telemetry event for successful action', async () => { + const actionResponse = { + data: { + errors: [], + action_id: '123-345-456', + action_status: 'successful', + command: 'unisolate', + agent_type: 'crowdstrike', + agent_id: '1-2-3', + }, + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + await crowdstrikeActionsClient.release( + createCrowdstrikeIsolationOptions({ actionId: '123-345-456' }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '123-345-456', + actionStatus: 'successful', + agentType: 'crowdstrike', + command: 'unisolate', + }, + }); + }); + + it('should send `release` action response telemetry event for failed action', async () => { + const actionResponse = { + data: { + errors: [ + { + message: 'Failed to release host', + }, + ], + action_id: '123-456-678', + action_status: 'failed', + command: 'unisolate', + agent_type: 'crowdstrike', + agent_id: '1-2-3', + }, + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + + await crowdstrikeActionsClient.release( + createCrowdstrikeIsolationOptions({ actionId: '123-456-678' }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '123-456-678', + actionStatus: 'failed', + agentType: 'crowdstrike', + command: 'unisolate', + }, + }); + }); + }); + }); + + describe('#runscript()', () => { + it('should send action to Crowdstrike', async () => { + await crowdstrikeActionsClient.runscript( + createCrowdstrikeRunscrtiptOptions({ + actionId: '123-456-789', + endpoint_ids: ['1-2-3-cs-agent'], + comment: 'test runscript comment', + parameters: { + raw: 'echo "Hello World"', + }, + }) + ); + + expect(connectorActionsMock.execute as jest.Mock).toHaveBeenCalledWith({ + params: { + subAction: SUB_ACTION.EXECUTE_ADMIN_RTR, + subActionParams: { + command: 'runscript --Raw=```echo "Hello World"```', + endpoint_ids: ['1-2-3-cs-agent'], + actionParameters: { + comment: + 'Action triggered from Elastic Security by user [foo] for action [runscript (action id: 123-456-789)]: test runscript comment', + }, + }, + }, + }); + }); + + it('should write action request to endpoint indexes', async () => { + await crowdstrikeActionsClient.runscript(responseActionsClientMock.createRunScriptOptions()); + + expect(classConstructorOptions.esClient.index).toHaveBeenCalledTimes(2); + expect(classConstructorOptions.esClient.index.mock.calls[0][0]).toEqual({ + document: { + '@timestamp': expect.any(String), + EndpointActions: { + action_id: expect.any(String), + data: { + command: 'runscript', + comment: 'test comment', + parameters: { raw: 'ls' }, + hosts: { + '1-2-3': { + name: 'Crowdstrike-1460', + }, + }, + }, + expiration: expect.any(String), + input_type: 'crowdstrike', + type: 'INPUT_ACTION', + }, + agent: { id: ['1-2-3'] }, + meta: { + hostName: 'Crowdstrike-1460', + }, + user: { id: 'foo' }, + }, + index: ENDPOINT_ACTIONS_INDEX, + refresh: 'wait_for', + }); + expect(classConstructorOptions.esClient.index.mock.calls[1][0]).toEqual({ + document: { + '@timestamp': expect.any(String), + agent: { id: ['1-2-3'] }, + EndpointActions: { + action_id: expect.any(String), + completed_at: expect.any(String), + started_at: expect.any(String), + data: { + command: 'runscript', + comment: 'test comment', + hosts: { + '1-2-3': { + name: 'Crowdstrike-1460', + }, + }, + output: { + content: { + code: '200', + stderr: '', + stdout: '', + }, + type: 'text', + }, + parameters: { raw: 'ls' }, + }, + input_type: 'crowdstrike', + }, + error: undefined, + meta: undefined, + }, + index: ENDPOINT_ACTION_RESPONSES_INDEX, + refresh: 'wait_for', + }); + }); + + it('should return action details', async () => { + await crowdstrikeActionsClient.runscript(responseActionsClientMock.createRunScriptOptions()); + + expect(getActionDetailsByIdMock).toHaveBeenCalled(); + }); + + it('should update cases', async () => { + await crowdstrikeActionsClient.runscript( + responseActionsClientMock.createRunScriptOptions({ + case_ids: ['case-1'], + }) + ); + + expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); + }); + + describe('telemetry events', () => { + it('should send `runscript` action creation telemetry event', async () => { + await crowdstrikeActionsClient.runscript( + responseActionsClientMock.createRunScriptOptions() + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'crowdstrike', + command: 'runscript', + isAutomated: false, + }, + }); + }); + + it('should send `runscript` action response telemetry event for successful action', async () => { + const actionResponse = { + actionId: '123-abc-678', + data: undefined, + status: 'ok', + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + + await crowdstrikeActionsClient.runscript( + createCrowdstrikeRunscrtiptOptions({ + actionId: '123-abc-678', + endpoint_ids: ['1-2-3-cs-agent'], + comment: 'test runscript comment', + parameters: { + raw: 'echo "Hello World"', + }, + }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '123-abc-678', + actionStatus: 'successful', + agentType: 'crowdstrike', + command: 'runscript', + }, + }); + }); + + it('should send `runscript` action response telemetry event for failed action', async () => { + const actionResponse = { + actionId: '456-pqr-789', + status: 'ok', + data: { + combined: { + resources: { + '1-2-3-cs-agent': { + stdout: '', + stderr: '', + errors: [ + { + code: '500', + message: 'Failed to run script on host', + }, + ], + }, + }, + }, + }, + }; + (connectorActionsMock.execute as jest.Mock).mockResolvedValueOnce(actionResponse); + + await crowdstrikeActionsClient.runscript( + createCrowdstrikeRunscrtiptOptions({ + actionId: '456-pqr-789', + endpoint_ids: ['1-2-3-cs-agent'], + parameters: { + raw: 'echo "Hello World"', + }, + }) + ); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenNthCalledWith(2, ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: '456-pqr-789', + actionStatus: 'failed', + agentType: 'crowdstrike', + command: 'runscript', + }, + }); + }); + }); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts index ace7efa3e5388..8316917fa6f29 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/crowdstrike_actions_client.ts @@ -30,6 +30,7 @@ import { stringify } from '../../../../utils/stringify'; import { ResponseActionsClientError } from '../errors'; import type { ActionDetails, + EndpointActionData, EndpointActionDataParameterTypes, EndpointActionResponseDataOutput, LogsEndpointAction, @@ -56,6 +57,18 @@ export type CrowdstrikeActionsClientOptions = ResponseActionsClientOptions & { connectorActions: NormalizedExternalConnectorClient; }; +interface CrowdstrikeResponseOptions { + error?: + | { + code: string; + message: string; + } + | undefined; + actionId: string; + agentId: string | string[]; + data: EndpointActionData; +} + export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { protected readonly agentType: ResponseActionAgentType = 'crowdstrike'; private readonly connectorActionsClient: NormalizedExternalConnectorClient; @@ -378,7 +391,7 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { const stdout = actionResponse.data?.combined.resources[agentId].stdout || ''; const stderr = actionResponse.data?.combined.resources[agentId].stderr || ''; const error = actionResponse.data?.combined.resources[agentId].errors?.[0]; - const options = { + const options: CrowdstrikeResponseOptions = { actionId: doc.EndpointActions.action_id, agentId, data: { @@ -402,14 +415,16 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { : {}), }; - await this.writeActionResponseToEndpointIndex(options); + const responseDoc = await this.writeActionResponseToEndpointIndex(options); + // telemetry event for completed action + await this.sendActionResponseTelemetry([responseDoc]); } private async completeCrowdstrikeAction( actionResponse: ActionTypeExecutorResult, doc: LogsEndpointAction ): Promise { - const options = { + const options: CrowdstrikeResponseOptions = { actionId: doc.EndpointActions.action_id, agentId: doc.agent.id, data: doc.EndpointActions.data, @@ -423,7 +438,9 @@ export class CrowdstrikeActionsClient extends ResponseActionsClientImpl { : {}), }; - await this.writeActionResponseToEndpointIndex(options); + const responseDoc = await this.writeActionResponseToEndpointIndex(options); + // telemetry event for completed action + await this.sendActionResponseTelemetry([responseDoc]); } async getCustomScripts(): Promise { diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.test.ts index 3448cff50d5da..42b5fe6baf4d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.test.ts @@ -29,6 +29,10 @@ import type { MicrosoftDefenderEndpointMachineAction, } from '@kbn/stack-connectors-plugin/common/microsoft_defender_endpoint/types'; import { MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION } from '@kbn/stack-connectors-plugin/common/microsoft_defender_endpoint/constants'; +import { + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, + ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT, +} from '../../../../../../../lib/telemetry/event_based/events'; jest.mock('../../../../action_details_by_id', () => { const originalMod = jest.requireActual('../../../../action_details_by_id'); @@ -181,6 +185,22 @@ describe('MS Defender response actions client', () => { expect(clientConstructorOptionsMock.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it(`should send ${responseActionMethod} action creation telemetry event`, async () => { + await msClientMock[responseActionMethod](responseActionsClientMock.createIsolateOptions()); + expect( + clientConstructorOptionsMock.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'microsoft_defender_endpoint', + command: responseActionMethod === 'release' ? 'unisolate' : responseActionMethod, + isAutomated: false, + }, + }); + }); + }); }); describe('#runscript()', () => { @@ -358,6 +378,26 @@ describe('MS Defender response actions client', () => { }) ); }); + + describe('telemetry event', () => { + it('should send runscript action creation telemetry event', async () => { + await msClientMock.runscript( + responseActionsClientMock.createRunScriptOptions({ + parameters: { scriptName: 'test-script.ps1', args: 'arg1 arg2' }, + }) + ); + expect( + clientConstructorOptionsMock.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'microsoft_defender_endpoint', + command: 'runscript', + isAutomated: false, + }, + }); + }); + }); }); describe('#getFileInfo()', () => { @@ -1054,5 +1094,178 @@ describe('MS Defender response actions client', () => { } ); }); + + describe('telemetry events', () => { + describe('for Isolate and Release', () => { + let msMachineActionsApiResponse: MicrosoftDefenderEndpointGetActionsResponse; + + beforeEach(() => { + const generator = new EndpointActionGenerator('seed'); + + const actionRequestsSearchResponse = generator.toEsSearchResponse([ + generator.generateActionEsHit< + undefined, + {}, + MicrosoftDefenderEndpointActionRequestCommonMeta + >({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { + data: { command: 'isolate' }, + input_type: 'microsoft_defender_endpoint', + }, + meta: { machineActionId: '5382f7ea-7557-4ab7-9782-d50480024a4e' }, + }), + ]); + + applyEsClientSearchMock({ + esClientMock: clientConstructorOptionsMock.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + msMachineActionsApiResponse = microsoftDefenderMock.createGetActionsApiResponse(); + responseActionsClientMock.setConnectorActionsClientExecuteResponse( + connectorActionsMock, + MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_ACTIONS, + msMachineActionsApiResponse + ); + + const msGetActionResultsApiResponse = + microsoftDefenderMock.createGetActionResultsApiResponse(); + + // Set the mock response for GET_ACTION_RESULTS + responseActionsClientMock.setConnectorActionsClientExecuteResponse( + connectorActionsMock, + MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_ACTION_RESULTS, + msGetActionResultsApiResponse + ); + }); + + it.each` + msStatusValue | responseState + ${'Failed'} | ${'failed'} + ${'TimeOut'} | ${'failed'} + ${'Cancelled'} | ${'failed'} + ${'Succeeded'} | ${'successful'} + `( + 'should send telemetry for $responseState action response if MS machine action status is $msStatusValue', + async ({ msStatusValue, responseState }) => { + msMachineActionsApiResponse.value[0].status = msStatusValue; + + await msClientMock.processPendingActions(processPendingActionsOptions); + expect( + clientConstructorOptionsMock.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: responseState, + agentType: 'microsoft_defender_endpoint', + command: 'isolate', + }, + }); + } + ); + }); + + describe('for Runscript', () => { + let msMachineActionsApiResponse: MicrosoftDefenderEndpointGetActionsResponse; + + beforeEach(() => { + // @ts-expect-error assign to readonly property + clientConstructorOptionsMock.endpointService.experimentalFeatures.microsoftDefenderEndpointRunScriptEnabled = + true; + + const generator = new EndpointActionGenerator('seed'); + + const actionRequestsSearchResponse = generator.toEsSearchResponse([ + generator.generateActionEsHit< + { scriptName: string }, + {}, + MicrosoftDefenderEndpointActionRequestCommonMeta + >({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { + data: { command: 'runscript', parameters: { scriptName: 'test-script.ps1' } }, + input_type: 'microsoft_defender_endpoint', + }, + meta: { machineActionId: '5382f7ea-7557-4ab7-9782-d50480024a4e' }, + }), + ]); + + applyEsClientSearchMock({ + esClientMock: clientConstructorOptionsMock.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + msMachineActionsApiResponse = microsoftDefenderMock.createGetActionsApiResponse(); + // Override the default machine action to be runscript-specific + msMachineActionsApiResponse.value[0] = { + ...msMachineActionsApiResponse.value[0], + type: 'LiveResponse', + commands: ['RunScript'], + }; + + responseActionsClientMock.setConnectorActionsClientExecuteResponse( + connectorActionsMock, + MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_ACTIONS, + msMachineActionsApiResponse + ); + + const msGetActionResultsApiResponse = + microsoftDefenderMock.createGetActionResultsApiResponse(); + + // Set the mock response for GET_ACTION_RESULTS + responseActionsClientMock.setConnectorActionsClientExecuteResponse( + connectorActionsMock, + MICROSOFT_DEFENDER_ENDPOINT_SUB_ACTION.GET_ACTION_RESULTS, + msGetActionResultsApiResponse + ); + }); + + it('should send telemetry for completed runscript actions', async () => { + await msClientMock.processPendingActions(processPendingActionsOptions); + + expect( + clientConstructorOptionsMock.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'successful', + agentType: 'microsoft_defender_endpoint', + command: 'runscript', + }, + }); + }); + + it.each` + msStatusValue | responseState + ${'Failed'} | ${'failed'} + ${'TimeOut'} | ${'failed'} + ${'Cancelled'} | ${'failed'} + ${'Succeeded'} | ${'successful'} + `( + 'should generate $responseState action response if MS runscript machine action status is $msStatusValue', + async ({ msStatusValue, responseState }) => { + msMachineActionsApiResponse.value[0].status = msStatusValue; + + await msClientMock.processPendingActions(processPendingActionsOptions); + + expect( + clientConstructorOptionsMock.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: responseState, + agentType: 'microsoft_defender_endpoint', + command: 'runscript', + }, + }); + } + ); + }); + }); }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts index d27120545edcc..fbe4149273707 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts @@ -51,7 +51,10 @@ import type { SentinelOneGetRemoteScriptStatusApiResponse, SentinelOneRemoteScriptExecutionStatus, } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; -import { ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT } from '../../../../../lib/telemetry/event_based/events'; +import { + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, + ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT, +} from '../../../../../lib/telemetry/event_based/events'; jest.mock('../../action_details_by_id', () => { const originalMod = jest.requireActual('../../action_details_by_id'); @@ -235,6 +238,23 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `isolate` action creation telemetry event', async () => { + await s1ActionsClient.isolate(createS1IsolationOptions()); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'sentinel_one', + command: 'isolate', + isAutomated: false, + }, + }); + }); + }); }); describe('#release()', () => { @@ -368,6 +388,23 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `release` action creation telemetry event', async () => { + await s1ActionsClient.release(createS1IsolationOptions()); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'sentinel_one', + command: 'unisolate', + isAutomated: false, + }, + }); + }); + }); }); describe('#processPendingActions()', () => { @@ -906,7 +943,7 @@ describe('SentinelOneActionsClient class', () => { }); }); - describe('Telemetry', () => { + describe('telemetry events', () => { describe('for Isolate and Release', () => { let s1ActivityHits: Array>; @@ -1387,6 +1424,23 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `get-file` action creation telemetry event', async () => { + await s1ActionsClient.getFile(getFileReqOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'sentinel_one', + command: 'get-file', + isAutomated: false, + }, + }); + }); + }); }); describe('#getFileInfo()', () => { @@ -1781,6 +1835,23 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); + + describe('telemetry events', () => { + it('should send `kill-process` action creation telemetry event', async () => { + await s1ActionsClient.killProcess(killProcessActionRequest); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'sentinel_one', + command: 'kill-process', + isAutomated: false, + }, + }); + }); + }); }); describe('#runningProcesses()', () => { @@ -1940,5 +2011,22 @@ describe('SentinelOneActionsClient class', () => { { meta: true } ); }); + + describe('telemetry events', () => { + it('should send `kill-process` action creation telemetry event', async () => { + await s1ActionsClient.runningProcesses(processesActionRequest); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + agentType: 'sentinel_one', + command: 'running-processes', + isAutomated: false, + }, + }); + }); + }); }); });