"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.NO_EXPIRATION = void 0; exports.bulkCreateAgentActionResults = bulkCreateAgentActionResults; exports.bulkCreateAgentActions = bulkCreateAgentActions; exports.cancelAgentAction = cancelAgentAction; exports.createAgentAction = createAgentAction; exports.createErrorActionResults = createErrorActionResults; exports.getAgentActions = getAgentActions; exports.getAgentsByActionsIds = void 0; exports.getUnenrollAgentActions = getUnenrollAgentActions; var _uuid = require("uuid"); var _elasticApmNode = _interopRequireDefault(require("elastic-apm-node")); var _app_context = require("../app_context"); var _constants = require("../../../common/constants"); var _errors = require("../../errors"); var _audit_logging = require("../audit_logging"); var _crud = require("./crud"); /* * 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. */ const ONE_MONTH_IN_MS = 2592000000; const NO_EXPIRATION = 'NONE'; exports.NO_EXPIRATION = NO_EXPIRATION; const SIGNED_ACTIONS = new Set(['UNENROLL', 'UPGRADE']); async function createAgentAction(esClient, newAgentAction) { var _newAgentAction$id, _newAgentAction$expir; const actionId = (_newAgentAction$id = newAgentAction.id) !== null && _newAgentAction$id !== void 0 ? _newAgentAction$id : (0, _uuid.v4)(); const timestamp = new Date().toISOString(); const body = { '@timestamp': timestamp, expiration: newAgentAction.expiration === NO_EXPIRATION ? undefined : (_newAgentAction$expir = newAgentAction.expiration) !== null && _newAgentAction$expir !== void 0 ? _newAgentAction$expir : new Date(Date.now() + ONE_MONTH_IN_MS).toISOString(), agents: newAgentAction.agents, action_id: actionId, data: newAgentAction.data, type: newAgentAction.type, start_time: newAgentAction.start_time, minimum_execution_duration: newAgentAction.minimum_execution_duration, rollout_duration_seconds: newAgentAction.rollout_duration_seconds, total: newAgentAction.total, traceparent: _elasticApmNode.default.currentTraceparent }; const messageSigningService = _app_context.appContextService.getMessageSigningService(); if (SIGNED_ACTIONS.has(newAgentAction.type) && messageSigningService) { const signedBody = await messageSigningService.sign(body); body.signed = { data: signedBody.data.toString('base64'), signature: signedBody.signature }; } await esClient.create({ index: _constants.AGENT_ACTIONS_INDEX, id: (0, _uuid.v4)(), body, refresh: 'wait_for' }); _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User created Fleet action [id=${actionId}]` }); return { id: actionId, ...newAgentAction, created_at: timestamp }; } async function bulkCreateAgentActions(esClient, newAgentActions) { const actions = newAgentActions.map(newAgentAction => { var _newAgentAction$id2; const id = (_newAgentAction$id2 = newAgentAction.id) !== null && _newAgentAction$id2 !== void 0 ? _newAgentAction$id2 : (0, _uuid.v4)(); return { id, ...newAgentAction }; }); if (actions.length === 0) { return []; } const messageSigningService = _app_context.appContextService.getMessageSigningService(); await esClient.bulk({ index: _constants.AGENT_ACTIONS_INDEX, body: await Promise.all(actions.flatMap(async action => { var _action$expiration; const body = { '@timestamp': new Date().toISOString(), expiration: (_action$expiration = action.expiration) !== null && _action$expiration !== void 0 ? _action$expiration : new Date(Date.now() + ONE_MONTH_IN_MS).toISOString(), start_time: action.start_time, rollout_duration_seconds: action.rollout_duration_seconds, agents: action.agents, action_id: action.id, data: action.data, type: action.type, traceparent: _elasticApmNode.default.currentTraceparent }; if (SIGNED_ACTIONS.has(action.type) && messageSigningService) { const signedBody = await messageSigningService.sign(body); body.signed = { data: signedBody.data.toString('base64'), signature: signedBody.signature }; } return [{ create: { _id: action.id } }, body]; })) }); for (const action of actions) { _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User created Fleet action [id=${action.id}]` }); } return actions; } async function createErrorActionResults(esClient, actionId, errors, errorMessage) { const errorCount = Object.keys(errors).length; if (errorCount > 0) { _app_context.appContextService.getLogger().info(`Writing error action results of ${errorCount} agents. Possibly failed validation: ${errorMessage}.`); // writing out error result for those agents that have errors, so the action is not going to stay in progress forever await bulkCreateAgentActionResults(esClient, Object.keys(errors).map(agentId => ({ agentId, actionId, error: errors[agentId].message }))); } } async function bulkCreateAgentActionResults(esClient, results) { if (results.length === 0) { return; } const bulkBody = results.flatMap(result => { const body = { '@timestamp': new Date().toISOString(), action_id: result.actionId, agent_id: result.agentId, error: result.error }; return [{ create: { _id: (0, _uuid.v4)() } }, body]; }); for (const result of results) { _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User created Fleet action result [id=${result.actionId}]` }); } await esClient.bulk({ index: _constants.AGENT_ACTIONS_RESULTS_INDEX, body: bulkBody, refresh: 'wait_for' }); } async function getAgentActions(esClient, actionId) { const res = await esClient.search({ index: _constants.AGENT_ACTIONS_INDEX, query: { bool: { must: [{ term: { action_id: actionId } }] } }, size: _constants.SO_SEARCH_LIMIT }); if (res.hits.hits.length === 0) { throw new _errors.AgentActionNotFoundError('Action not found'); } const result = []; for (const hit of res.hits.hits) { var _hit$_source; _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User retrieved Fleet action [id=${(_hit$_source = hit._source) === null || _hit$_source === void 0 ? void 0 : _hit$_source.action_id}]` }); result.push({ ...hit._source, id: hit._id }); } return result; } async function getUnenrollAgentActions(esClient) { const res = await esClient.search({ index: _constants.AGENT_ACTIONS_INDEX, query: { bool: { must: [{ term: { type: 'UNENROLL' } }, { exists: { field: 'agents' } }, { range: { expiration: { gte: new Date().toISOString() } } }] } }, size: _constants.SO_SEARCH_LIMIT }); const result = []; for (const hit of res.hits.hits) { var _hit$_source2; _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User retrieved Fleet action [id=${(_hit$_source2 = hit._source) === null || _hit$_source2 === void 0 ? void 0 : _hit$_source2.action_id}]` }); result.push({ ...hit._source, id: hit._id }); } return result; } async function cancelAgentAction(esClient, actionId) { const getUpgradeActions = async () => { const res = await esClient.search({ index: _constants.AGENT_ACTIONS_INDEX, query: { bool: { filter: [{ term: { action_id: actionId } }] } }, size: _constants.SO_SEARCH_LIMIT }); if (res.hits.hits.length === 0) { throw new _errors.AgentActionNotFoundError('Action not found'); } for (const hit of res.hits.hits) { var _hit$_source3; _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User retrieved Fleet action [id=${(_hit$_source3 = hit._source) === null || _hit$_source3 === void 0 ? void 0 : _hit$_source3.action_id}]}]` }); } const upgradeActions = res.hits.hits.map(hit => hit._source).filter(action => !!action && !!action.agents && !!action.action_id && action.type === 'UPGRADE'); return upgradeActions; }; const cancelActionId = (0, _uuid.v4)(); const now = new Date().toISOString(); const cancelledActions = []; const createAction = async action => { await createAgentAction(esClient, { id: cancelActionId, type: 'CANCEL', agents: action.agents, data: { target_id: action.action_id }, created_at: now, expiration: action.expiration }); cancelledActions.push({ agents: action.agents }); }; let upgradeActions = await getUpgradeActions(); for (const action of upgradeActions) { await createAction(action); } const updateAgentsToHealthy = async action => { _app_context.appContextService.getLogger().info(`Moving back ${action.agents.length} agents from updating to healthy state after cancel upgrade`); const errors = {}; await (0, _crud.bulkUpdateAgents)(esClient, action.agents.map(agentId => ({ agentId, data: { upgraded_at: null, upgrade_started_at: null } })), errors); if (Object.keys(errors).length > 0) { _app_context.appContextService.getLogger().info(`Errors while bulk updating agents for cancel action: ${JSON.stringify(errors)}`); } }; for (const action of upgradeActions) { await updateAgentsToHealthy(action); } // At the end of cancel, doing one more query on upgrade action to find those docs that were possibly created by a concurrent upgrade action. // This is to make sure we cancel all upgrade batches. upgradeActions = await getUpgradeActions(); if (cancelledActions.length < upgradeActions.length) { const missingBatches = upgradeActions.filter(upgradeAction => !cancelledActions.some(cancelled => upgradeAction.agents && cancelled.agents[0] === upgradeAction.agents[0])); _app_context.appContextService.getLogger().debug(`missing batches to cancel: ${missingBatches.length}`); if (missingBatches.length > 0) { for (const missingBatch of missingBatches) { await createAction(missingBatch); await updateAgentsToHealthy(missingBatch); } } } return { created_at: now, id: cancelActionId, type: 'CANCEL' }; } async function getAgentActionsByIds(esClient, actionIds) { const res = await esClient.search({ index: _constants.AGENT_ACTIONS_INDEX, query: { bool: { filter: [{ terms: { action_id: actionIds } }] } }, size: _constants.SO_SEARCH_LIMIT }); if (res.hits.hits.length === 0) { throw new _errors.AgentActionNotFoundError('Action not found'); } const result = []; for (const hit of res.hits.hits) { var _hit$_source4; _audit_logging.auditLoggingService.writeCustomAuditLog({ message: `User retrieved Fleet action [id=${(_hit$_source4 = hit._source) === null || _hit$_source4 === void 0 ? void 0 : _hit$_source4.action_id}]` }); result.push({ ...hit._source, id: hit._id }); } return result; } const getAgentsByActionsIds = async (esClient, actionsIds) => { const actions = await getAgentActionsByIds(esClient, actionsIds); return actions.flatMap(a => a === null || a === void 0 ? void 0 : a.agents).filter(agent => !!agent); }; exports.getAgentsByActionsIds = getAgentsByActionsIds;