"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.TOTAL_FIELDS_LIMIT = exports.ECS_CONTEXT = exports.ECS_COMPONENT_TEMPLATE_NAME = exports.AlertsService = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _lodash = require("lodash"); var _alertsAsDataUtils = require("@kbn/alerts-as-data-utils"); var _coreSavedObjectsUtilsServer = require("@kbn/core-saved-objects-utils-server"); var _default_lifecycle_policy = require("./default_lifecycle_policy"); var _resource_installer_utils = require("./resource_installer_utils"); var _create_resource_installation_helper = require("./create_resource_installation_helper"); var _lib = require("./lib"); var _alerts_client = require("../alerts_client"); /* * 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 TOTAL_FIELDS_LIMIT = 2500; exports.TOTAL_FIELDS_LIMIT = TOTAL_FIELDS_LIMIT; const LEGACY_ALERT_CONTEXT = 'legacy-alert'; const ECS_CONTEXT = `ecs`; exports.ECS_CONTEXT = ECS_CONTEXT; const ECS_COMPONENT_TEMPLATE_NAME = (0, _resource_installer_utils.getComponentTemplateName)({ name: ECS_CONTEXT }); exports.ECS_COMPONENT_TEMPLATE_NAME = ECS_COMPONENT_TEMPLATE_NAME; class AlertsService { constructor(options) { (0, _defineProperty2.default)(this, "initialized", void 0); (0, _defineProperty2.default)(this, "isInitializing", false); (0, _defineProperty2.default)(this, "resourceInitializationHelper", void 0); (0, _defineProperty2.default)(this, "registeredContexts", new Map()); (0, _defineProperty2.default)(this, "commonInitPromise", void 0); this.options = options; this.initialized = false; // Kick off initialization of common assets and save the promise this.commonInitPromise = this.initializeCommon(this.options.timeoutMs); // Create helper for initializing context-specific resources this.resourceInitializationHelper = (0, _create_resource_installation_helper.createResourceInstallationHelper)(this.options.logger, this.commonInitPromise, this.initializeContext.bind(this)); } isInitialized() { return this.initialized; } async createAlertsClient(opts) { if (!opts.ruleType.alerts) { return null; } // Check if context specific installation has succeeded const { result: initialized, error } = await this.getContextInitializationPromise(opts.ruleType.alerts.context, opts.namespace); // If initialization failed, retry if (!initialized && error) { let initPromise; // If !this.initialized, we know that common resource initialization failed // and we need to retry this before retrying the context specific resources // However, if this.isInitializing, then the alerts service is in the process // of retrying common installation, so we don't want to kick off another retry if (!this.initialized) { if (!this.isInitializing) { this.options.logger.info(`Retrying common resource initialization`); initPromise = this.initializeCommon(this.options.timeoutMs); } else { this.options.logger.info(`Skipped retrying common resource initialization because it is already being retried.`); } } this.resourceInitializationHelper.retry(opts.ruleType.alerts, opts.namespace, initPromise); const retryResult = await this.resourceInitializationHelper.getInitializedContext(opts.ruleType.alerts.context, opts.ruleType.alerts.isSpaceAware ? opts.namespace : _coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING); if (!retryResult.result) { const errorLogPrefix = `There was an error in the framework installing namespace-level resources and creating concrete indices for context "${opts.ruleType.alerts.context}" - `; // Retry also failed this.options.logger.warn(retryResult.error === error ? `${errorLogPrefix}Retry failed with error: ${error}` : `${errorLogPrefix}Original error: ${error}; Error after retry: ${retryResult.error}`); return null; } else { this.options.logger.info(`Resource installation for "${opts.ruleType.alerts.context}" succeeded after retry`); } } // TODO - when we replace the LegacyAlertsClient, we will need to decide whether to // initialize the AlertsClient even if alert resource installation failed. That would allow // us to detect alerts and trigger notifications even if we can't persist the alerts // (partial rule failure vs failing the entire rule execution). return new _alerts_client.AlertsClient({ logger: this.options.logger, elasticsearchClientPromise: this.options.elasticsearchClientPromise, ruleType: opts.ruleType, namespace: opts.namespace, rule: opts.rule, kibanaVersion: this.options.kibanaVersion }); } async getContextInitializationPromise(context, namespace) { const registeredOpts = this.registeredContexts.has(context) ? this.registeredContexts.get(context) : null; if (!registeredOpts) { const errMsg = `Error getting initialized status for context ${context} - context has not been registered.`; this.options.logger.error(errMsg); return (0, _create_resource_installation_helper.errorResult)(errMsg); } const result = await this.resourceInitializationHelper.getInitializedContext(context, registeredOpts.isSpaceAware ? namespace : _coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING); // If the context is unrecognized and namespace is not the default, we // need to kick off resource installation and return the promise if (result.error && result.error.includes(`Unrecognized context`) && namespace !== _coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING) { this.resourceInitializationHelper.add(registeredOpts, namespace); return this.resourceInitializationHelper.getInitializedContext(context, namespace); } return result; } register(opts, timeoutMs) { const { context } = opts; // check whether this context has been registered before if (this.registeredContexts.has(context)) { const registeredOptions = this.registeredContexts.get(context); if (!(0, _lodash.isEqual)(opts, registeredOptions)) { throw new Error(`${context} has already been registered with different options`); } this.options.logger.debug(`Resources for context "${context}" have already been registered.`); return; } this.options.logger.info(`Registering resources for context "${context}".`); this.registeredContexts.set(context, opts); // When a context is registered, we install resources in the default namespace by default this.resourceInitializationHelper.add(opts, _coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING, timeoutMs); } /** * Initializes the common ES resources needed for framework alerts as data * - ILM policy - common policy shared by all AAD indices * - Component template - common mappings for fields populated and used by the framework */ async initializeCommon(timeoutMs) { this.isInitializing = true; try { this.options.logger.debug(`Initializing resources for AlertsService`); const esClient = await this.options.elasticsearchClientPromise; // Common initialization installs ILM policy and shared component templates const initFns = [() => (0, _lib.createOrUpdateIlmPolicy)({ logger: this.options.logger, esClient, name: _default_lifecycle_policy.DEFAULT_ALERTS_ILM_POLICY_NAME, policy: _default_lifecycle_policy.DEFAULT_ALERTS_ILM_POLICY }), () => (0, _lib.createOrUpdateComponentTemplate)({ logger: this.options.logger, esClient, template: (0, _resource_installer_utils.getComponentTemplate)({ fieldMap: _alertsAsDataUtils.alertFieldMap, includeSettings: true }), totalFieldsLimit: TOTAL_FIELDS_LIMIT }), () => (0, _lib.createOrUpdateComponentTemplate)({ logger: this.options.logger, esClient, template: (0, _resource_installer_utils.getComponentTemplate)({ fieldMap: _alertsAsDataUtils.legacyAlertFieldMap, name: LEGACY_ALERT_CONTEXT, includeSettings: true }), totalFieldsLimit: TOTAL_FIELDS_LIMIT }), () => (0, _lib.createOrUpdateComponentTemplate)({ logger: this.options.logger, esClient, template: (0, _resource_installer_utils.getComponentTemplate)({ fieldMap: _alertsAsDataUtils.ecsFieldMap, name: ECS_CONTEXT, includeSettings: true }), totalFieldsLimit: TOTAL_FIELDS_LIMIT })]; // Install in parallel await Promise.all(initFns.map(fn => (0, _lib.installWithTimeout)({ installFn: async () => await fn(), pluginStop$: this.options.pluginStop$, logger: this.options.logger, timeoutMs }))); this.initialized = true; this.isInitializing = false; return (0, _create_resource_installation_helper.successResult)(); } catch (err) { this.options.logger.error(`Error installing common resources for AlertsService. No additional resources will be installed and rule execution may be impacted. - ${err.message}`); this.initialized = false; this.isInitializing = false; return (0, _create_resource_installation_helper.errorResult)(err.message); } } async initializeContext({ context, mappings, useEcs, useLegacyAlerts, secondaryAlias }, namespace = _coreSavedObjectsUtilsServer.DEFAULT_NAMESPACE_STRING, timeoutMs) { const esClient = await this.options.elasticsearchClientPromise; const indexTemplateAndPattern = (0, _resource_installer_utils.getIndexTemplateAndPattern)({ context, namespace, secondaryAlias }); let initFns = []; // List of component templates to reference // Order matters in this list - templates specified last take precedence over those specified first // 1. ECS component template, if using // 2. Context specific component template, if defined during registration // 3. Legacy alert component template, if using // 4. Framework common component template, always included const componentTemplateRefs = []; // If useEcs is set to true, add the ECS component template to the references if (useEcs) { componentTemplateRefs.push((0, _resource_installer_utils.getComponentTemplateName)({ name: ECS_CONTEXT })); } // If fieldMap is not empty, create a context specific component template and add to the references if (!(0, _lodash.isEmpty)(mappings.fieldMap)) { const componentTemplate = (0, _resource_installer_utils.getComponentTemplate)({ fieldMap: mappings.fieldMap, dynamic: mappings.dynamic, context }); initFns.push(async () => await (0, _lib.createOrUpdateComponentTemplate)({ logger: this.options.logger, esClient, template: componentTemplate, totalFieldsLimit: TOTAL_FIELDS_LIMIT })); componentTemplateRefs.push(componentTemplate.name); } // If useLegacy is set to true, add the legacy alert component template to the references if (useLegacyAlerts) { componentTemplateRefs.push((0, _resource_installer_utils.getComponentTemplateName)({ name: LEGACY_ALERT_CONTEXT })); } // Add framework component template to the references componentTemplateRefs.push((0, _resource_installer_utils.getComponentTemplateName)()); // Context specific initialization installs index template and write index initFns = initFns.concat([async () => await (0, _lib.createOrUpdateIndexTemplate)({ logger: this.options.logger, esClient, template: (0, _lib.getIndexTemplate)({ componentTemplateRefs, ilmPolicyName: _default_lifecycle_policy.DEFAULT_ALERTS_ILM_POLICY_NAME, indexPatterns: indexTemplateAndPattern, kibanaVersion: this.options.kibanaVersion, namespace, totalFieldsLimit: TOTAL_FIELDS_LIMIT }) }), async () => await (0, _lib.createConcreteWriteIndex)({ logger: this.options.logger, esClient, totalFieldsLimit: TOTAL_FIELDS_LIMIT, indexPatterns: indexTemplateAndPattern })]); // We want to install these in sequence and not in parallel because // the concrete index depends on the index template which depends on // the component template. for (const fn of initFns) { await (0, _lib.installWithTimeout)({ installFn: async () => await fn(), pluginStop$: this.options.pluginStop$, logger: this.options.logger, timeoutMs }); } } } exports.AlertsService = AlertsService;