"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.configSchema = exports.config = exports.ElasticsearchConfig = exports.DEFAULT_API_VERSION = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _configSchema = require("@kbn/config-schema");
var _crypto = require("@kbn/crypto");
var _i18n = require("@kbn/i18n");
var _fs = require("fs");
var _default_headers = require("./default_headers");
/*
 * 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 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

const hostURISchema = _configSchema.schema.uri({
  scheme: ['http', 'https']
});
const DEFAULT_API_VERSION = 'master';
exports.DEFAULT_API_VERSION = DEFAULT_API_VERSION;
/**
 * Validation schema for elasticsearch service config. It can be reused when plugins allow users
 * to specify a local elasticsearch config.
 * @public
 */
const configSchema = _configSchema.schema.object({
  sniffOnStart: _configSchema.schema.boolean({
    defaultValue: false
  }),
  sniffInterval: _configSchema.schema.oneOf([_configSchema.schema.duration(), _configSchema.schema.literal(false)], {
    defaultValue: false
  }),
  sniffOnConnectionFault: _configSchema.schema.boolean({
    defaultValue: false
  }),
  hosts: _configSchema.schema.oneOf([hostURISchema, _configSchema.schema.arrayOf(hostURISchema, {
    minSize: 1
  })], {
    defaultValue: 'http://localhost:9200'
  }),
  maxSockets: _configSchema.schema.number({
    defaultValue: Infinity,
    min: 1
  }),
  maxIdleSockets: _configSchema.schema.number({
    defaultValue: 256,
    min: 1
  }),
  idleSocketTimeout: _configSchema.schema.duration({
    defaultValue: '60s'
  }),
  compression: _configSchema.schema.boolean({
    defaultValue: false
  }),
  username: _configSchema.schema.maybe(_configSchema.schema.string({
    validate: rawConfig => {
      if (rawConfig === 'elastic') {
        return 'value of "elastic" is forbidden. This is a superuser account that cannot write to system indices that Kibana needs to ' + 'function. Use a service account token instead. Learn more: ' + 'https://www.elastic.co/guide/en/elasticsearch/reference/8.0/service-accounts.html' // we don't have a way to pass a branch into the config schema; hardcoding this one link to the 8.0 docs is OK
        ;
      }
    }
  })),

  password: _configSchema.schema.maybe(_configSchema.schema.string()),
  serviceAccountToken: _configSchema.schema.maybe(_configSchema.schema.conditional(_configSchema.schema.siblingRef('username'), _configSchema.schema.never(), _configSchema.schema.string(), _configSchema.schema.string({
    validate: () => {
      return `serviceAccountToken cannot be specified when "username" is also set.`;
    }
  }))),
  requestHeadersWhitelist: _configSchema.schema.oneOf([_configSchema.schema.string({
    // can't use `validate` option on union types, forced to validate each individual subtypes
    // see https://github.com/elastic/kibana/issues/64906
    validate: headersWhitelist => {
      const reservedHeaders = (0, _default_headers.getReservedHeaders)([headersWhitelist]);
      if (reservedHeaders.length) {
        return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
      }
    }
  }), _configSchema.schema.arrayOf(_configSchema.schema.string(), {
    // can't use `validate` option on union types, forced to validate each individual subtypes
    // see https://github.com/elastic/kibana/issues/64906
    validate: headersWhitelist => {
      const reservedHeaders = (0, _default_headers.getReservedHeaders)(headersWhitelist);
      if (reservedHeaders.length) {
        return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
      }
    }
  })], {
    defaultValue: ['authorization']
  }),
  customHeaders: _configSchema.schema.recordOf(_configSchema.schema.string(), _configSchema.schema.string(), {
    defaultValue: {},
    validate: customHeaders => {
      const reservedHeaders = (0, _default_headers.getReservedHeaders)(Object.keys(customHeaders));
      if (reservedHeaders.length) {
        return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
      }
    }
  }),
  shardTimeout: _configSchema.schema.duration({
    defaultValue: '30s'
  }),
  requestTimeout: _configSchema.schema.duration({
    defaultValue: '30s'
  }),
  pingTimeout: _configSchema.schema.duration({
    defaultValue: _configSchema.schema.siblingRef('requestTimeout')
  }),
  logQueries: _configSchema.schema.boolean({
    defaultValue: false
  }),
  ssl: _configSchema.schema.object({
    verificationMode: _configSchema.schema.oneOf([_configSchema.schema.literal('none'), _configSchema.schema.literal('certificate'), _configSchema.schema.literal('full')], {
      defaultValue: 'full'
    }),
    certificateAuthorities: _configSchema.schema.maybe(_configSchema.schema.oneOf([_configSchema.schema.string(), _configSchema.schema.arrayOf(_configSchema.schema.string(), {
      minSize: 1
    })])),
    certificate: _configSchema.schema.maybe(_configSchema.schema.string()),
    key: _configSchema.schema.maybe(_configSchema.schema.string()),
    keyPassphrase: _configSchema.schema.maybe(_configSchema.schema.string()),
    keystore: _configSchema.schema.object({
      path: _configSchema.schema.maybe(_configSchema.schema.string()),
      password: _configSchema.schema.maybe(_configSchema.schema.string())
    }),
    truststore: _configSchema.schema.object({
      path: _configSchema.schema.maybe(_configSchema.schema.string()),
      password: _configSchema.schema.maybe(_configSchema.schema.string())
    }),
    alwaysPresentCertificate: _configSchema.schema.boolean({
      defaultValue: false
    })
  }, {
    validate: rawConfig => {
      if (rawConfig.key && rawConfig.keystore.path) {
        return 'cannot use [key] when [keystore.path] is specified';
      }
      if (rawConfig.certificate && rawConfig.keystore.path) {
        return 'cannot use [certificate] when [keystore.path] is specified';
      }
    }
  }),
  apiVersion: _configSchema.schema.string({
    defaultValue: DEFAULT_API_VERSION
  }),
  healthCheck: _configSchema.schema.object({
    delay: _configSchema.schema.duration({
      defaultValue: 2500
    })
  }),
  ignoreVersionMismatch: _configSchema.schema.conditional(_configSchema.schema.contextRef('dev'), false, _configSchema.schema.boolean({
    validate: rawValue => {
      if (rawValue === true) {
        return '"ignoreVersionMismatch" can only be set to true in development mode';
      }
    },
    // When running in serverless mode, default to `true`
    defaultValue: _configSchema.schema.contextRef('serverless')
  }), _configSchema.schema.boolean({
    defaultValue: false
  })),
  skipStartupConnectionCheck: _configSchema.schema.conditional(
  // Using dist over dev because integration_tests run with dev: false,
  // and this config is solely introduced to allow some of the integration tests to run without an ES server.
  _configSchema.schema.contextRef('dist'), true, _configSchema.schema.boolean({
    validate: rawValue => {
      if (rawValue === true) {
        return '"skipStartupConnectionCheck" can only be set to true when running from source to allow integration tests to run without an ES server';
      }
    },
    defaultValue: false
  }), _configSchema.schema.boolean({
    defaultValue: false
  })),
  apisToRedactInLogs: _configSchema.schema.arrayOf(_configSchema.schema.object({
    path: _configSchema.schema.string(),
    method: _configSchema.schema.maybe(_configSchema.schema.string())
  }), {
    defaultValue: []
  })
});
exports.configSchema = configSchema;
const deprecations = () => [(settings, fromPath, addDeprecation, {
  branch
}) => {
  var _es$ssl, _es$ssl2, _es$ssl3, _es$ssl4;
  const es = settings[fromPath];
  if (!es) {
    return;
  }
  if (es.username === 'kibana') {
    const username = es.username;
    addDeprecation({
      configPath: `${fromPath}.username`,
      title: _i18n.i18n.translate('core.deprecations.elasticsearchUsername.title', {
        defaultMessage: 'Using "elasticsearch.username: {username}" is deprecated',
        values: {
          username
        }
      }),
      message: _i18n.i18n.translate('core.deprecations.elasticsearchUsername.message', {
        defaultMessage: 'Kibana is configured to authenticate to Elasticsearch with the "{username}" user. Use a service account token instead.',
        values: {
          username
        }
      }),
      level: 'warning',
      documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${branch}/service-accounts.html`,
      correctiveActions: {
        manualSteps: [_i18n.i18n.translate('core.deprecations.elasticsearchUsername.manualSteps1', {
          defaultMessage: 'Use the elasticsearch-service-tokens CLI tool to create a new service account token for the "elastic/kibana" service account.'
        }), _i18n.i18n.translate('core.deprecations.elasticsearchUsername.manualSteps2', {
          defaultMessage: 'Add the "elasticsearch.serviceAccountToken" setting to kibana.yml.'
        }), _i18n.i18n.translate('core.deprecations.elasticsearchUsername.manualSteps3', {
          defaultMessage: 'Remove "elasticsearch.username" and "elasticsearch.password" from kibana.yml.'
        })]
      }
    });
  }
  const addSslDeprecation = (existingSetting, missingSetting) => {
    addDeprecation({
      configPath: existingSetting,
      title: _i18n.i18n.translate('core.deprecations.elasticsearchSSL.title', {
        defaultMessage: 'Using "{existingSetting}" without "{missingSetting}" has no effect',
        values: {
          existingSetting,
          missingSetting
        }
      }),
      message: _i18n.i18n.translate('core.deprecations.elasticsearchSSL.message', {
        defaultMessage: 'Use both "{existingSetting}" and "{missingSetting}" to enable Kibana to use Mutual TLS authentication with Elasticsearch.',
        values: {
          existingSetting,
          missingSetting
        }
      }),
      level: 'warning',
      documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/elasticsearch-mutual-tls.html`,
      correctiveActions: {
        manualSteps: [_i18n.i18n.translate('core.deprecations.elasticsearchSSL.manualSteps1', {
          defaultMessage: 'Add the "{missingSetting}" setting to kibana.yml.',
          values: {
            missingSetting
          }
        }), _i18n.i18n.translate('core.deprecations.elasticsearchSSL.manualSteps2', {
          defaultMessage: 'Alternatively, if you don\'t want to use Mutual TLS authentication, remove "{existingSetting}" from kibana.yml.',
          values: {
            existingSetting
          }
        })]
      }
    });
  };
  if (((_es$ssl = es.ssl) === null || _es$ssl === void 0 ? void 0 : _es$ssl.key) !== undefined && ((_es$ssl2 = es.ssl) === null || _es$ssl2 === void 0 ? void 0 : _es$ssl2.certificate) === undefined) {
    addSslDeprecation(`${fromPath}.ssl.key`, `${fromPath}.ssl.certificate`);
  } else if (((_es$ssl3 = es.ssl) === null || _es$ssl3 === void 0 ? void 0 : _es$ssl3.certificate) !== undefined && ((_es$ssl4 = es.ssl) === null || _es$ssl4 === void 0 ? void 0 : _es$ssl4.key) === undefined) {
    addSslDeprecation(`${fromPath}.ssl.certificate`, `${fromPath}.ssl.key`);
  }
  if (es.logQueries === true) {
    addDeprecation({
      configPath: `${fromPath}.logQueries`,
      level: 'warning',
      message: `Setting [${fromPath}.logQueries] is deprecated and no longer used. You should set the log level to "debug" for the "elasticsearch.query" context in "logging.loggers".`,
      correctiveActions: {
        manualSteps: [`Remove Setting [${fromPath}.logQueries] from your kibana configs`, `Set the log level to "debug" for the "elasticsearch.query" context in "logging.loggers".`]
      }
    });
  }
  return;
}];
const config = {
  path: 'elasticsearch',
  schema: configSchema,
  deprecations
};

