import { schema , TypeOf } from '@osd/config-schema' ; import { Duration } from 'moment' ; import { readFileSync } from 'fs' ; import { ConfigDeprecationProvider } from 'src/core/server' ; import { readPkcs12Keystore , readPkcs12Truststore } from '../utils' ; import { ServiceConfigDescriptor } from '../internal_types' ; const hostURISchema = schema . uri ( { scheme : [ 'http' , 'https' ] } ) ; export const DEFAULT_API_VERSION = '7.x' ; export type OpenSearchConfigType = TypeOf < typeof configSchema > ; type SslConfigSchema = OpenSearchConfigType [ 'ssl' ] ; public export const configSchema = schema . object ( { sniffOnStart : schema . boolean ( { defaultValue : false } ) , sniffInterval : schema . oneOf ( [ schema . duration ( ) , schema . literal ( false ) ] , { defaultValue : false , } ) , sniffOnConnectionFault : schema . boolean ( { defaultValue : false } ) , hosts : schema . oneOf ( [ hostURISchema , schema . arrayOf ( hostURISchema , { minSize : 1 } ) ] , { defaultValue : 'http://localhost:9200' , } ) , username : schema . maybe ( schema . conditional ( schema . contextRef ( 'dist' ) , false , schema . string ( { validate : ( rawConfig ) => { if ( rawConfig === 'elastic' ) { return ( 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + 'privilege-related issues. You should use the "opensearch_dashboards_system" user instead.' ) ; } } , } ) , schema . string ( ) ) ) , password : schema . maybe ( schema . string ( ) ) , requestHeadersWhitelist : schema . oneOf ( [ schema . string ( ) , schema . arrayOf ( schema . string ( ) ) ] , { defaultValue : [ 'authorization' ] , } ) , customHeaders : schema . recordOf ( schema . string ( ) , schema . string ( ) , { defaultValue : { } } ) , shardTimeout : schema . duration ( { defaultValue : '30s' } ) , requestTimeout : schema . duration ( { defaultValue : '30s' } ) , pingTimeout : schema . duration ( { defaultValue : schema . siblingRef ( 'requestTimeout' ) } ) , logQueries : schema . boolean ( { defaultValue : false } ) , optimizedHealthcheckId : schema . maybe ( schema . string ( ) ) , ssl : schema . object ( { verificationMode : schema . oneOf ( [ schema . literal ( 'none' ) , schema . literal ( 'certificate' ) , schema . literal ( 'full' ) ] , { defaultValue : 'full' } ) , certificateAuthorities : schema . maybe ( schema . oneOf ( [ schema . string ( ) , schema . arrayOf ( schema . string ( ) , { minSize : 1 } ) ] ) ) , certificate : schema . maybe ( schema . string ( ) ) , key : schema . maybe ( schema . string ( ) ) , keyPassphrase : schema . maybe ( schema . string ( ) ) , keystore : schema . object ( { path : schema . maybe ( schema . string ( ) ) , password : schema . maybe ( schema . string ( ) ) , } ) , truststore : schema . object ( { path : schema . maybe ( schema . string ( ) ) , password : schema . maybe ( schema . string ( ) ) , } ) , alwaysPresentCertificate : 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 : schema . string ( { defaultValue : DEFAULT_API_VERSION } ) , healthCheck : schema . object ( { delay : schema . duration ( { defaultValue : 2500 } ) } ) , ignoreVersionMismatch : schema . conditional ( schema . contextRef ( 'dev' ) , false , schema . boolean ( { validate : ( rawValue ) => { if ( rawValue === true ) { return '"ignoreVersionMismatch" can only be set to true in development mode' ; } } , defaultValue : false , } ) , schema . boolean ( { defaultValue : false } ) ) , } ) ; const deprecations = ( { renameFromRoot } ) => [ renameFromRoot ( 'elasticsearch.sniffOnStart' , 'opensearch.sniffOnStart' ) , renameFromRoot ( 'elasticsearch.sniffInterval' , 'opensearch.sniffInterval' ) , renameFromRoot ( 'elasticsearch.sniffOnConnectionFault' , 'opensearch.sniffOnConnectionFault' ) , renameFromRoot ( 'elasticsearch.hosts' , 'opensearch.hosts' ) , renameFromRoot ( 'elasticsearch.username' , 'opensearch.username' ) , renameFromRoot ( 'elasticsearch.password' , 'opensearch.password' ) , renameFromRoot ( 'elasticsearch.requestHeadersWhitelist' , 'opensearch.requestHeadersWhitelist' ) , renameFromRoot ( 'elasticsearch.customHeaders' , 'opensearch.customHeaders' ) , renameFromRoot ( 'elasticsearch.shardTimeout' , 'opensearch.shardTimeout' ) , renameFromRoot ( 'elasticsearch.requestTimeout' , 'opensearch.requestTimeout' ) , renameFromRoot ( 'elasticsearch.pingTimeout' , 'opensearch.pingTimeout' ) , renameFromRoot ( 'elasticsearch.logQueries' , 'opensearch.logQueries' ) , renameFromRoot ( 'elasticsearch.optimizedHealthcheckId' , 'opensearch.optimizedHealthcheckId' ) , renameFromRoot ( 'elasticsearch.ssl' , 'opensearch.ssl' ) , renameFromRoot ( 'elasticsearch.apiVersion' , 'opensearch.apiVersion' ) , renameFromRoot ( 'elasticsearch.healthCheck' , 'opensearch.healthCheck' ) , renameFromRoot ( 'elasticsearch.ignoreVersionMismatch' , 'opensearch.ignoreVersionMismatch' ) , ( settings , fromPath , log ) => { const opensearch = settings [ fromPath ] ; if ( ! opensearch ) { return settings ; } if ( opensearch . username === 'elastic' ) { log ( `Setting [${ fromPath }.username] to "elastic" is deprecated. You should use the "opensearch_dashboards_system" user instead.` ) ; } else if ( opensearch . username === 'opensearchDashboards' ) { log ( `Setting [${ fromPath }.username] to "opensearchDashboards" is deprecated. You should use the "opensearch_dashboards_system" user instead.` ) ; } if ( opensearch . ssl ?. key !== undefined && opensearch . ssl ?. certificate === undefined ) { log ( `Setting [${ fromPath }.ssl.key] without [${ fromPath }.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to OpenSearch.` ) ; } else if ( opensearch . ssl ?. certificate !== undefined && opensearch . ssl ?. key === undefined ) { log ( `Setting [${ fromPath }.ssl.certificate] without [${ fromPath }.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to OpenSearch.` ) ; } return settings ; } , ] ; export const config = { path : 'opensearch' , schema : configSchema , deprecations , } ; public export class OpenSearchConfig { /**
     * The interval between health check requests OpenSearch Dashboards sends to the OpenSearch.
     */ public readonly healthCheckDelay : Duration ; /**
     * Whether to allow opensearch-dashboards to connect to a non-compatible opensearch node.
     */ public readonly ignoreVersionMismatch : boolean ; /**
     * Version of the OpenSearch (6.7, 7.1 or `master`) client will be connecting to.
     */ public readonly apiVersion : string ; /**
     * Specifies whether all queries to the client should be logged (status code,
     * method, query etc.).
     */ public readonly logQueries : boolean ; /**
     * Specifies whether Dashboards should only query the local OpenSearch node when
     * all nodes in the cluster have the same node attribute value
     */ public readonly optimizedHealthcheckId ? : string ; /**
     * 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.
     */ public readonly hosts : string [ ] ; /**
     * List of OpenSearch Dashboards client-side headers to send to OpenSearch when request
     * scoped cluster client is used. If this is an empty array then *no* client-side
     * will be sent.
     */ public readonly requestHeadersWhitelist : string [ ] ; /**
     * Timeout after which PING HTTP request will be aborted and retried.
     */ public readonly pingTimeout : Duration ; /**
     * Timeout after which HTTP request will be aborted and retried.
     */ public readonly requestTimeout : Duration ; /**
     * Timeout for OpenSearch to wait for responses from shards. Set to 0 to disable.
     */ public readonly shardTimeout : Duration ; /**
     * Specifies whether the client should attempt to detect the rest of the cluster
     * when it is first instantiated.
     */ public readonly sniffOnStart : boolean ; /**
     * Interval to perform a sniff operation and make sure the list of nodes is complete.
     * If `false` then sniffing is disabled.
     */ public readonly sniffInterval : false | Duration ; /**
     * Specifies whether the client should immediately sniff for a more current list
     * of nodes when a connection dies.
     */ public readonly sniffOnConnectionFault : boolean ; /**
     * If OpenSearch is protected with basic authentication, this setting provides
     * the username that the OpenSearch Dashboards server uses to perform its administrative functions.
     */ public readonly username ? : string ; /**
     * If OpenSearch is protected with basic authentication, this setting provides
     * the password that the OpenSearch Dashboards server uses to perform its administrative functions.
     */ public readonly password ? : string ; /**
     * Set of settings configure SSL connection between OpenSearch Dashboards and OpenSearch that
     * are required when `xpack.ssl.verification_mode` in OpenSearch is set to
     * either `certificate` or `full`.
     */ public readonly ssl : Pick < SslConfigSchema , Exclude < keyof SslConfigSchema , 'certificateAuthorities' | 'keystore' | 'truststore' > > & { certificateAuthorities ? : string [ ] ; } ; /**
     * Header names and values to send to OpenSearch with every request. These
     * headers cannot be overwritten by client-side headers and aren't affected by
     * `requestHeadersWhitelist` configuration.
     */ public readonly customHeaders : OpenSearchConfigType [ 'customHeaders' ] ; constructor ( rawConfig ) { this . ignoreVersionMismatch = rawConfig . ignoreVersionMismatch ; this . apiVersion = rawConfig . apiVersion ; this . logQueries = rawConfig . logQueries ; this . optimizedHealthcheckId = rawConfig . optimizedHealthcheckId ; 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 . customHeaders = rawConfig . customHeaders ; const { alwaysPresentCertificate , verificationMode } = rawConfig . ssl ; const { key , keyPassphrase , certificate , certificateAuthorities } = readKeyAndCerts ( rawConfig ) ; this . ssl = { alwaysPresentCertificate , key , keyPassphrase , certificate , certificateAuthorities , verificationMode , } ; } } const readKeyAndCerts = ( rawConfig ) => { let key ; let keyPassphrase ; let certificate ; let certificateAuthorities ; const addCAs = ( ca ) => { if ( ca && ca . length ) { certificateAuthorities = [ ... ( certificateAuthorities || [ ] ) , ... ca ] ; } } ; if ( rawConfig . ssl . keystore ?. path ) { const keystore = readPkcs12Keystore ( rawConfig . ssl . keystore . path , rawConfig . ssl . keystore . password ) ; if ( ! keystore . key ) { throw new Error ( `Did not find key in OpenSearch keystore.` ) ; } else if ( ! keystore . cert ) { throw new Error ( `Did not find certificate in OpenSearch 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 . truststore ?. path ) { const ca = 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 readFileSync ( file , 'utf8' ) ; } ;