diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 0122eccb2e7ce9..88ca097e09b18a 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -182,6 +182,12 @@ export const allowedExperimentalValues = Object.freeze({ */ crowdstrikeDataInAnalyzerEnabled: true, + /** + * Enables Response actions telemetry collection + * Should be enabled in 8.17.0 + */ + responseActionsTelemetryEnabled: false, + /** * Enables experimental JAMF integration data to be available in Analyzer */ diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx index 7e19a03860013a..ba93729c68d2c6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/execute_action.tsx @@ -32,12 +32,13 @@ export const ExecuteActionResult = memo< >(({ command, setStore, store, status, setStatus, ResultComponent }) => { const actionCreator = useSendExecuteEndpoint(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { endpointId, agentType } = command.commandDefinition?.meta ?? {}; if (!endpointId) { return; } return { + agent_type: agentType, endpoint_ids: [endpointId], parameters: { command: command.args.args.command[0], @@ -46,7 +47,7 @@ export const ExecuteActionResult = memo< comment: command.args.args?.comment?.[0], }; }, [ - command.commandDefinition?.meta?.endpointId, + command.commandDefinition?.meta, command.args.args.command, command.args.args.timeout, command.args.args?.comment, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx index 90e44c4a56ee2a..8b45e1dee9dac1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/get_file_action.tsx @@ -23,9 +23,8 @@ export const GetFileActionResult = memo< const actionCreator = useSendGetFileRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { agentType, endpointId } = command.commandDefinition?.meta ?? {}; const { path, comment } = command.args.args; - const agentType = command.commandDefinition?.meta?.agentType; return endpointId ? { @@ -37,11 +36,7 @@ export const GetFileActionResult = memo< }, } : undefined; - }, [ - command.args.args, - command.commandDefinition?.meta?.agentType, - command.commandDefinition?.meta?.endpointId, - ]); + }, [command.args.args, command.commandDefinition?.meta]); const { result, actionDetails } = useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx index 52aaf0675e2df5..7e2a5bf6222338 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/execute_action.test.tsx @@ -153,7 +153,7 @@ describe('When using execute action from response actions console', () => { await waitFor(() => { expect(apiMocks.responseProvider.execute).toHaveBeenCalledWith({ - body: '{"endpoint_ids":["a.b.c"],"parameters":{"command":"ls -al"}}', + body: '{"agent_type":"endpoint","endpoint_ids":["a.b.c"],"parameters":{"command":"ls -al"}}', path: EXECUTE_ROUTE, version: '2023-10-31', }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/isolate_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/isolate_action.tsx index d4755a204482b6..325a84bc70bf0f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/isolate_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/isolate_action.tsx @@ -19,9 +19,8 @@ export const IsolateActionResult = memo( const isolateHostApi = useSendIsolateEndpointRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { agentType, endpointId } = command.commandDefinition?.meta ?? {}; const comment = command.args.args?.comment?.[0]; - const agentType = command.commandDefinition?.meta?.agentType; return endpointId ? { @@ -30,12 +29,7 @@ export const IsolateActionResult = memo( comment, } : undefined; - }, [ - command.args.args?.comment, - command.commandDefinition?.meta?.agentType, - command.commandDefinition?.meta?.endpointId, - isSentinelOneV1Enabled, - ]); + }, [command.args.args?.comment, command.commandDefinition?.meta, isSentinelOneV1Enabled]); return useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx index e96f1f0028b7d6..3f781d54240ffa 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/kill_process_action.tsx @@ -18,8 +18,7 @@ export const KillProcessActionResult = memo< const actionCreator = useSendKillProcessRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; - const agentType = command.commandDefinition?.meta?.agentType; + const { endpointId, agentType } = command.commandDefinition?.meta ?? {}; const parameters = parsedKillOrSuspendParameter(command.args.args); return endpointId @@ -30,11 +29,7 @@ export const KillProcessActionResult = memo< parameters, } : undefined; - }, [ - command.args.args, - command.commandDefinition?.meta?.agentType, - command.commandDefinition?.meta?.endpointId, - ]); + }, [command.args.args, command.commandDefinition?.meta]); return useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/release_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/release_action.tsx index 5f250ea6564140..52caba57af8623 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/release_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/release_action.tsx @@ -19,9 +19,8 @@ export const ReleaseActionResult = memo( const releaseHostApi = useSendReleaseEndpointRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { endpointId, agentType } = command.commandDefinition?.meta ?? {}; const comment = command.args.args?.comment?.[0]; - const agentType = command.commandDefinition?.meta?.agentType; return endpointId ? { @@ -30,12 +29,7 @@ export const ReleaseActionResult = memo( comment, } : undefined; - }, [ - command.args.args?.comment, - command.commandDefinition?.meta?.agentType, - command.commandDefinition?.meta?.endpointId, - isSentinelOneV1Enabled, - ]); + }, [command.args.args?.comment, command.commandDefinition?.meta, isSentinelOneV1Enabled]); return useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx index b2a73b426aa27d..d685e306e7b0df 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/suspend_process_action.tsx @@ -23,19 +23,20 @@ export const SuspendProcessActionResult = memo< const actionCreator = useSendSuspendProcessRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { agentType, endpointId } = command.commandDefinition?.meta ?? {}; const parameters = parsedKillOrSuspendParameter(command.args.args) as | ResponseActionParametersWithPid | ResponseActionParametersWithEntityId; return endpointId ? { + agent_type: agentType, endpoint_ids: [endpointId], comment: command.args.args?.comment?.[0], parameters, } : undefined; - }, [command.args.args, command.commandDefinition?.meta?.endpointId]); + }, [command.args.args, command.commandDefinition?.meta]); return useConsoleActionSubmitter({ ResultComponent, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/upload_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/upload_action.tsx index 5f27cb285ed17c..b656f7aade6eb5 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/upload_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/upload_action.tsx @@ -29,7 +29,7 @@ export const UploadActionResult = memo< const actionCreator = useSendUploadEndpointRequest(); const actionRequestBody = useMemo(() => { - const endpointId = command.commandDefinition?.meta?.endpointId; + const { agentType, endpointId } = command.commandDefinition?.meta ?? {}; const { comment, overwrite, file } = command.args.args; if (!endpointId) { @@ -37,6 +37,7 @@ export const UploadActionResult = memo< } const reqBody: UploadActionUIRequestBody = { + agent_type: agentType, endpoint_ids: [endpointId], ...(comment?.[0] ? { comment: comment?.[0] } : {}), parameters: @@ -49,7 +50,7 @@ export const UploadActionResult = memo< }; return reqBody; - }, [command.args.args, command.commandDefinition?.meta?.endpointId]); + }, [command.args.args, command.commandDefinition?.meta]); const { result, actionDetails } = useConsoleActionSubmitter< UploadActionUIRequestBody, diff --git a/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson index 9b69cdc5a8493d..2d89364a0449be 100644 --- a/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson +++ b/x-pack/plugins/security_solution/scripts/telemetry/saved_objects/security_solution_ebt_kibana_server.ndjson @@ -1,2 +1,2 @@ -{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{},\"properties.alertsContextCount\":{},\"properties.alertsCount\":{},\"properties.configuredAlertsCount\":{},\"properties.discoveriesGenerated\":{},\"properties.durationMs\":{},\"properties.provider\":{},\"properties.total_tokens\":{},\"properties.prompt_tokens\":{},\"properties.completion_tokens\":{},\"properties.suppressionRuleType\":{},\"properties.suppressionMissingFields\":{},\"properties.suppressionAlertsCreated\":{},\"properties.suppressionAlertsSuppressed\":{},\"properties.suppressionRuleName\":{},\"properties.suppressionDuration\":{},\"properties.suppressionFieldsNumber\":{},\"properties.suppressionGroupByFieldsNumber\":{},\"properties.suppressionGroupByFields\":{},\"properties.suppressionRuleId\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.alertsContextCount\":{\"type\":\"long\"},\"properties.alertsCount\":{\"type\":\"long\"},\"properties.configuredAlertsCount\":{\"type\":\"long\"},\"properties.discoveriesGenerated\":{\"type\":\"long\"},\"properties.durationMs\":{\"type\":\"long\"},\"properties.provider\":{\"type\":\"keyword\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"},\"properties.total_tokens\":{\"type\":\"long\"},\"properties.prompt_tokens\":{\"type\":\"long\"},\"properties.completion_tokens\":{\"type\":\"keyword\"},\"properties.suppressionMissingFields\":{\"type\":\"boolean\"},\"properties.suppressionAlertsCreated\":{\"type\":\"long\"},\"properties.suppressionAlertsSuppressed\":{\"type\":\"long\"},\"properties.suppressionRuleName\":{\"type\":\"keyword\"},\"properties.suppressionDuration\":{\"type\":\"long\"},\"properties.suppressionRuleType\":{\"type\":\"keyword\"},\"properties.suppressionGroupByFieldsNumber\":{\"type\":\"long\"},\"properties.suppressionGroupByFields\":{\"type\":\"keyword\"},\"properties.suppressionRuleId\":{\"type\":\"keyword\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:44.874Z","id":"security-solution-ebt-kibana-server","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-07-30T11:12:43.928Z","version":"WzM4ODczLDVd"} -{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file +{"attributes":{"allowHidden":false,"fieldAttrs":"{\"properties.model\":{},\"properties.resourceAccessed\":{},\"properties.resultCount\":{},\"properties.responseTime\":{},\"properties.errorMessage\":{},\"properties.isEnabledKnowledgeBase\":{},\"properties.isEnabledRAGAlerts\":{},\"properties.assistantStreamingEnabled\":{},\"properties.actionTypeId\":{},\"properties.message\":{},\"properties.productTier\":{},\"properties.failedToDeleteCount\":{},\"properties.totalInstalledCount\":{},\"properties.scoresWritten\":{},\"properties.taskDurationInSeconds\":{},\"properties.interval\":{},\"properties.alertSampleSizePerShard\":{},\"properties.status\":{},\"properties.processing.startTime\":{},\"properties.processing.endTime\":{},\"properties.processing.tookMs\":{},\"properties.result.successful\":{},\"properties.result.failed\":{},\"properties.result.total\":{},\"properties.alertsContextCount\":{},\"properties.alertsCount\":{},\"properties.configuredAlertsCount\":{},\"properties.discoveriesGenerated\":{},\"properties.durationMs\":{},\"properties.provider\":{},\"properties.total_tokens\":{},\"properties.prompt_tokens\":{},\"properties.completion_tokens\":{},\"properties.suppressionRuleType\":{},\"properties.suppressionMissingFields\":{},\"properties.suppressionAlertsCreated\":{},\"properties.suppressionAlertsSuppressed\":{},\"properties.suppressionRuleName\":{},\"properties.suppressionDuration\":{},\"properties.suppressionFieldsNumber\":{},\"properties.suppressionGroupByFieldsNumber\":{},\"properties.suppressionGroupByFields\":{},\"properties.suppressionRuleId\":{},\"properties.responseActions.actionId\":{},\"properties.responseActions.agentType\":{},\"properties.responseActions.command\":{},\"properties.responseActions.endpointIds\":{},\"properties.responseActions.isAutomated\":{},\"properties.responseActions.actionStatus\":{}}","fieldFormatMap":"{}","fields":"[]","name":"security-solution-ebt-kibana-server","runtimeFieldMap":"{\"properties.message\":{\"type\":\"keyword\"},\"properties.productTier\":{\"type\":\"keyword\"},\"properties.failedToDeleteCount\":{\"type\":\"long\"},\"properties.totalInstalledCount\":{\"type\":\"long\"},\"properties.isEnabledKnowledgeBase\":{\"type\":\"boolean\"},\"properties.isEnabledRAGAlerts\":{\"type\":\"boolean\"},\"properties.total_tokens\":{\"type\":\"long\"},\"properties.prompt_tokens\":{\"type\":\"long\"},\"properties.completion_tokens\":{\"type\":\"keyword\"},\"properties.suppressionGroupByFields\":{\"type\":\"keyword\"},\"properties.model\":{\"type\":\"keyword\"},\"properties.resourceAccessed\":{\"type\":\"keyword\"},\"properties.resultCount\":{\"type\":\"long\"},\"properties.responseTime\":{\"type\":\"long\"},\"properties.errorMessage\":{\"type\":\"keyword\"},\"properties.assistantStreamingEnabled\":{\"type\":\"boolean\"},\"properties.actionTypeId\":{\"type\":\"keyword\"},\"properties.alertsContextCount\":{\"type\":\"long\"},\"properties.alertsCount\":{\"type\":\"long\"},\"properties.configuredAlertsCount\":{\"type\":\"long\"},\"properties.discoveriesGenerated\":{\"type\":\"long\"},\"properties.durationMs\":{\"type\":\"long\"},\"properties.provider\":{\"type\":\"keyword\"},\"properties.scoresWritten\":{\"type\":\"long\"},\"properties.taskDurationInSeconds\":{\"type\":\"long\"},\"properties.interval\":{\"type\":\"keyword\"},\"properties.alertSampleSizePerShard\":{\"type\":\"long\"},\"properties.status\":{\"type\":\"keyword\"},\"properties.processing.startTime\":{\"type\":\"date\"},\"properties.processing.endTime\":{\"type\":\"date\"},\"properties.processing.tookMs\":{\"type\":\"long\"},\"properties.result.successful\":{\"type\":\"long\"},\"properties.result.failed\":{\"type\":\"long\"},\"properties.result.total\":{\"type\":\"long\"},\"properties.suppressionAlertsCreated\":{\"type\":\"long\"},\"properties.suppressionAlertsSuppressed\":{\"type\":\"long\"},\"properties.suppressionRuleName\":{\"type\":\"keyword\"},\"properties.suppressionDuration\":{\"type\":\"long\"},\"properties.suppressionGroupByFieldsNumber\":{\"type\":\"long\"},\"properties.suppressionRuleType\":{\"type\":\"keyword\"},\"properties.suppressionMissingFields\":{\"type\":\"boolean\"},\"properties.suppressionRuleId\":{\"type\":\"keyword\"},\"properties.responseActions.actionId\":{\"type\":\"keyword\"},\"properties.responseActions.agentType\":{\"type\":\"keyword\"},\"properties.responseActions.command\":{\"type\":\"keyword\"},\"properties.responseActions.endpointIds\":{\"type\":\"keyword\"},\"properties.responseActions.isAutomated\":{\"type\":\"boolean\"},\"properties.responseActions.actionStatus\":{\"type\":\"keyword\"}}","sourceFilters":"[]","timeFieldName":"timestamp","title":"ebt-kibana-server"},"coreMigrationVersion":"8.8.0","created_at":"2024-05-30T16:12:44.874Z","id":"security-solution-ebt-kibana-server","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-09-16T11:22:09.683Z","version":"WzQ2MDU0LDdd"} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index d7181b3ce49c60..c18fce53146066 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -6,6 +6,7 @@ */ import type { + AnalyticsServiceSetup, ElasticsearchClient, KibanaRequest, Logger, @@ -58,6 +59,7 @@ export interface EndpointAppContextServiceSetupContract { securitySolutionRequestContextFactory: IRequestContextFactory; cloud: CloudSetup; loggerFactory: LoggerFactory; + telemetry: AnalyticsServiceSetup; } export interface EndpointAppContextServiceStartContract { @@ -339,4 +341,11 @@ export class EndpointAppContextService { return this.startDependencies.createFleetActionsClient('endpoint'); } + + public getTelemetryService(): AnalyticsServiceSetup { + if (!this.setupDependencies?.telemetry) { + throw new EndpointAppContentServicesNotSetUpError(); + } + return this.setupDependencies.telemetry; + } } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts index 141a5ebb440f6d..7807dd44bddbde 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts @@ -9,6 +9,7 @@ import type { ScopedClusterClientMock } from '@kbn/core/server/mocks'; import { + analyticsServiceMock, elasticsearchServiceMock, httpServerMock, httpServiceMock, @@ -128,6 +129,7 @@ export const createMockEndpointAppContextService = ( getExceptionListsClient: jest.fn(), getMessageSigningService: jest.fn().mockReturnValue(messageSigningService), getFleetActionsClient: jest.fn(async (_) => fleetActionsClientMock), + getTelemetryService: jest.fn(), getInternalResponseActionsClient: jest.fn(() => { return responseActionsClientMock.create(); }), @@ -143,6 +145,7 @@ export const createMockEndpointAppContextServiceSetupContract = securitySolutionRequestContextFactory: requestContextFactoryMock.create(), cloud: cloudMock.createSetup(), loggerFactory: loggingSystemMock.create(), + telemetry: analyticsServiceMock.createAnalyticsServiceSetup(), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index 16c3ea8ffd427f..b6eb2376bd1cba 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -55,7 +55,10 @@ import type { ResponseActionsExecuteParameters, ResponseActionScanParameters, } from '../../../../common/endpoint/types'; -import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; +import type { + ResponseActionAgentType, + ResponseActionsApiCommandNames, +} from '../../../../common/endpoint/service/response_actions/constants'; import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, @@ -321,15 +324,12 @@ function responseActionRequestHandler { logger.debug(() => `response action [${command}]:\n${stringify(req.body)}`); + const experimentalFeatures = endpointContext.experimentalFeatures; + // Note: because our API schemas are defined as module static variables (as opposed to a // `getter` function), we need to include this additional validation here, since // `agent_type` is included in the schema independent of the feature flag - if ( - (req.body.agent_type === 'sentinel_one' && - !endpointContext.experimentalFeatures.responseActionsSentinelOneV1Enabled) || - (req.body.agent_type === 'crowdstrike' && - !endpointContext.experimentalFeatures.responseActionsCrowdstrikeManualHostIsolationEnabled) - ) { + if (isThirdPartyFeatureDisabled(req.body.agent_type, experimentalFeatures)) { return errorHandler( logger, res, @@ -354,59 +354,12 @@ function responseActionRequestHandler { + switch (command) { + case 'isolate': + return responseActionsClient.isolate(body); + case 'unisolate': + return responseActionsClient.release(body); + case 'running-processes': + return responseActionsClient.runningProcesses(body); + case 'execute': + return responseActionsClient.execute(body as ExecuteActionRequestBody); + case 'suspend-process': + return responseActionsClient.suspendProcess(body as SuspendProcessRequestBody); + case 'kill-process': + return responseActionsClient.killProcess(body as KillProcessRequestBody); + case 'get-file': + return responseActionsClient.getFile(body as ResponseActionGetFileRequestBody); + case 'upload': + return responseActionsClient.upload(body as UploadActionApiRequestBody); + case 'scan': + return responseActionsClient.scan(body as ScanActionRequestBody); + default: + throw new CustomHttpRequestError( + `No handler found for response action command: [${command}]`, + 501 + ); + } +} + function redirectHandler( location: string ): RequestHandler< diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts index 67633e3badcc97..20389d41f39564 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts @@ -43,6 +43,10 @@ import { getResponseActionFeatureKey } from '../../../feature_usage/feature_keys import { isActionSupportedByAgentType as _isActionSupportedByAgentType } from '../../../../../../common/endpoint/service/response_actions/is_response_action_supported'; import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; +import { + ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT, + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, +} from '../../../../../lib/telemetry/event_based/events'; jest.mock('../../action_details_by_id', () => { const original = jest.requireActual('../../action_details_by_id'); @@ -535,6 +539,100 @@ describe('ResponseActionsClientImpl base class', () => { }); }); }); + + describe('Telemetry', () => { + beforeEach(() => { + // @ts-expect-error + endpointAppContextService.experimentalFeatures.responseActionsTelemetryEnabled = true; + }); + + it('should send action creation success telemetry for manual actions', async () => { + await baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + + expect(endpointAppContextService.getTelemetryService().reportEvent).toHaveBeenCalledWith( + ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, + { + responseActions: { + actionId: expect.any(String), + agentType: indexDocOptions.agent_type, + command: indexDocOptions.command, + isAutomated: false, + }, + } + ); + }); + + it('should send action creation success telemetry for automated actions', async () => { + constructorOptions.isAutomated = true; + baseClassMock = new MockClassWithExposedProtectedMembers(constructorOptions); + + await baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + + expect(endpointAppContextService.getTelemetryService().reportEvent).toHaveBeenCalledWith( + ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, + { + responseActions: { + actionId: expect.any(String), + agentType: indexDocOptions.agent_type, + command: indexDocOptions.command, + isAutomated: true, + }, + } + ); + }); + + it('should send error telemetry if action creation fails', async () => { + esClient.index.mockImplementation(async () => { + throw new Error('test error'); + }); + const responsePromise = baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + await expect(responsePromise).rejects.toBeInstanceOf(ResponseActionsClientError); + + expect(endpointAppContextService.getTelemetryService().reportEvent).toHaveBeenCalledWith( + ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT.eventType, + { + responseActions: { + agentType: indexDocOptions.agent_type, + command: indexDocOptions.command, + error: 'test error', + }, + } + ); + }); + }); + + describe('Telemetry (with feature disabled)', () => { + // although this is redundant, it is here to make sure that it works as expected wit the feature disabled + beforeEach(() => { + // @ts-expect-error + endpointAppContextService.experimentalFeatures.responseActionsTelemetryEnabled = false; + }); + + it('should not send action creation success telemetry for manual actions', async () => { + await baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + + expect(endpointAppContextService.getTelemetryService().reportEvent).not.toHaveBeenCalled(); + }); + + it('should not send action creation success telemetry for automated actions', async () => { + constructorOptions.isAutomated = true; + baseClassMock = new MockClassWithExposedProtectedMembers(constructorOptions); + + await baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + + expect(endpointAppContextService.getTelemetryService().reportEvent).not.toHaveBeenCalled(); + }); + + it('should not send error telemetry if action creation fails', async () => { + esClient.index.mockImplementation(async () => { + throw new Error('test error'); + }); + const responsePromise = baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions); + await expect(responsePromise).rejects.toBeInstanceOf(ResponseActionsClientError); + + expect(endpointAppContextService.getTelemetryService().reportEvent).not.toHaveBeenCalled(); + }); + }); }); describe('#writeActionResponseToEndpointIndex()', () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts index 07ab63b77a312f..0411e4a9c8f653 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts @@ -13,6 +13,11 @@ import { AttachmentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/ import type { CaseAttachments } from '@kbn/cases-plugin/public/types'; import { i18n } from '@kbn/i18n'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, + ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT, + ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT, +} from '../../../../../lib/telemetry/event_based/events'; import { NotFoundError } from '../../../../errors'; import { fetchActionRequestById } from '../../utils/fetch_action_request_by_id'; import { SimpleMemCache } from './simple_mem_cache'; @@ -513,8 +518,12 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient ); } + this.sendActionCreationTelemetry(doc); + return doc; } catch (err) { + this.sendActionCreationErrorTelemetry(actionRequest.command, err); + if (!(err instanceof ResponseActionsClientError)) { throw new ResponseActionsClientError( `Failed to create action request document: ${err.message}`, @@ -709,6 +718,58 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient }); } + protected sendActionCreationTelemetry(actionRequest: LogsEndpointAction): void { + if (!this.options.endpointService.experimentalFeatures.responseActionsTelemetryEnabled) { + return; + } + this.options.endpointService + .getTelemetryService() + .reportEvent(ENDPOINT_RESPONSE_ACTION_SENT_EVENT.eventType, { + responseActions: { + actionId: actionRequest.EndpointActions.action_id, + agentType: this.agentType, + command: actionRequest.EndpointActions.data.command, + isAutomated: this.options.isAutomated ?? false, + }, + }); + } + + protected sendActionCreationErrorTelemetry( + command: ResponseActionsApiCommandNames, + error: Error + ): void { + if (!this.options.endpointService.experimentalFeatures.responseActionsTelemetryEnabled) { + return; + } + this.options.endpointService + .getTelemetryService() + .reportEvent(ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT.eventType, { + responseActions: { + agentType: this.agentType, + command, + error: error.message, + }, + }); + } + + protected sendActionResponseTelemetry(responseList: LogsEndpointActionResponse[]): void { + if (!this.options.endpointService.experimentalFeatures.responseActionsTelemetryEnabled) { + return; + } + for (const response of responseList) { + this.options.endpointService + .getTelemetryService() + .reportEvent(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: response.EndpointActions.action_id, + agentType: this.agentType, + actionStatus: response.error ? 'failed' : 'successful', + command: response.EndpointActions.data.command, + }, + }); + } + } + public async isolate( actionRequest: IsolationRouteRequestBody, options?: CommonResponseActionMethodOptions 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 c3adf944bc024a..b31e70b37e5607 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 @@ -51,6 +51,7 @@ 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'; jest.mock('../../action_details_by_id', () => { const originalMod = jest.requireActual('../../action_details_by_id'); @@ -803,7 +804,7 @@ describe('SentinelOneActionsClient class', () => { }); }); - it('should create response at error if request has no parentTaskId', async () => { + it('should create response as error if request has no parentTaskId', async () => { // @ts-expect-error actionRequestsSearchResponse.hits.hits[0]!._source!.meta!.parentTaskId = ''; await s1ActionsClient.processPendingActions(processPendingActionsOptions); @@ -904,6 +905,278 @@ describe('SentinelOneActionsClient class', () => { expect(processPendingActionsOptions.addToQueue).not.toHaveBeenCalled(); }); }); + + describe('Telemetry', () => { + beforeEach(() => { + // @ts-expect-error + classConstructorOptions.endpointService.experimentalFeatures.responseActionsTelemetryEnabled = + true; + }); + describe('for Isolate and Release', () => { + let s1ActivityHits: Array>; + + beforeEach(() => { + const s1DataGenerator = new SentinelOneDataGenerator('seed'); + const actionRequestsSearchResponse = s1DataGenerator.toEsSearchResponse([ + s1DataGenerator.generateActionEsHit({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { data: { command: 'isolate' } }, + meta: { + agentId: 's1-agent-a', + agentUUID: 'agent-uuid-1', + hostName: 's1-host-name', + }, + }), + ]); + const actionResponsesSearchResponse = s1DataGenerator.toEsSearchResponse< + LogsEndpointActionResponse | EndpointActionResponse + >([]); + const s1ActivitySearchResponse = s1DataGenerator.generateActivityEsSearchResponse([ + s1DataGenerator.generateActivityEsSearchHit({ + sentinel_one: { + activity: { + agent: { + id: 's1-agent-a', + }, + type: 1001, + }, + }, + }), + ]); + + s1ActivityHits = s1ActivitySearchResponse.hits.hits; + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, + response: actionResponsesSearchResponse, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: SENTINEL_ONE_ACTIVITY_INDEX_PATTERN, + response: s1ActivitySearchResponse, + }); + }); + + it('should send action response telemetry for completed/failed action', async () => { + s1ActivityHits[0]._source!.sentinel_one.activity.type = 2010; + s1ActivityHits[0]._source!.sentinel_one.activity.description.primary = + 'Agent SOME_HOST_NAME was unable to disconnect from network.'; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'failed', + agentType: 'sentinel_one', + command: 'isolate', + }, + }); + }); + + it('should send action response telemetry for completed/successful action', async () => { + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'successful', + agentType: 'sentinel_one', + command: 'isolate', + }, + }); + }); + }); + + describe('for get-file response action', () => { + let actionRequestsSearchResponse: SearchResponse< + LogsEndpointAction + >; + + beforeEach(() => { + const s1DataGenerator = new SentinelOneDataGenerator('seed'); + actionRequestsSearchResponse = s1DataGenerator.toEsSearchResponse([ + s1DataGenerator.generateActionEsHit< + ResponseActionGetFileParameters, + ResponseActionGetFileOutputContent, + SentinelOneGetFileRequestMeta + >({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { data: { command: 'get-file' } }, + meta: { + agentId: 's1-agent-a', + agentUUID: 'agent-uuid-1', + hostName: 's1-host-name', + commandBatchUuid: 'batch-111', + activityId: 'activity-222', + }, + }), + ]); + const actionResponsesSearchResponse = s1DataGenerator.toEsSearchResponse< + LogsEndpointActionResponse | EndpointActionResponse + >([]); + const s1ActivitySearchResponse = s1DataGenerator.generateActivityEsSearchResponse([ + s1DataGenerator.generateActivityEsSearchHit({ + sentinel_one: { + activity: { + id: 'activity-222', + data: s1DataGenerator.generateActivityFetchFileResponseData({ + flattened: { + commandBatchUuid: 'batch-111', + }, + }), + agent: { + id: 's1-agent-a', + }, + type: 80, + }, + }, + }), + ]); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, + response: actionResponsesSearchResponse, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: SENTINEL_ONE_ACTIVITY_INDEX_PATTERN, + response: s1ActivitySearchResponse, + }); + }); + + it('should send action response telemetry for completed/failed action', async () => { + actionRequestsSearchResponse.hits.hits[0]!._source!.meta = { + agentId: 's1-agent-a', + agentUUID: 'agent-uuid-1', + hostName: 's1-host-name', + }; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'failed', + agentType: 'sentinel_one', + command: 'get-file', + }, + }); + }); + + it('should send action response telemetry for completed/successful action', async () => { + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'successful', + agentType: 'sentinel_one', + command: 'get-file', + }, + }); + }); + }); + + describe.each` + actionName | requestData + ${'kill-process'} | ${{ command: 'kill-process', parameters: { process_name: 'foo' } }} + ${'running-processes'} | ${{ command: 'running-processes', parameters: undefined }} + `('for $actionName response action', ({ actionName, requestData }) => { + let actionRequestsSearchResponse: SearchResponse; + + beforeEach(() => { + const s1DataGenerator = new SentinelOneDataGenerator('seed'); + + actionRequestsSearchResponse = s1DataGenerator.toEsSearchResponse([ + s1DataGenerator.generateActionEsHit({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { + data: requestData, + }, + meta: { + agentId: 's1-agent-a', + agentUUID: 'agent-uuid-1', + hostName: 's1-host-name', + parentTaskId: 's1-parent-task-123', + }, + }), + ]); + const actionResponsesSearchResponse = s1DataGenerator.toEsSearchResponse< + LogsEndpointActionResponse | EndpointActionResponse + >([]); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, + response: actionResponsesSearchResponse, + }); + }); + + it('should send action response telemetry for completed/failed action', async () => { + // @ts-expect-error + actionRequestsSearchResponse.hits.hits[0]!._source!.meta!.parentTaskId = ''; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'failed', + agentType: 'sentinel_one', + command: actionName, + }, + }); + }); + + it('should send action response telemetry for completed/successful action', async () => { + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect( + classConstructorOptions.endpointService.getTelemetryService().reportEvent + ).toHaveBeenCalledWith(ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT.eventType, { + responseActions: { + actionId: expect.any(String), + actionStatus: 'successful', + agentType: 'sentinel_one', + command: actionName, + }, + }); + }); + }); + }); }); describe('#getFile()', () => { 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 ba017ea9db1c9b..b35fa0fa455e92 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 @@ -824,7 +824,7 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { return actionDetails; } - public async runningProcesses( + async runningProcesses( actionRequest: GetProcessesRequestBody, options?: CommonResponseActionMethodOptions ): Promise> { @@ -908,6 +908,8 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { const addResponsesToQueueIfAny = (responseList: LogsEndpointActionResponse[]): void => { if (responseList.length > 0) { addToQueue(...responseList); + + this.sendActionResponseTelemetry(responseList); } }; @@ -950,8 +952,6 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { ); break; - // FIXME:PT refactor kill-process entry here when that PR is merged - case 'get-file': addResponsesToQueueIfAny( await this.checkPendingGetFileActions( @@ -967,8 +967,8 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { break; case 'kill-process': - { - const responseDocsForKillProcess = await this.checkPendingKillProcessActions( + addResponsesToQueueIfAny( + await this.checkPendingKillProcessActions( typePendingActions as Array< ResponseActionsClientPendingAction< ResponseActionParametersWithProcessName, @@ -976,11 +976,8 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { SentinelOneKillProcessRequestMeta > > - ); - if (responseDocsForKillProcess.length) { - addToQueue(...responseDocsForKillProcess); - } - } + ) + ); break; } } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts index e367f012ad0bca..b8a2df85f10adf 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts @@ -5,6 +5,11 @@ * 2.0. */ import type { EventTypeOpts } from '@kbn/core/server'; +import type { + ResponseActionAgentType, + ResponseActionStatus, + ResponseActionsApiCommandNames, +} from '../../../../common/endpoint/service/response_actions/constants'; import type { BulkUpsertAssetCriticalityRecordsResponse } from '../../../../common/api/entity_analytics'; export const RISK_SCORE_EXECUTION_SUCCESS_EVENT: EventTypeOpts<{ @@ -250,10 +255,139 @@ const getUploadStatus = (stats?: BulkUpsertAssetCriticalityRecordsResponse['stat return 'fail'; }; +export const ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT: EventTypeOpts<{ + responseActions: { + agentType: ResponseActionAgentType; + command: ResponseActionsApiCommandNames; + error: string; + }; +}> = { + eventType: 'endpoint_response_action_sent_error', + schema: { + responseActions: { + properties: { + agentType: { + type: 'keyword', + _meta: { + description: 'The type of agent that the action was sent to', + optional: false, + }, + }, + command: { + type: 'keyword', + _meta: { + description: 'The command that was sent to the endpoint', + optional: false, + }, + }, + error: { + type: 'text', + _meta: { + description: 'The error message for the response action', + }, + }, + }, + }, + }, +}; + +export const ENDPOINT_RESPONSE_ACTION_SENT_EVENT: EventTypeOpts<{ + responseActions: { + actionId: string; + agentType: ResponseActionAgentType; + command: ResponseActionsApiCommandNames; + isAutomated: boolean; + }; +}> = { + eventType: 'endpoint_response_action_sent', + schema: { + responseActions: { + properties: { + actionId: { + type: 'keyword', + _meta: { + description: 'The ID of the action that was sent to the endpoint', + optional: false, + }, + }, + agentType: { + type: 'keyword', + _meta: { + description: 'The type of agent that the action was sent to', + optional: false, + }, + }, + command: { + type: 'keyword', + _meta: { + description: 'The command that was sent to the endpoint', + optional: false, + }, + }, + isAutomated: { + type: 'boolean', + _meta: { + description: 'Whether the action was auto-initiated by a pre-configured rule', + optional: false, + }, + }, + }, + }, + }, +}; + +export const ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT: EventTypeOpts<{ + responseActions: { + actionId: string; + agentType: ResponseActionAgentType; + actionStatus: ResponseActionStatus; + command: ResponseActionsApiCommandNames; + }; +}> = { + eventType: 'endpoint_response_action_status_change_event', + schema: { + responseActions: { + properties: { + actionId: { + type: 'keyword', + _meta: { + description: 'The ID of the action that was sent to the endpoint', + optional: false, + }, + }, + agentType: { + type: 'keyword', + _meta: { + description: 'The type of agent that the action was sent to', + optional: false, + }, + }, + actionStatus: { + type: 'keyword', + _meta: { + description: 'The status of the action', + optional: false, + }, + }, + command: { + type: 'keyword', + _meta: { + description: 'The command that was sent to the endpoint', + optional: false, + }, + }, + }, + }, + }, +}; + export const events = [ RISK_SCORE_EXECUTION_SUCCESS_EVENT, RISK_SCORE_EXECUTION_ERROR_EVENT, RISK_SCORE_EXECUTION_CANCELLATION_EVENT, ASSET_CRITICALITY_SYSTEM_PROCESSED_ASSIGNMENT_FILE_EVENT, ALERT_SUPPRESSION_EVENT, + ENDPOINT_RESPONSE_ACTION_SENT_EVENT, + ENDPOINT_RESPONSE_ACTION_SENT_ERROR_EVENT, + ENDPOINT_RESPONSE_ACTION_STATUS_CHANGE_EVENT, ]; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 0f442564d44873..e189f1c71a7837 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -238,6 +238,7 @@ export class Plugin implements ISecuritySolutionPlugin { securitySolutionRequestContextFactory: requestContextFactory, cloud: plugins.cloud, loggerFactory: this.pluginContext.logger, + telemetry: core.analytics, }); initUsageCollectors({