/**
 * Wrapper of config schema.
 * @internal
 */
exports.config = config;
class ElasticsearchConfig {
  /**
   * @internal
   * Only valid in dev mode. Skip the valid connection check during startup. The connection check allows
   * Kibana to ensure that the Elasticsearch connection is valid before allowing
   * any other services to be set up.
   *
   * @remarks
   * You should disable this check at your own risk: Other services in Kibana
   * may fail if this step is not completed.
   */

  /**
   * The interval between health check requests Kibana sends to the Elasticsearch.
   */

  /**
   * Whether to allow kibana to connect to a non-compatible elasticsearch node.
   */

  /**
   * Version of the Elasticsearch (6.7, 7.1 or `master`) client will be connecting to.
   */

  /**
   * The maximum number of sockets that can be used for communications with elasticsearch.
   */

  /**
   * The maximum number of idle sockets to keep open between Kibana and Elasticsearch. If more sockets become idle, they will be closed.
   */

  /**
   * The timeout for idle sockets kept open between Kibana and Elasticsearch. If the socket is idle for longer than this timeout, it will be closed.
   */

  /**
   * Whether to use compression for communications with elasticsearch.
   */

  /**
   * Hosts that the client will connect to. If sniffing is enabled, this list will
   * be used as seeds to discover the rest of your cluster.
   */

