"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ActionExecutor = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _lodash = require("lodash"); var _apmUtils = require("@kbn/apm-utils"); var _server = require("@kbn/event-log-plugin/server"); var _validate_with_schema = require("./validate_with_schema"); var _event_log = require("../constants/event_log"); var _create_action_event_log_record_object = require("./create_action_event_log_record_object"); var _action_execution_error = require("./errors/action_execution_error"); /* * 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. */ // 1,000,000 nanoseconds in 1 millisecond const Millis2Nanos = 1000 * 1000; class ActionExecutor { constructor({ isESOCanEncrypt }) { (0, _defineProperty2.default)(this, "isInitialized", false); (0, _defineProperty2.default)(this, "actionExecutorContext", void 0); (0, _defineProperty2.default)(this, "isESOCanEncrypt", void 0); (0, _defineProperty2.default)(this, "actionInfo", void 0); this.isESOCanEncrypt = isESOCanEncrypt; } initialize(actionExecutorContext) { if (this.isInitialized) { throw new Error('ActionExecutor already initialized'); } this.isInitialized = true; this.actionExecutorContext = actionExecutorContext; } async execute({ actionId, params, request, source, isEphemeral, taskInfo, actionInfo: actionInfoFromTaskRunner, executionId, consumer, relatedSavedObjects, actionExecutionId }) { if (!this.isInitialized) { throw new Error('ActionExecutor not initialized'); } return (0, _apmUtils.withSpan)({ name: `execute_action`, type: 'actions', labels: { actions_connector_id: actionId } }, async span => { const { spaces, getServices, actionTypeRegistry, eventLogger, security, getActionsAuthorizationWithRequest } = this.actionExecutorContext; const services = getServices(request); const spaceId = spaces && spaces.getSpaceId(request); const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; const authorization = getActionsAuthorizationWithRequest(request); const actionInfo = actionInfoFromTaskRunner || (await this.getActionInfoInternal(actionId, request, namespace.namespace)); const { actionTypeId, name, config, secrets } = actionInfo; if (!this.actionInfo || this.actionInfo.actionId !== actionId) { this.actionInfo = actionInfo; } if (!actionTypeRegistry.isActionExecutable(actionId, actionTypeId, { notifyUsage: true })) { actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); } const actionType = actionTypeRegistry.get(actionTypeId); const configurationUtilities = actionTypeRegistry.getUtils(); let validatedParams; let validatedConfig; let validatedSecrets; try { const validationResult = validateAction({ actionId, actionType, params, config, secrets, taskInfo }, { configurationUtilities }); validatedParams = validationResult.validatedParams; validatedConfig = validationResult.validatedConfig; validatedSecrets = validationResult.validatedSecrets; } catch (err) { return err.result; } const loggerId = actionTypeId.startsWith('.') ? actionTypeId.substring(1) : actionTypeId; let { logger } = this.actionExecutorContext; logger = logger.get(loggerId); if (span) { span.name = `execute_action ${actionTypeId}`; span.addLabels({ actions_connector_type_id: actionTypeId }); } const actionLabel = `${actionTypeId}:${actionId}: ${name}`; logger.debug(`executing action ${actionLabel}`); const task = taskInfo ? { task: { scheduled: taskInfo.scheduled.toISOString(), scheduleDelay: Millis2Nanos * (Date.now() - taskInfo.scheduled.getTime()) } } : {}; const event = (0, _create_action_event_log_record_object.createActionEventLogRecordObject)({ actionId, action: _event_log.EVENT_LOG_ACTIONS.execute, consumer, ...namespace, ...task, executionId, spaceId, savedObjects: [{ type: 'action', id: actionId, typeId: actionTypeId, relation: _server.SAVED_OBJECT_REL_PRIMARY }], relatedSavedObjects, name, actionExecutionId, isInMemory: this.actionInfo.isInMemory, ...(source ? { source } : {}) }); eventLogger.startTiming(event); const startEvent = (0, _lodash.cloneDeep)({ ...event, event: { ...event.event, action: _event_log.EVENT_LOG_ACTIONS.executeStart }, message: `action started: ${actionLabel}` }); eventLogger.logEvent(startEvent); let rawResult; try { /** * Ensures correct permissions for execution and * performs authorization checks for system actions. * It will thrown an error in case of failure. */ await ensureAuthorizedToExecute({ params, actionId, actionTypeId, actionTypeRegistry, authorization }); rawResult = await actionType.executor({ actionId, services, params: validatedParams, config: validatedConfig, secrets: validatedSecrets, isEphemeral, taskInfo, configurationUtilities, logger, source }); } catch (err) { if (err.reason === _action_execution_error.ActionExecutionErrorReason.Validation || err.reason === _action_execution_error.ActionExecutionErrorReason.Authorization) { rawResult = err.result; } else { rawResult = { actionId, status: 'error', message: 'an error occurred while running the action', serviceMessage: err.message, error: err, retry: true }; } } eventLogger.stopTiming(event); // allow null-ish return to indicate success const result = rawResult || { actionId, status: 'ok' }; event.event = event.event || {}; // start gen_ai extension // add event.kibana.action.execution.gen_ai to event log when GenerativeAi Connector is executed if (result.status === 'ok' && actionTypeId === '.gen-ai') { var _data$usage, _data$usage2, _data$usage3; const data = result.data; event.kibana = event.kibana || {}; event.kibana.action = event.kibana.action || {}; event.kibana = { ...event.kibana, action: { ...event.kibana.action, execution: { ...event.kibana.action.execution, gen_ai: { usage: { total_tokens: (_data$usage = data.usage) === null || _data$usage === void 0 ? void 0 : _data$usage.total_tokens, prompt_tokens: (_data$usage2 = data.usage) === null || _data$usage2 === void 0 ? void 0 : _data$usage2.prompt_tokens, completion_tokens: (_data$usage3 = data.usage) === null || _data$usage3 === void 0 ? void 0 : _data$usage3.completion_tokens } } } } }; } // end gen_ai extension const currentUser = security === null || security === void 0 ? void 0 : security.authc.getCurrentUser(request); event.user = event.user || {}; event.user.name = currentUser === null || currentUser === void 0 ? void 0 : currentUser.username; event.user.id = currentUser === null || currentUser === void 0 ? void 0 : currentUser.profile_uid; if (result.status === 'ok') { span === null || span === void 0 ? void 0 : span.setOutcome('success'); event.event.outcome = 'success'; event.message = `action executed: ${actionLabel}`; } else if (result.status === 'error') { span === null || span === void 0 ? void 0 : span.setOutcome('failure'); event.event.outcome = 'failure'; event.message = `action execution failure: ${actionLabel}`; event.error = event.error || {}; event.error.message = actionErrorToMessage(result); if (result.error) { logger.error(result.error, { tags: [actionTypeId, actionId, 'action-run-failed'], error: { stack_trace: result.error.stack } }); } logger.warn(`action execution failure: ${actionLabel}: ${event.error.message}`); } else { span === null || span === void 0 ? void 0 : span.setOutcome('failure'); event.event.outcome = 'failure'; event.message = `action execution returned unexpected result: ${actionLabel}: "${result.status}"`; event.error = event.error || {}; event.error.message = 'action execution returned unexpected result'; logger.warn(`action execution failure: ${actionLabel}: returned unexpected result "${result.status}"`); } eventLogger.logEvent(event); const { error, ...resultWithoutError } = result; return resultWithoutError; }); } async logCancellation({ actionId, request, relatedSavedObjects, source, executionId, taskInfo, consumer, actionExecutionId }) { var _this$actionInfo$name; const { spaces, eventLogger } = this.actionExecutorContext; const spaceId = spaces && spaces.getSpaceId(request); const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; if (!this.actionInfo || this.actionInfo.actionId !== actionId) { this.actionInfo = await this.getActionInfoInternal(actionId, request, namespace.namespace); } const task = taskInfo ? { task: { scheduled: taskInfo.scheduled.toISOString(), scheduleDelay: Millis2Nanos * (Date.now() - taskInfo.scheduled.getTime()) } } : {}; // Write event log entry const event = (0, _create_action_event_log_record_object.createActionEventLogRecordObject)({ actionId, consumer, action: _event_log.EVENT_LOG_ACTIONS.executeTimeout, message: `action: ${this.actionInfo.actionTypeId}:${actionId}: '${(_this$actionInfo$name = this.actionInfo.name) !== null && _this$actionInfo$name !== void 0 ? _this$actionInfo$name : ''}' execution cancelled due to timeout - exceeded default timeout of "5m"`, ...namespace, ...task, executionId, spaceId, savedObjects: [{ type: 'action', id: actionId, typeId: this.actionInfo.actionTypeId, relation: _server.SAVED_OBJECT_REL_PRIMARY }], relatedSavedObjects, actionExecutionId, isInMemory: this.actionInfo.isInMemory, ...(source ? { source } : {}) }); eventLogger.logEvent(event); } async getActionInfoInternal(actionId, request, namespace) { const { encryptedSavedObjectsClient, inMemoryConnectors } = this.actionExecutorContext; // check to see if it's in memory action first const inMemoryAction = inMemoryConnectors.find(inMemoryConnector => inMemoryConnector.id === actionId); if (inMemoryAction) { return { actionTypeId: inMemoryAction.actionTypeId, name: inMemoryAction.name, config: inMemoryAction.config, secrets: inMemoryAction.secrets, actionId, isInMemory: true, rawAction: { ...inMemoryAction, isMissingSecrets: false } }; } if (!this.isESOCanEncrypt) { throw new Error(`Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`); } const rawAction = await encryptedSavedObjectsClient.getDecryptedAsInternalUser('action', actionId, { namespace: namespace === 'default' ? undefined : namespace }); const { attributes: { secrets, actionTypeId, config, name } } = rawAction; return { actionTypeId, name, config, secrets, actionId, rawAction: rawAction.attributes }; } } exports.ActionExecutor = ActionExecutor; function actionErrorToMessage(result) { let message = result.message || 'unknown error running action'; if (result.serviceMessage) { message = `${message}: ${result.serviceMessage}`; } if (result.retry instanceof Date) { message = `${message}; retry at ${result.retry.toISOString()}`; } else if (result.retry) { message = `${message}; retry: ${JSON.stringify(result.retry)}`; } return message; } function validateAction({ actionId, actionType, params, config, secrets, taskInfo }, validatorServices) { let validatedParams; let validatedConfig; let validatedSecrets; try { var _actionType$validate; validatedParams = (0, _validate_with_schema.validateParams)(actionType, params, validatorServices); validatedConfig = (0, _validate_with_schema.validateConfig)(actionType, config, validatorServices); validatedSecrets = (0, _validate_with_schema.validateSecrets)(actionType, secrets, validatorServices); if ((_actionType$validate = actionType.validate) !== null && _actionType$validate !== void 0 && _actionType$validate.connector) { (0, _validate_with_schema.validateConnector)(actionType, { config, secrets }); } return { validatedParams, validatedConfig, validatedSecrets }; } catch (err) { throw new _action_execution_error.ActionExecutionError(err.message, _action_execution_error.ActionExecutionErrorReason.Validation, { actionId, status: 'error', message: err.message, retry: !!taskInfo }); } } const ensureAuthorizedToExecute = async ({ actionId, actionTypeId, params, actionTypeRegistry, authorization }) => { try { if (actionTypeRegistry.isSystemActionType(actionTypeId)) { const additionalPrivileges = actionTypeRegistry.getSystemActionKibanaPrivileges(actionTypeId, params); await authorization.ensureAuthorized({ operation: 'execute', additionalPrivileges }); } } catch (error) { throw new _action_execution_error.ActionExecutionError(error.message, _action_execution_error.ActionExecutionErrorReason.Authorization, { actionId, status: 'error', message: error.message, retry: false }); } };