From ff26a8cd849aea429e154f02bb58e2fadad55095 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:46:05 -0500 Subject: [PATCH] [Security Solution][Endpoint] Delete obsolete action create service (#177621) ## Summary - Deletes the obsolete Action Create service now that all response actions usage has been moved to use the new `ResponseActionsClient`'s. --- .../common/endpoint/types/agents.ts | 1 - .../endpoint/endpoint_app_context_services.ts | 11 - .../server/endpoint/mocks.ts | 3 - .../routes/actions/response_actions.test.ts | 2 - .../endpoint/endpoint_actions_client.ts | 6 +- .../lib/base_response_actions_client.test.ts | 17 +- .../lib/base_response_actions_client.ts | 34 ++- .../services/actions/create/action_errors.ts | 44 ---- .../endpoint/services/actions/create/index.ts | 79 ------- .../endpoint/services/actions/create/types.ts | 41 ---- .../services/actions/create/validate.ts | 53 ----- .../actions/create/write_action_to_indices.ts | 213 ------------------ .../server/endpoint/services/actions/index.ts | 1 - .../server/endpoint/services/actions/mocks.ts | 12 - .../server/endpoint/services/actions/utils.ts | 12 +- .../endpoint_params_type_guards.ts | 24 -- .../rule_response_actions/types.ts | 10 +- .../rule_response_actions/utils.ts | 10 +- .../security_solution/server/plugin.ts | 6 - 19 files changed, 57 insertions(+), 522 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/create/types.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/types/agents.ts b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts index 5394df26a3bae..99ba35f504d3e 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/agents.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/agents.ts @@ -10,7 +10,6 @@ import type { ResponseActionsApiCommandNames, ResponseActionAgentType, } from '../service/response_actions/constants'; -import {} from '../service/response_actions/constants'; export interface AgentStatusInfo { id: string; 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 75b96842757fc..80f261b9aa818 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 @@ -52,7 +52,6 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { calculateEndpointAuthz } from '../../common/endpoint/service/authz'; import type { FeatureUsageService } from './services/feature_usage/service'; import type { ExperimentalFeatures } from '../../common/experimental_features'; -import type { ActionCreateService } from './services/actions/create/types'; import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service'; import type { ResponseActionAgentType } from '../../common/endpoint/service/response_actions/constants'; @@ -81,7 +80,6 @@ export interface EndpointAppContextServiceStartContract { featureUsageService: FeatureUsageService; experimentalFeatures: ExperimentalFeatures; messageSigningService: MessageSigningServiceInterface | undefined; - actionCreateService: ActionCreateService | undefined; esClient: ElasticsearchClient; productFeaturesService: ProductFeaturesService; savedObjectsClient: SavedObjectsClientContract; @@ -309,15 +307,6 @@ export class EndpointAppContextService { }); } - /** @deprecated use `getInternalResponseActionsClient()` */ - public getActionCreateService(): ActionCreateService { - if (!this.startDependencies?.actionCreateService) { - throw new EndpointAppContentServicesNotStartedError(); - } - - return this.startDependencies.actionCreateService; - } - public async getFleetToHostFilesClient() { if (!this.startDependencies?.createFleetFilesClient) { throw new EndpointAppContentServicesNotStartedError(); diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index e7014878c792e..6a543084f51d3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -46,7 +46,6 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import { casesPluginMock } from '@kbn/cases-plugin/server/mocks'; import { createCasesClientMock } from '@kbn/cases-plugin/server/client/mocks'; import type { AddVersionOpts, VersionedRouteConfig } from '@kbn/core-http-server'; -import { createActionCreateServiceMock } from './services/actions/mocks'; import { getEndpointAuthzInitialStateMock } from '../../common/endpoint/service/authz/mocks'; import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; import type { @@ -117,7 +116,6 @@ export const createMockEndpointAppContextService = ( getInternalFleetServices: jest.fn(() => mockEndpointMetadataContext.fleetServices), getEndpointAuthz: jest.fn(async (_) => getEndpointAuthzInitialStateMock()), getCasesClient: jest.fn().mockReturnValue(casesClientMock), - getActionCreateService: jest.fn().mockReturnValue(createActionCreateServiceMock()), getFleetFromHostFilesClient: jest.fn(async () => fleetFromHostFilesClientMock), getFleetToHostFilesClient: jest.fn(async () => fleetToHostFilesClientMock), setup: jest.fn(), @@ -223,7 +221,6 @@ export const createMockEndpointAppContextServiceStartContract = featureUsageService: createFeatureUsageServiceMock(), experimentalFeatures, messageSigningService: createMessageSigningServiceMock(), - actionCreateService: undefined, createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), productFeaturesService, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 87687165c3e05..d964d2bc00b70 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -61,7 +61,6 @@ import { registerResponseActionRoutes } from './response_actions'; import * as ActionDetailsService from '../../services/actions/action_details_by_id'; import { CaseStatuses } from '@kbn/cases-components'; import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks'; -import { actionCreateService } from '../../services/actions'; import { getResponseActionsClient as _getResponseActionsClient } from '../../services'; import type { UploadActionApiRequestBody } from '../../../../common/api/endpoint'; import type { FleetToHostFileClientInterface } from '@kbn/fleet-plugin/server'; @@ -159,7 +158,6 @@ describe('Response actions', () => { endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, - actionCreateService: actionCreateService(mockScopedClient.asInternalUser, endpointContext), licenseService, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts index dbef273c7c62a..c60d74b543e0c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/endpoint/endpoint_actions_client.ts @@ -7,8 +7,8 @@ import type { FleetActionRequest } from '@kbn/fleet-plugin/server/services/actions'; import { v4 as uuidv4 } from 'uuid'; +import { getActionRequestExpiration } from '../../utils'; import { ResponseActionsClientError } from '../errors'; -import { getActionRequestExpiration } from '../../create/write_action_to_indices'; import { stringify } from '../../../../utils/stringify'; import type { HapiReadableStream } from '../../../../../types'; import type { @@ -112,8 +112,8 @@ export class EndpointActionsClient extends ResponseActionsClientImpl { await this.writeActionRequestToEndpointIndex({ ...actionReq, error: actionError, - rule_id: ruleId, - rule_name: ruleName, + ruleId, + ruleName, hosts, actionId, command, 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 20e1942d16732..d3a5cb3851ac7 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 @@ -12,7 +12,7 @@ import type { ResponseActionsClientWriteActionRequestToEndpointIndexOptions, ResponseActionsClientWriteActionResponseToEndpointIndexOptions, } from './base_response_actions_client'; -import { ResponseActionsClientImpl } from './base_response_actions_client'; +import { HOST_NOT_ENROLLED, ResponseActionsClientImpl } from './base_response_actions_client'; import type { ActionDetails, LogsEndpointAction, @@ -34,7 +34,6 @@ import { set } from 'lodash'; import { responseActionsClientMock } from '../mocks'; import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; import { getResponseActionFeatureKey } from '../../../feature_usage/feature_keys'; -import { HOST_NOT_ENROLLED } from '../../create/validate'; import { isActionSupportedByAgentType as _isActionSupportedByAgentType } from '../../../../../../common/endpoint/service/response_actions/is_response_action_supported'; jest.mock('../../action_details_by_id', () => { @@ -308,8 +307,8 @@ describe('ResponseActionsClientImpl base class', () => { agent_type: 'endpoint', endpoint_ids: ['one'], comment: 'test comment', - rule_name: undefined, - rule_id: undefined, + ruleName: undefined, + ruleId: undefined, alert_ids: undefined, case_ids: undefined, hosts: undefined, @@ -390,11 +389,11 @@ describe('ResponseActionsClientImpl base class', () => { }); it('should include Rule information if rule_id and rule_name were provided', async () => { - indexDocOptions.rule_id = '1-2-3'; - indexDocOptions.rule_name = 'rule 123'; + indexDocOptions.ruleId = '1-2-3'; + indexDocOptions.ruleName = 'rule 123'; expectedIndexDoc.rule = { - name: indexDocOptions.rule_name, - id: indexDocOptions.rule_id, + name: indexDocOptions.ruleName, + id: indexDocOptions.ruleId, }; await expect( @@ -403,7 +402,7 @@ describe('ResponseActionsClientImpl base class', () => { }); it('should NOT include Rule information if rule_id or rule_name are missing', async () => { - indexDocOptions.rule_id = '1-2-3'; + indexDocOptions.ruleId = '1-2-3'; await expect( baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions) 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 a30c9ed65817f..fb61525683489 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 @@ -11,8 +11,9 @@ import type { Logger } from '@kbn/logging'; import { v4 as uuidv4 } from 'uuid'; import { AttachmentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/common'; import type { CaseAttachments } from '@kbn/cases-plugin/public/types'; +import { i18n } from '@kbn/i18n'; +import { getActionRequestExpiration } from '../../utils'; import { isActionSupportedByAgentType } from '../../../../../../common/endpoint/service/response_actions/is_response_action_supported'; -import { HOST_NOT_ENROLLED, LICENSE_TOO_LOW } from '../../create/validate'; import type { EndpointAppContextService } from '../../../../endpoint_app_context_services'; import { APP_ID } from '../../../../../../common'; import type { @@ -21,11 +22,6 @@ import type { } from '../../../../../../common/endpoint/service/response_actions/constants'; import { getActionDetailsById } from '../../action_details_by_id'; import { ResponseActionsClientError, ResponseActionsNotSupportedError } from '../errors'; -import { - addRuleInfoToAction, - getActionParameters, - getActionRequestExpiration, -} from '../../create/write_action_to_indices'; import { ENDPOINT_ACTION_RESPONSES_INDEX, ENDPOINT_ACTIONS_INDEX, @@ -58,11 +54,24 @@ import type { ResponseActionsRequestBody, UploadActionApiRequestBody, } from '../../../../../../common/api/endpoint'; -import type { CreateActionPayload } from '../../create/types'; import { stringify } from '../../../../utils/stringify'; import { CASE_ATTACHMENT_ENDPOINT_TYPE_ID } from '../../../../../../common/constants'; import { EMPTY_COMMENT } from '../../../../utils/translations'; +const ENTERPRISE_LICENSE_REQUIRED_MSG = i18n.translate( + 'xpack.securitySolution.responseActionsList.error.licenseTooLow', + { + defaultMessage: 'At least Enterprise license is required to use Response Actions.', + } +); + +export const HOST_NOT_ENROLLED = i18n.translate( + 'xpack.securitySolution.responseActionsList.error.hostNotEnrolled', + { + defaultMessage: 'The host does not have Elastic Defend integration installed', + } +); + export interface ResponseActionsClientOptions { endpointService: EndpointAppContextService; esClient: ElasticsearchClient; @@ -96,7 +105,8 @@ export interface ResponseActionsClientUpdateCasesOptions { export type ResponseActionsClientWriteActionRequestToEndpointIndexOptions = ResponseActionsRequestBody & - Pick & { + Pick & { + command: ResponseActionsApiCommandNames; actionId?: string; }; @@ -277,7 +287,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient if (!this.options.endpointService.getLicenseService().isEnterprise()) { return { isValid: false, - error: new ResponseActionsClientError(LICENSE_TOO_LOW, 403), + error: new ResponseActionsClientError(ENTERPRISE_LICENSE_REQUIRED_MSG, 403), }; } } @@ -343,14 +353,16 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient comment: actionRequest.comment ?? undefined, ...(actionRequest.alert_ids ? { alert_id: actionRequest.alert_ids } : {}), ...(actionRequest.hosts ? { hosts: actionRequest.hosts } : {}), - parameters: getActionParameters(actionRequest) as EndpointActionDataParameterTypes, + parameters: actionRequest.parameters as EndpointActionDataParameterTypes, }, }, user: { id: this.options.username, }, ...(errorMsg ? { error: { message: errorMsg } } : {}), - ...addRuleInfoToAction(actionRequest), + ...(actionRequest.ruleId && actionRequest.ruleName + ? { rule: { id: actionRequest.ruleId, name: actionRequest.ruleName } } + : {}), }; try { diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts deleted file mode 100644 index f93a6d13cd720..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/action_errors.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { LicenseType } from '@kbn/licensing-plugin/common/types'; -import type { EcsError } from '@elastic/ecs'; -import { validateAgents, validateAlertError, validateEndpointLicense } from './validate'; -import type { LicenseService } from '../../../../../common/license/license'; - -export const addErrorsToActionIfAny = ({ - agents, - licenseService, - minimumLicenseRequired = 'basic', - error, -}: { - agents: string[]; - licenseService: LicenseService; - minimumLicenseRequired: LicenseType; - error?: string; -}): - | { - error: { - code: EcsError['code']; - message: EcsError['message']; - }; - } - | undefined => { - const licenseError = validateEndpointLicense(licenseService, minimumLicenseRequired); - const agentsError = validateAgents(agents); - const actionError = validateAlertError(error); - const alertActionError = licenseError || agentsError || actionError; - - if (alertActionError) { - return { - error: { - code: '400', - message: alertActionError, - }, - }; - } -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts deleted file mode 100644 index e7a2b8a244324..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { v4 as uuidv4 } from 'uuid'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; - -import type { - ActionDetails, - EndpointActionDataParameterTypes, - HostMetadata, - EndpointActionResponseDataOutput, -} from '../../../../../common/endpoint/types'; -import type { EndpointAppContext } from '../../../types'; -import { getActionDetailsById } from '..'; -import type { ActionCreateService, CreateActionMetadata, CreateActionPayload } from './types'; -import { writeActionToIndices } from './write_action_to_indices'; -import { responseActionsWithLegacyActionProperty } from '../constants'; - -export const actionCreateService = ( - esClient: ElasticsearchClient, - endpointContext: EndpointAppContext -): ActionCreateService => { - const createAction = async < - TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, - TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes - >( - payload: CreateActionPayload, - agents: string[], - { minimumLicenseRequired = 'basic' }: CreateActionMetadata = {} - ): Promise> => { - const usageService = endpointContext.service.getFeatureUsageService(); - const featureKey = usageService.getResponseActionFeatureKey(payload.command); - - if (featureKey) { - usageService.notifyUsage(featureKey); - } - - // create an Action ID and use that to dispatch action to ES & Fleet Server - const actionID = uuidv4(); - - await writeActionToIndices({ - actionID, - agents, - esClient, - endpointContext, - minimumLicenseRequired, - payload, - }); - - const actionId = responseActionsWithLegacyActionProperty.includes(payload.command) - ? { action: actionID } - : {}; - const data = await getActionDetailsById( - esClient, - endpointContext.service.getEndpointMetadataService(), - actionID - ); - - return { - ...actionId, - ...data, - } as ActionDetails; - }; - - return { - createAction, - createActionFromAlert: async (payload) => { - const endpointData = await endpointContext.service - .getEndpointMetadataService() - .getMetadataForEndpoints(esClient, [...new Set(payload.endpoint_ids)]); - const agentIds = endpointData.map((endpoint: HostMetadata) => endpoint.elastic.agent.id); - return createAction(payload, agentIds, { minimumLicenseRequired: 'enterprise' }); - }, - }; -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/types.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/types.ts deleted file mode 100644 index 131869e5248fa..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/types.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { LicenseType } from '@kbn/licensing-plugin/server'; -import type { TypeOf } from '@kbn/config-schema'; -import type { ResponseActionBodySchema } from '../../../../../common/api/endpoint'; -import type { - ActionDetails, - EndpointActionDataParameterTypes, - EndpointActionResponseDataOutput, -} from '../../../../../common/endpoint/types'; -import type { ResponseActionsApiCommandNames } from '../../../../../common/endpoint/service/response_actions/constants'; - -export type CreateActionPayload = TypeOf & { - command: ResponseActionsApiCommandNames; - user?: { username: string } | null | undefined; - rule_id?: string; - rule_name?: string; - error?: string; - hosts?: Record; -}; - -export interface CreateActionMetadata { - minimumLicenseRequired?: LicenseType; -} - -export interface ActionCreateService { - createActionFromAlert: (payload: CreateActionPayload, agents: string[]) => Promise; - createAction: < - TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, - TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes - >( - payload: CreateActionPayload, - agents: string[], - metadata?: CreateActionMetadata - ) => Promise>; -} diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts deleted file mode 100644 index cb831021501c3..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/validate.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import type { LicenseType } from '@kbn/licensing-plugin/server'; -import type { LicenseService } from '../../../../../common/license'; - -export const validateEndpointLicense = ( - license: LicenseService, - licenseType: LicenseType -): string | undefined => { - const hasEnterpriseLicense = license.isAtLeast(licenseType); - - if (!hasEnterpriseLicense) { - return LICENSE_TOO_LOW; - } -}; - -export const validateAgents = (agents: string[]): string | undefined => { - if (!agents.length) { - return HOST_NOT_ENROLLED; - } -}; - -export const validateAlertError = (field?: string): string | undefined => { - if (field) { - return FIELD_NOT_EXIST(field); - } -}; - -export const LICENSE_TOO_LOW = i18n.translate( - 'xpack.securitySolution.responseActionsList.error.licenseTooLow', - { - defaultMessage: 'At least Enterprise license is required to use Response Actions.', - } -); - -export const HOST_NOT_ENROLLED = i18n.translate( - 'xpack.securitySolution.responseActionsList.error.hostNotEnrolled', - { - defaultMessage: 'The host does not have Elastic Defend integration installed', - } -); - -export const FIELD_NOT_EXIST = (field: string): string => - i18n.translate('xpack.securitySolution.responseActionsList.error.nonExistingFieldName', { - defaultMessage: 'The action was called with a non-existing event field name: {field}', - values: { field }, - }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts deleted file mode 100644 index 3e86e8fa9625c..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/create/write_action_to_indices.ts +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { Logger } from '@kbn/core/server'; -import moment from 'moment'; -import type { LicenseType } from '@kbn/licensing-plugin/common/types'; -import type { FleetActionRequest } from '@kbn/fleet-plugin/server/services/actions/types'; -import { isExecuteAction } from '../../../../../common/endpoint/service/response_actions/type_guards'; -import { DEFAULT_EXECUTE_ACTION_TIMEOUT } from '../../../../../common/endpoint/service/response_actions/constants'; -import { - ENDPOINT_ACTIONS_DS, - ENDPOINT_ACTIONS_INDEX, - ENDPOINT_ACTION_RESPONSES_DS, - failedFleetActionErrorCode, -} from '../../../../../common/endpoint/constants'; -import type { - EndpointAction, - LogsEndpointAction, - LogsEndpointActionResponse, - ResponseActionsExecuteParameters, -} from '../../../../../common/endpoint/types'; -import type { EndpointAppContext } from '../../../types'; -import { doLogsEndpointActionDsExists, wrapErrorIfNeeded } from '../../../utils'; -import { addErrorsToActionIfAny } from './action_errors'; -import type { CreateActionPayload } from './types'; - -export const writeActionToIndices = async ({ - actionID, - agents, - esClient, - endpointContext, - minimumLicenseRequired, - payload, -}: { - actionID: string; - agents: string[]; - esClient: ElasticsearchClient; - endpointContext: EndpointAppContext; - minimumLicenseRequired: LicenseType; - payload: CreateActionPayload; -}): Promise => { - const logger = endpointContext.logFactory.get('createResponseAction'); - const licenseService = endpointContext.service.getLicenseService(); - - const doc: LogsEndpointAction = { - '@timestamp': moment().toISOString(), - agent: { - id: payload.endpoint_ids, - }, - EndpointActions: { - action_id: actionID, - expiration: moment().add(2, 'weeks').toISOString(), - type: 'INPUT_ACTION', - input_type: 'endpoint', - data: { - command: payload.command, - comment: payload.comment ?? undefined, - ...(payload.alert_ids ? { alert_id: payload.alert_ids } : {}), - ...(payload.hosts ? { hosts: payload.hosts } : {}), - parameters: getActionParameters(payload) ?? undefined, - }, - } as Omit, - user: { - id: payload.user ? payload.user.username : 'unknown', - }, - ...addErrorsToActionIfAny({ - agents, - licenseService, - minimumLicenseRequired, - error: payload.error, - }), - ...addRuleInfoToAction(payload), - }; - - // if .logs-endpoint.actions data stream exists - // try to create action request record in .logs-endpoint.actions DS as the current user - // (from >= v7.16, use this check to ensure the current user has privileges to write to the new index) - // and allow only users with superuser privileges to write to fleet indices - const doesLogsEndpointActionsDsExist = await doLogsEndpointActionDsExists({ - esClient, - logger, - dataStreamName: ENDPOINT_ACTIONS_DS, - }); - - // if the new endpoint indices/data streams exists - // write the action request to the new endpoint index - if (doesLogsEndpointActionsDsExist) { - const logsEndpointActionsResult = await esClient.index( - { - index: ENDPOINT_ACTIONS_INDEX, - document: { - ...doc, - agent: { - id: payload.endpoint_ids, - }, - }, - refresh: 'wait_for', - }, - { meta: true } - ); - if (logsEndpointActionsResult.statusCode !== 201) { - throw new Error(logsEndpointActionsResult.body.result); - } - } - - if (!doc.error) { - // add signature to doc - const fleetActionDoc = { - ...doc.EndpointActions, - '@timestamp': doc['@timestamp'], - agents, - timeout: 300, // 5 minutes - user_id: doc.user.id, - }; - const fleetActionDocSignature = await endpointContext.service - .getMessageSigningService() - .sign(fleetActionDoc); - const signedFleetActionDoc = { - ...fleetActionDoc, - signed: { - data: fleetActionDocSignature.data.toString('base64'), - signature: fleetActionDocSignature.signature, - }, - } as unknown as FleetActionRequest; - // write actions to .fleet-actions index - try { - const fleetActionsClient = await endpointContext.service.getFleetActionsClient(); - await fleetActionsClient.create(signedFleetActionDoc); - } catch (e) { - // create entry in .logs-endpoint.action.responses-default data stream - // when writing to .fleet-actions fails - if (doesLogsEndpointActionsDsExist) { - await createFailedActionResponseEntry({ - esClient, - doc: { - '@timestamp': moment().toISOString(), - agent: doc.agent, - EndpointActions: { - action_id: doc.EndpointActions.action_id, - completed_at: moment().toISOString(), - started_at: moment().toISOString(), - data: doc.EndpointActions.data, - input_type: 'endpoint', - }, - }, - logger, - }); - } - - throw e; - } - } -}; - -const createFailedActionResponseEntry = async ({ - esClient, - doc, - logger, -}: { - esClient: ElasticsearchClient; - doc: LogsEndpointActionResponse; - logger: Logger; -}): Promise => { - try { - await esClient.index({ - index: `${ENDPOINT_ACTION_RESPONSES_DS}-default`, - document: { - ...doc, - error: { - code: failedFleetActionErrorCode, - message: 'Failed to deliver action request to fleet', - }, - }, - }); - } catch (error) { - logger.error(wrapErrorIfNeeded(error)); - } -}; - -export const addRuleInfoToAction = ( - payload: Pick -): Pick => { - if (payload.rule_id && payload.rule_name) { - return { rule: { id: payload.rule_id, name: payload.rule_name } }; - } - - return {}; -}; - -export const getActionParameters = ( - action: Pick -): ResponseActionsExecuteParameters | Readonly<{}> | undefined => { - // set timeout to 4h (if not specified or when timeout is specified as 0) when command is `execute` - if (isExecuteAction(action)) { - const actionRequestParams = action.parameters; - if (typeof actionRequestParams?.timeout === 'undefined') { - return { ...actionRequestParams, timeout: DEFAULT_EXECUTE_ACTION_TIMEOUT }; - } - return actionRequestParams; - } - - // for all other commands return the parameters as is - return action.parameters ?? undefined; -}; - -export const getActionRequestExpiration = (): string => { - return moment().add(2, 'weeks').toISOString(); -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/index.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/index.ts index 49f8a89944d37..e9f0ed89eb2ac 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/index.ts @@ -10,5 +10,4 @@ export { getActionDetailsById } from './action_details_by_id'; export { getActionList, getActionListByStatus } from './action_list'; export { getPendingActionsSummary } from './pending_actions_summary'; export { validateActionId } from './validate_action_id'; -export * from './create'; export * from './clients'; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts index 0c5ac6cf38b38..d15fb6ad2fe3d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts @@ -23,7 +23,6 @@ import { ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, ENDPOINT_ACTIONS_INDEX, } from '../../../../common/endpoint/constants'; -import type { actionCreateService } from '..'; export const createActionRequestsEsSearchResultsMock = ( agentIds?: string[], @@ -241,14 +240,3 @@ export const createHapiReadableStreamMock = (): HapiReadableStream => { return readable; }; - -export const createActionCreateServiceMock = (): jest.Mocked< - ReturnType -> => { - const createdActionMock = new EndpointActionGenerator('seed').generateActionDetails(); - - return { - createAction: jest.fn().mockResolvedValue(createdActionMock), - createActionFromAlert: jest.fn().mockResolvedValue(createdActionMock), - }; -}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts index cd75c8622a394..df07a46fb14f8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts @@ -8,13 +8,14 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EcsError } from '@elastic/ecs'; +import moment from 'moment/moment'; import type { - ResponseActionsApiCommandNames, ResponseActionAgentType, + ResponseActionsApiCommandNames, } from '../../../../common/endpoint/service/response_actions/constants'; import { - ENDPOINT_ACTIONS_DS, ENDPOINT_ACTION_RESPONSES_DS, + ENDPOINT_ACTIONS_DS, failedFleetActionErrorCode, } from '../../../../common/endpoint/constants'; import type { @@ -25,15 +26,16 @@ import type { EndpointAction, EndpointActionDataParameterTypes, EndpointActionResponse, + EndpointActionResponseDataOutput, EndpointActivityLogAction, EndpointActivityLogActionResponse, LogsEndpointAction, LogsEndpointActionResponse, - EndpointActionResponseDataOutput, WithAllKeys, } from '../../../../common/endpoint/types'; import { ActivityLogItemTypes } from '../../../../common/endpoint/types'; import type { EndpointMetadataService } from '../metadata'; + /** * Type guard to check if a given Action is in the shape of the Endpoint Action. * @param item @@ -592,3 +594,7 @@ export const createActionDetailsRecord = { + return moment().add(2, 'weeks').toISOString(); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts deleted file mode 100644 index d1dbb9c7d4958..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/endpoint_params_type_guards.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - DefaultParams, - ProcessesParams, - RuleResponseEndpointAction, -} from '../../../../common/api/detection_engine'; - -export const isIsolateAction = ( - params: RuleResponseEndpointAction['params'] -): params is DefaultParams => { - return params.command === 'isolate'; -}; - -export const isProcessesAction = ( - params: RuleResponseEndpointAction['params'] -): params is ProcessesParams => { - return params.command === 'kill-process' || params.command === 'suspend-process'; -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts index 1ab3b8018bf1d..9d8f4b0246129 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts @@ -6,7 +6,8 @@ */ import type { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; -import type { CreateActionPayload } from '../../../endpoint/services/actions/create/types'; +import type { ResponseActionsRequestBody } from '../../../../common/api/endpoint'; +import type { CommonResponseActionMethodOptions } from '../../../endpoint/services'; export type Alert = ParsedTechnicalFields & { _id: string; @@ -33,6 +34,7 @@ export interface ResponseActionAlerts { } export type AlertsAction = Pick< - CreateActionPayload, - 'alert_ids' | 'endpoint_ids' | 'hosts' | 'parameters' | 'error' ->; + ResponseActionsRequestBody, + 'alert_ids' | 'endpoint_ids' | 'parameters' +> & + Pick; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts index 3fbd04daebf58..0cf22eca8e20c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/utils.ts @@ -6,14 +6,20 @@ */ import { get } from 'lodash'; -import { FIELD_NOT_EXIST } from '../../../endpoint/services/actions/create/validate'; -import type { AlertAgent, AlertWithAgent, AlertsAction } from './types'; +import { i18n } from '@kbn/i18n'; +import type { AlertAgent, AlertsAction, AlertWithAgent } from './types'; import type { ProcessesParams } from '../../../../common/api/detection_engine'; interface ProcessAlertsAcc { [key: string]: Record; } +export const FIELD_NOT_EXIST = (field: string): string => + i18n.translate('xpack.securitySolution.responseActionsList.error.nonExistingFieldName', { + defaultMessage: 'The action was called with a non-existing event field name: {field}', + values: { field }, + }); + export const getProcessAlerts = ( alerts: AlertWithAgent[], config: ProcessesParams['config'] diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index f769daba26033..575ee09ee4196 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -109,7 +109,6 @@ import type { } from './plugin_contract'; import { EndpointFleetServicesFactory } from './endpoint/services/fleet'; import { featureUsageService } from './endpoint/services/feature_usage'; -import { actionCreateService } from './endpoint/services/actions'; import { setIsElasticCloudDeployment } from './lib/telemetry/helpers'; import { artifactService } from './lib/telemetry/artifact'; import { events } from './lib/telemetry/event_based/events'; @@ -632,11 +631,6 @@ export class Plugin implements ISecuritySolutionPlugin { featureUsageService, experimentalFeatures: config.experimentalFeatures, messageSigningService: plugins.fleet?.messageSigningService, - // TODO:PT remove this and use new method of EndpointAppServices - actionCreateService: actionCreateService( - core.elasticsearch.client.asInternalUser, - this.endpointContext - ), createFleetActionsClient, esClient: core.elasticsearch.client.asInternalUser, productFeaturesService,