  /**
   * List of Kibana client-side headers to send to Elasticsearch when request
   * scoped cluster client is used. If this is an empty array then *no* client-side
   * will be sent.
   */

  /**
   * Timeout after which PING HTTP request will be aborted and retried.
   */

  /**
   * Timeout after which HTTP request will be aborted and retried.
   */

  /**
   * Timeout for Elasticsearch to wait for responses from shards. Set to 0 to disable.
   */

  /**
   * Specifies whether the client should attempt to detect the rest of the cluster
   * when it is first instantiated.
   */

  /**
   * Interval to perform a sniff operation and make sure the list of nodes is complete.
   * If `false` then sniffing is disabled.
   */

  /**
   * Specifies whether the client should immediately sniff for a more current list
   * of nodes when a connection dies.
   */

  /**
   * If Elasticsearch is protected with basic authentication, this setting provides
   * the username that the Kibana server uses to perform its administrative functions.
   * Cannot be used in conjunction with serviceAccountToken.
   */

  /**
   * If Elasticsearch is protected with basic authentication, this setting provides
   * the password that the Kibana server uses to perform its administrative functions.
   */

  /**
   * If Elasticsearch security features are enabled, this setting provides the service account
   * token that the Kibana server users to perform its administrative functions.
   *
   * This is an alternative to specifying a username and password.
   */

