"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.UninstallTokenService = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _crypto = require("crypto"); var _lodash = require("lodash"); var _coreSavedObjectsServer = require("@kbn/core-saved-objects-server"); var _std = require("@kbn/std"); var _errors = require("../../../../common/errors"); var _constants = require("../../../constants"); var _app_context = require("../../app_context"); var _agent_policy = require("../../agent_policy"); /* * 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. */ class UninstallTokenService { constructor(esoClient) { (0, _defineProperty2.default)(this, "_soClient", void 0); (0, _defineProperty2.default)(this, "getDecryptedTokens", async options => { const tokensFinder = await this.esoClient.createPointInTimeFinderDecryptedAsInternalUser({ type: _constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, perPage: _constants.SO_SEARCH_LIMIT, ...options }); let tokenObject = []; for await (const result of tokensFinder.find()) { tokenObject = result.saved_objects; break; } tokensFinder.close(); const uninstallTokens = tokenObject.map(({ id: _id, attributes, created_at: createdAt }) => { this.assertPolicyId(attributes); this.assertToken(attributes); this.assertCreatedAt(createdAt); return { id: _id, policy_id: attributes.policy_id, token: attributes.token || attributes.token_plain, created_at: createdAt }; }); return uninstallTokens; }); this.esoClient = esoClient; } async getToken(id) { const filter = `${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:${id}"`; const uninstallTokens = await this.getDecryptedTokens({ filter }); return uninstallTokens.length === 1 ? uninstallTokens[0] : null; } async getTokenMetadata(policyIdFilter, page = 1, perPage = 20) { const includeFilter = policyIdFilter ? `.*${policyIdFilter}.*` : undefined; const tokenObjects = await this.getTokenObjectsByIncludeFilter(includeFilter); const items = tokenObjects.slice((page - 1) * perPage, page * perPage).map(({ _id, _source }) => { this.assertPolicyId(_source[_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE]); this.assertCreatedAt(_source.created_at); return { id: _id.replace(`${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:`, ''), policy_id: _source[_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id, created_at: _source.created_at }; }); return { items, total: tokenObjects.length, page, perPage }; } async getDecryptedTokensForPolicyIds(policyIds) { const tokenObjectHits = await this.getTokenObjectsByIncludeFilter(policyIds); if (tokenObjectHits.length === 0) { return []; } const filter = tokenObjectHits.map(({ _id }) => { return `${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${_id}"`; }).join(' or '); return this.getDecryptedTokens({ filter }); } async getTokenObjectsByIncludeFilter(include) { const bucketSize = 10000; const query = { type: _constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, perPage: 0, aggs: { by_policy_id: { terms: { field: `${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.policy_id`, size: bucketSize, include }, aggs: { latest: { top_hits: { size: 1, sort: [{ created_at: { order: 'desc' } }], _source: [`${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.policy_id`, 'created_at'] } } } } } }; // encrypted saved objects doesn't decrypt aggregation values so we get // the ids first from saved objects to use with encrypted saved objects const idFinder = this.soClient.createPointInTimeFinder(query); let aggResults = []; for await (const result of idFinder.find()) { var _result$aggregations, _result$aggregations2; if (!(result !== null && result !== void 0 && (_result$aggregations = result.aggregations) !== null && _result$aggregations !== void 0 && _result$aggregations.by_policy_id.buckets) || !Array.isArray(result === null || result === void 0 ? void 0 : (_result$aggregations2 = result.aggregations) === null || _result$aggregations2 === void 0 ? void 0 : _result$aggregations2.by_policy_id.buckets)) { break; } aggResults = result.aggregations.by_policy_id.buckets; break; } const getCreatedAt = soBucket => { var _soBucket$latest$hits, _soBucket$latest$hits2; return new Date((_soBucket$latest$hits = (_soBucket$latest$hits2 = soBucket.latest.hits.hits[0]._source) === null || _soBucket$latest$hits2 === void 0 ? void 0 : _soBucket$latest$hits2.created_at) !== null && _soBucket$latest$hits !== void 0 ? _soBucket$latest$hits : Date.now()).getTime(); }; // sorting and paginating buckets is done here instead of ES, // because SO query doesn't support `bucket_sort` aggResults.sort((a, b) => getCreatedAt(b) - getCreatedAt(a)); return aggResults.map(bucket => bucket.latest.hits.hits[0]); } async getHashedTokenForPolicyId(policyId) { return (await this.getHashedTokensForPolicyIds([policyId]))[policyId]; } async getHashedTokensForPolicyIds(policyIds) { const tokens = await this.getDecryptedTokensForPolicyIds(policyIds); return tokens.reduce((acc, { policy_id: policyId, token }) => { if (policyId && token) { acc[policyId] = this.hashToken(token); } return acc; }, {}); } async getAllHashedTokens() { const policyIds = await this.getAllPolicyIds(); return this.getHashedTokensForPolicyIds(policyIds); } async generateTokenForPolicyId(policyId, force = false) { return (await this.generateTokensForPolicyIds([policyId], force))[policyId]; } async generateTokensForPolicyIds(policyIds, force = false) { const { agentTamperProtectionEnabled } = _app_context.appContextService.getExperimentalFeatures(); if (!agentTamperProtectionEnabled || !policyIds.length) { return {}; } const existingTokens = force ? {} : (await this.getDecryptedTokensForPolicyIds(policyIds)).reduce((acc, { policy_id: policyId, token }) => { acc[policyId] = token; return acc; }, {}); const missingTokenPolicyIds = force ? policyIds : policyIds.filter(policyId => !existingTokens[policyId]); const newTokensMap = missingTokenPolicyIds.reduce((acc, policyId) => { const token = this.generateToken(); return { ...acc, [policyId]: token }; }, {}); await this.persistTokens(missingTokenPolicyIds, newTokensMap); if (force) { var _config$setup$agentPo, _config$setup; const config = _app_context.appContextService.getConfig(); const batchSize = (_config$setup$agentPo = config === null || config === void 0 ? void 0 : (_config$setup = config.setup) === null || _config$setup === void 0 ? void 0 : _config$setup.agentPolicySchemaUpgradeBatchSize) !== null && _config$setup$agentPo !== void 0 ? _config$setup$agentPo : 100; (0, _std.asyncForEach)((0, _lodash.chunk)(policyIds, batchSize), async policyIdsBatch => await _agent_policy.agentPolicyService.deployPolicies(this.soClient, policyIdsBatch)); } const tokensMap = { ...existingTokens, ...newTokensMap }; return Object.entries(tokensMap).reduce((acc, [policyId, token]) => { acc[policyId] = this.hashToken(token); return acc; }, {}); } async generateTokensForAllPolicies(force = false) { const policyIds = await this.getAllPolicyIds(); return this.generateTokensForPolicyIds(policyIds, force); } async encryptTokens() { if (!this.isEncryptionAvailable) { return; } const { saved_objects: unencryptedTokenObjects } = await this.soClient.find({ type: _constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, filter: `${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.token_plain:* AND (NOT ${_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.token_plain: "")` }); if (!unencryptedTokenObjects.length) { return; } const bulkUpdateObjects = []; for (const unencryptedTokenObject of unencryptedTokenObjects) { bulkUpdateObjects.push({ ...unencryptedTokenObject, attributes: { ...unencryptedTokenObject.attributes, token: unencryptedTokenObject.attributes.token_plain, token_plain: '' } }); } await this.soClient.bulkUpdate(bulkUpdateObjects); } async getPolicyIdsBatch(batchSize = _constants.SO_SEARCH_LIMIT, page = 1) { return (await _agent_policy.agentPolicyService.list(this.soClient, { page, perPage: batchSize, fields: ['id'] })).items.map(policy => policy.id); } async getAllPolicyIds() { const batchSize = _constants.SO_SEARCH_LIMIT; let policyIdsBatch = await this.getPolicyIdsBatch(batchSize); let policyIds = policyIdsBatch; let page = 2; while (policyIdsBatch.length === batchSize) { policyIdsBatch = await this.getPolicyIdsBatch(batchSize, page); policyIds = [...policyIds, ...policyIdsBatch]; page++; } return policyIds; } async persistTokens(policyIds, tokensMap) { var _config$setup$agentPo2, _config$setup2; if (!policyIds.length) { return; } const config = _app_context.appContextService.getConfig(); const batchSize = (_config$setup$agentPo2 = config === null || config === void 0 ? void 0 : (_config$setup2 = config.setup) === null || _config$setup2 === void 0 ? void 0 : _config$setup2.agentPolicySchemaUpgradeBatchSize) !== null && _config$setup$agentPo2 !== void 0 ? _config$setup$agentPo2 : 100; await (0, _std.asyncForEach)((0, _lodash.chunk)(policyIds, batchSize), async policyIdsBatch => { await this.soClient.bulkCreate(policyIdsBatch.map(policyId => ({ type: _constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, attributes: this.isEncryptionAvailable ? { policy_id: policyId, token: tokensMap[policyId] } : { policy_id: policyId, token_plain: tokensMap[policyId] } }))); }); } generateToken() { return (0, _crypto.randomBytes)(16).toString('hex'); } hashToken(token) { if (!token) { return ''; } const hash = (0, _crypto.createHash)('sha256'); hash.update(token); return hash.digest('base64'); } get soClient() { if (this._soClient) { return this._soClient; } const fakeRequest = { headers: {}, getBasePath: () => '', path: '/', route: { settings: {} }, url: { href: {} }, raw: { req: { url: '/' } } }; this._soClient = _app_context.appContextService.getSavedObjects().getScopedClient(fakeRequest, { excludedExtensions: [_coreSavedObjectsServer.SECURITY_EXTENSION_ID], includedHiddenTypes: [_constants.UNINSTALL_TOKENS_SAVED_OBJECT_TYPE] }); return this._soClient; } get isEncryptionAvailable() { var _appContextService$ge, _appContextService$ge2; return (_appContextService$ge = (_appContextService$ge2 = _app_context.appContextService.getEncryptedSavedObjectsSetup()) === null || _appContextService$ge2 === void 0 ? void 0 : _appContextService$ge2.canEncrypt) !== null && _appContextService$ge !== void 0 ? _appContextService$ge : false; } assertCreatedAt(createdAt) { if (!createdAt) { throw new _errors.UninstallTokenError('Uninstall Token is missing creation date.'); } } assertToken(attributes) { if (!(attributes !== null && attributes !== void 0 && attributes.token) && !(attributes !== null && attributes !== void 0 && attributes.token_plain)) { throw new _errors.UninstallTokenError('Uninstall Token is missing the token.'); } } assertPolicyId(attributes) { if (!(attributes !== null && attributes !== void 0 && attributes.policy_id)) { throw new _errors.UninstallTokenError('Uninstall Token is missing policy ID.'); } } } exports.UninstallTokenService = UninstallTokenService;