  /**
   * Set of settings configure SSL connection between Kibana and Elasticsearch that
   * are required when `xpack.ssl.verification_mode` in Elasticsearch is set to
   * either `certificate` or `full`.
   */

  /**
   * Header names and values to send to Elasticsearch with every request. These
   * headers cannot be overwritten by client-side headers and aren't affected by
   * `requestHeadersWhitelist` configuration.
   */

  /**
   * Extends the list of APIs that should be redacted in logs.
   */

  constructor(rawConfig) {
    (0, _defineProperty2.default)(this, "skipStartupConnectionCheck", void 0);
    (0, _defineProperty2.default)(this, "healthCheckDelay", void 0);
    (0, _defineProperty2.default)(this, "ignoreVersionMismatch", void 0);
    (0, _defineProperty2.default)(this, "apiVersion", void 0);
    (0, _defineProperty2.default)(this, "maxSockets", void 0);
    (0, _defineProperty2.default)(this, "maxIdleSockets", void 0);
    (0, _defineProperty2.default)(this, "idleSocketTimeout", void 0);
    (0, _defineProperty2.default)(this, "compression", void 0);
    (0, _defineProperty2.default)(this, "hosts", void 0);
    (0, _defineProperty2.default)(this, "requestHeadersWhitelist", void 0);
    (0, _defineProperty2.default)(this, "pingTimeout", void 0);
    (0, _defineProperty2.default)(this, "requestTimeout", void 0);
    (0, _defineProperty2.default)(this, "shardTimeout", void 0);
    (0, _defineProperty2.default)(this, "sniffOnStart", void 0);
    (0, _defineProperty2.default)(this, "sniffInterval", void 0);
    (0, _defineProperty2.default)(this, "sniffOnConnectionFault", void 0);
    (0, _defineProperty2.default)(this, "username", void 0);
    (0, _defineProperty2.default)(this, "password", void 0);
    (0, _defineProperty2.default)(this, "serviceAccountToken", void 0);
    (0, _defineProperty2.default)(this, "ssl", void 0);
    (0, _defineProperty2.default)(this, "customHeaders", void 0);
    (0, _defineProperty2.default)(this, "apisToRedactInLogs", void 0);
    this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch;
    this.apiVersion = rawConfig.apiVersion;
    this.hosts = Array.isArray(rawConfig.hosts) ? rawConfig.hosts : [rawConfig.hosts];
    this.requestHeadersWhitelist = Array.isArray(rawConfig.requestHeadersWhitelist) ? rawConfig.requestHeadersWhitelist : [rawConfig.requestHeadersWhitelist];
    this.pingTimeout = rawConfig.pingTimeout;
    this.requestTimeout = rawConfig.requestTimeout;
    this.shardTimeout = rawConfig.shardTimeout;
    this.sniffOnStart = rawConfig.sniffOnStart;
    this.sniffOnConnectionFault = rawConfig.sniffOnConnectionFault;
    this.sniffInterval = rawConfig.sniffInterval;
    this.healthCheckDelay = rawConfig.healthCheck.delay;
    this.username = rawConfig.username;
    this.password = rawConfig.password;
    this.serviceAccountToken = rawConfig.serviceAccountToken;
    this.customHeaders = rawConfig.customHeaders;
    this.maxSockets = rawConfig.maxSockets;
    this.maxIdleSockets = rawConfig.maxIdleSockets;
    this.idleSocketTimeout = rawConfig.idleSocketTimeout;
    this.compression = rawConfig.compression;
    this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck;
    this.apisToRedactInLogs = rawConfig.apisToRedactInLogs;
    const {
      alwaysPresentCertificate,
      verificationMode
    } = rawConfig.ssl;
    const {
      key,
      keyPassphrase,
      certificate,
      certificateAuthorities
    } = readKeyAndCerts(rawConfig);
    this.ssl = {
      alwaysPresentCertificate,
      key,
      keyPassphrase,
      certificate,
      certificateAuthorities,
      verificationMode
    };
  }
}
exports.ElasticsearchConfig = ElasticsearchConfig;
const readKeyAndCerts = rawConfig => {
  var _rawConfig$ssl$keysto, _rawConfig$ssl$trusts;
  let key;
  let keyPassphrase;
  let certificate;
  let certificateAuthorities;
  const addCAs = ca => {
    if (ca && ca.length) {
      certificateAuthorities = [...(certificateAuthorities || []), ...ca];
    }
  };
  if ((_rawConfig$ssl$keysto = rawConfig.ssl.keystore) !== null && _rawConfig$ssl$keysto !== void 0 && _rawConfig$ssl$keysto.path) {
    const keystore = (0, _crypto.readPkcs12Keystore)(rawConfig.ssl.keystore.path, rawConfig.ssl.keystore.password);
    if (!keystore.key) {
      throw new Error(`Did not find key in Elasticsearch keystore.`);
    } else if (!keystore.cert) {
      throw new Error(`Did not find certificate in Elasticsearch keystore.`);
    }
    key = keystore.key;
    certificate = keystore.cert;
    addCAs(keystore.ca);
  } else {
    if (rawConfig.ssl.key) {
      key = readFile(rawConfig.ssl.key);
      keyPassphrase = rawConfig.ssl.keyPassphrase;
    }
    if (rawConfig.ssl.certificate) {
      certificate = readFile(rawConfig.ssl.certificate);
    }
  }
  if ((_rawConfig$ssl$trusts = rawConfig.ssl.truststore) !== null && _rawConfig$ssl$trusts !== void 0 && _rawConfig$ssl$trusts.path) {
    const ca = (0, _crypto.readPkcs12Truststore)(rawConfig.ssl.truststore.path, rawConfig.ssl.truststore.password);
    addCAs(ca);
  }
  const ca = rawConfig.ssl.certificateAuthorities;
  if (ca) {
    const parsed = [];
    const paths = Array.isArray(ca) ? ca : [ca];
    if (paths.length > 0) {
      for (const path of paths) {
        parsed.push(readFile(path));
      }
      addCAs(parsed);
    }
  }
  return {
    key,
    keyPassphrase,
    certificate,
    certificateAuthorities
  };
};
const readFile = file => {
  return (0, _fs.readFileSync)(file, 'utf8');
};