diff --git a/src/core/server/elasticsearch/client/client_config.test.ts b/src/core/server/elasticsearch/client/client_config.test.ts index 675d8840e7118..e8083836d3c1e 100644 --- a/src/core/server/elasticsearch/client/client_config.test.ts +++ b/src/core/server/elasticsearch/client/client_config.test.ts @@ -19,6 +19,7 @@ import { duration } from 'moment'; import { ElasticsearchClientConfig, parseClientOptions } from './client_config'; +import { DEFAULT_HEADERS } from '../default_headers'; const createConfig = ( parts: Partial = {} @@ -36,6 +37,18 @@ const createConfig = ( }; describe('parseClientOptions', () => { + it('includes headers designing the HTTP request as originating from Kibana by default', () => { + const config = createConfig({}); + + expect(parseClientOptions(config, false)).toEqual( + expect.objectContaining({ + headers: { + ...DEFAULT_HEADERS, + }, + }) + ); + }); + describe('basic options', () => { it('`customHeaders` option', () => { const config = createConfig({ @@ -48,6 +61,7 @@ describe('parseClientOptions', () => { expect(parseClientOptions(config, false)).toEqual( expect.objectContaining({ headers: { + ...DEFAULT_HEADERS, foo: 'bar', hello: 'dolly', }, @@ -55,6 +69,25 @@ describe('parseClientOptions', () => { ); }); + it('`customHeaders` take precedence to default kibana headers', () => { + const customHeader = { + [Object.keys(DEFAULT_HEADERS)[0]]: 'foo', + }; + const config = createConfig({ + customHeaders: { + ...customHeader, + }, + }); + + expect(parseClientOptions(config, false)).toEqual( + expect.objectContaining({ + headers: { + ...customHeader, + }, + }) + ); + }); + it('`keepAlive` option', () => { expect(parseClientOptions(createConfig({ keepAlive: true }), false)).toEqual( expect.objectContaining({ agent: { keepAlive: true } }) diff --git a/src/core/server/elasticsearch/client/client_config.ts b/src/core/server/elasticsearch/client/client_config.ts index f365ca331cfea..f24c0d86550b8 100644 --- a/src/core/server/elasticsearch/client/client_config.ts +++ b/src/core/server/elasticsearch/client/client_config.ts @@ -22,6 +22,7 @@ import { URL } from 'url'; import { Duration } from 'moment'; import { ClientOptions, NodeOptions } from '@elastic/elasticsearch'; import { ElasticsearchConfig } from '../elasticsearch_config'; +import { DEFAULT_HEADERS } from '../default_headers'; /** * Configuration options to be used to create a {@link IClusterClient | cluster client} using the @@ -61,7 +62,10 @@ export function parseClientOptions( const clientOptions: ClientOptions = { sniffOnStart: config.sniffOnStart, sniffOnConnectionFault: config.sniffOnConnectionFault, - headers: config.customHeaders, + headers: { + ...DEFAULT_HEADERS, + ...config.customHeaders, + }, }; if (config.pingTimeout != null) { diff --git a/src/core/server/elasticsearch/client/cluster_client.test.ts b/src/core/server/elasticsearch/client/cluster_client.test.ts index e35d9962e9e7e..429fea65704d8 100644 --- a/src/core/server/elasticsearch/client/cluster_client.test.ts +++ b/src/core/server/elasticsearch/client/cluster_client.test.ts @@ -24,6 +24,7 @@ import { GetAuthHeaders } from '../../http'; import { elasticsearchClientMock } from './mocks'; import { ClusterClient } from './cluster_client'; import { ElasticsearchClientConfig } from './client_config'; +import { DEFAULT_HEADERS } from '../default_headers'; const createConfig = ( parts: Partial = {} @@ -127,7 +128,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { foo: 'bar', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, foo: 'bar', 'x-opaque-id': expect.any(String) }, }); }); @@ -147,7 +148,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { authorization: 'auth', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, }); }); @@ -171,7 +172,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { authorization: 'auth', 'x-opaque-id': expect.any(String) }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth', 'x-opaque-id': expect.any(String) }, }); }); @@ -193,6 +194,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { + ...DEFAULT_HEADERS, foo: 'bar', hello: 'dolly', 'x-opaque-id': expect.any(String), @@ -214,6 +216,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { + ...DEFAULT_HEADERS, 'x-opaque-id': 'my-fake-id', }, }); @@ -239,6 +242,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { + ...DEFAULT_HEADERS, foo: 'auth', hello: 'dolly', 'x-opaque-id': expect.any(String), @@ -266,6 +270,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { + ...DEFAULT_HEADERS, foo: 'request', hello: 'dolly', 'x-opaque-id': expect.any(String), @@ -273,6 +278,52 @@ describe('ClusterClient', () => { }); }); + it('respect the precedence of config headers over default headers', () => { + const headerKey = Object.keys(DEFAULT_HEADERS)[0]; + const config = createConfig({ + customHeaders: { + [headerKey]: 'foo', + }, + }); + getAuthHeaders.mockReturnValue({}); + + const clusterClient = new ClusterClient(config, logger, getAuthHeaders); + const request = httpServerMock.createKibanaRequest(); + + clusterClient.asScoped(request); + + expect(scopedClient.child).toHaveBeenCalledTimes(1); + expect(scopedClient.child).toHaveBeenCalledWith({ + headers: { + [headerKey]: 'foo', + 'x-opaque-id': expect.any(String), + }, + }); + }); + + it('respect the precedence of request headers over default headers', () => { + const headerKey = Object.keys(DEFAULT_HEADERS)[0]; + const config = createConfig({ + requestHeadersWhitelist: [headerKey], + }); + getAuthHeaders.mockReturnValue({}); + + const clusterClient = new ClusterClient(config, logger, getAuthHeaders); + const request = httpServerMock.createKibanaRequest({ + headers: { [headerKey]: 'foo' }, + }); + + clusterClient.asScoped(request); + + expect(scopedClient.child).toHaveBeenCalledTimes(1); + expect(scopedClient.child).toHaveBeenCalledWith({ + headers: { + [headerKey]: 'foo', + 'x-opaque-id': expect.any(String), + }, + }); + }); + it('respect the precedence of x-opaque-id header over config headers', () => { const config = createConfig({ customHeaders: { @@ -292,6 +343,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: { + ...DEFAULT_HEADERS, 'x-opaque-id': 'from request', }, }); @@ -315,7 +367,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { authorization: 'auth' }, + headers: { ...DEFAULT_HEADERS, authorization: 'auth' }, }); }); @@ -339,7 +391,7 @@ describe('ClusterClient', () => { expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ - headers: { foo: 'bar' }, + headers: { ...DEFAULT_HEADERS, foo: 'bar' }, }); }); }); diff --git a/src/core/server/elasticsearch/client/cluster_client.ts b/src/core/server/elasticsearch/client/cluster_client.ts index ffe0c10321fff..2294df89d4274 100644 --- a/src/core/server/elasticsearch/client/cluster_client.ts +++ b/src/core/server/elasticsearch/client/cluster_client.ts @@ -26,6 +26,7 @@ import { ElasticsearchClient } from './types'; import { configureClient } from './configure_client'; import { ElasticsearchClientConfig } from './client_config'; import { ScopedClusterClient, IScopedClusterClient } from './scoped_cluster_client'; +import { DEFAULT_HEADERS } from '../default_headers'; const noop = () => undefined; @@ -108,6 +109,7 @@ export class ClusterClient implements ICustomClusterClient { } return { + ...DEFAULT_HEADERS, ...this.config.customHeaders, ...scopedHeaders, }; diff --git a/src/core/server/elasticsearch/default_headers.ts b/src/core/server/elasticsearch/default_headers.ts new file mode 100644 index 0000000000000..4cc4b4b2507c4 --- /dev/null +++ b/src/core/server/elasticsearch/default_headers.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { deepFreeze } from '@kbn/std'; + +export const DEFAULT_HEADERS = deepFreeze({ + // Elasticsearch uses this to identify when a request is coming from Kibana, to allow Kibana to + // access system indices using the standard ES APIs without logging a warning. After migrating to + // use the new system index APIs, this header can be removed. + 'x-elastic-product-origin': 'kibana', +}); diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts index d6b65da7726d2..1e85932caa8ae 100644 --- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts +++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts @@ -23,6 +23,7 @@ import { LegacyElasticsearchClientConfig, parseElasticsearchClientConfig, } from './elasticsearch_client_config'; +import { DEFAULT_HEADERS } from '../default_headers'; const logger = loggingSystemMock.create(); afterEach(() => jest.clearAllMocks()); @@ -41,26 +42,27 @@ test('parses minimally specified config', () => { logger.get() ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "master", - "hosts": Array [ Object { - "headers": Object { - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": false, - "sniffOnStart": false, -} -`); + "apiVersion": "master", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "localhost", + "path": "/elasticsearch", + "port": "80", + "protocol": "http:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": false, + "sniffOnStart": false, + } + `); }); test('parses fully specified config', () => { @@ -104,63 +106,66 @@ test('parses fully specified config', () => { expect(elasticsearchConfig.ssl).not.toBe(elasticsearchClientConfig.ssl); expect(elasticsearchClientConfig).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "auth": "elastic:changeme", - "headers": Object { - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - Object { - "auth": "elastic:changeme", - "headers": Object { - "xsrf": "something", - }, - "host": "domain.com", - "path": "/elasticsearch", - "port": "1234", - "protocol": "http:", - "query": null, - }, Object { - "auth": "elastic:changeme", - "headers": Object { - "xsrf": "something", + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "auth": "elastic:changeme", + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "localhost", + "path": "/elasticsearch", + "port": "80", + "protocol": "http:", + "query": null, + }, + Object { + "auth": "elastic:changeme", + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "domain.com", + "path": "/elasticsearch", + "port": "1234", + "protocol": "http:", + "query": null, + }, + Object { + "auth": "elastic:changeme", + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "pingTimeout": 12345, + "requestTimeout": 54321, + "sniffInterval": 11223344, + "sniffOnConnectionFault": true, + "sniffOnStart": true, + "ssl": Object { + "ca": Array [ + "content-of-ca-path-1", + "content-of-ca-path-2", + ], + "cert": "content-of-certificate-path", + "checkServerIdentity": [Function], + "key": "content-of-key-path", + "passphrase": "key-pass", + "rejectUnauthorized": true, }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "pingTimeout": 12345, - "requestTimeout": 54321, - "sniffInterval": 11223344, - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": Array [ - "content-of-ca-path-1", - "content-of-ca-path-2", - ], - "cert": "content-of-certificate-path", - "checkServerIdentity": [Function], - "key": "content-of-key-path", - "passphrase": "key-pass", - "rejectUnauthorized": true, - }, -} -`); + } + `); }); test('parses config timeouts of moment.Duration type', () => { @@ -181,29 +186,30 @@ test('parses config timeouts of moment.Duration type', () => { logger.get() ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "master", - "hosts": Array [ Object { - "headers": Object { - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "9200", - "protocol": "http:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "pingTimeout": 100, - "requestTimeout": 30000, - "sniffInterval": 60000, - "sniffOnConnectionFault": false, - "sniffOnStart": false, -} -`); + "apiVersion": "master", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "localhost", + "path": "/elasticsearch", + "port": "9200", + "protocol": "http:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "pingTimeout": 100, + "requestTimeout": 30000, + "sniffInterval": 60000, + "sniffOnConnectionFault": false, + "sniffOnStart": false, + } + `); }); describe('#auth', () => { @@ -225,36 +231,38 @@ describe('#auth', () => { { auth: false } ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "xsrf": "something", - }, - "host": "localhost", - "path": "/elasticsearch", - "port": "80", - "protocol": "http:", - "query": null, - }, - Object { - "headers": Object { - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "localhost", + "path": "/elasticsearch", + "port": "80", + "protocol": "http:", + "query": null, + }, + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + } + `); }); test('is not set if username is not specified', () => { @@ -274,26 +282,27 @@ Object { { auth: true } ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "xsrf": "something", - }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + } + `); }); test('is not set if password is not specified', () => { @@ -313,26 +322,48 @@ Object { { auth: true } ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object { - "xsrf": "something", + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + "xsrf": "something", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + } + `); + }); +}); + +describe('#customHeaders', () => { + test('override the default headers', () => { + const headerKey = Object.keys(DEFAULT_HEADERS)[0]; + const parsedConfig = parseElasticsearchClientConfig( + { + apiVersion: 'master', + customHeaders: { [headerKey]: 'foo' }, + logQueries: false, + sniffOnStart: false, + sniffOnConnectionFault: false, + hosts: ['http://localhost/elasticsearch'], + requestHeadersWhitelist: [], }, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, -} -`); + logger.get() + ); + expect(parsedConfig.hosts[0].headers).toEqual({ + [headerKey]: 'foo', + }); }); }); @@ -361,24 +392,24 @@ describe('#log', () => { expect(typeof esLogger.close).toBe('function'); expect(loggingSystemMock.collect(logger)).toMatchInlineSnapshot(` -Object { - "debug": Array [], - "error": Array [ - Array [ - "some-error", - ], - ], - "fatal": Array [], - "info": Array [], - "log": Array [], - "trace": Array [], - "warn": Array [ - Array [ - "some-warning", - ], - ], -} -`); + Object { + "debug": Array [], + "error": Array [ + Array [ + "some-error", + ], + ], + "fatal": Array [], + "info": Array [], + "log": Array [], + "trace": Array [], + "warn": Array [ + Array [ + "some-warning", + ], + ], + } + `); }); test('default logger with #logQueries = true', () => { @@ -407,35 +438,35 @@ Object { expect(typeof esLogger.close).toBe('function'); expect(loggingSystemMock.collect(logger)).toMatchInlineSnapshot(` -Object { - "debug": Array [ - Array [ - "304 -METHOD /some-path -?query=2", Object { - "tags": Array [ - "query", + "debug": Array [ + Array [ + "304 + METHOD /some-path + ?query=2", + Object { + "tags": Array [ + "query", + ], + }, + ], ], - }, - ], - ], - "error": Array [ - Array [ - "some-error", - ], - ], - "fatal": Array [], - "info": Array [], - "log": Array [], - "trace": Array [], - "warn": Array [ - Array [ - "some-warning", - ], - ], -} -`); + "error": Array [ + Array [ + "some-error", + ], + ], + "fatal": Array [], + "info": Array [], + "log": Array [], + "trace": Array [], + "warn": Array [ + Array [ + "some-warning", + ], + ], + } + `); }); test('custom logger', () => { @@ -476,28 +507,30 @@ describe('#ssl', () => { logger.get() ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object {}, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "rejectUnauthorized": false, - }, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + "ssl": Object { + "ca": undefined, + "rejectUnauthorized": false, + }, + } + `); }); test('#verificationMode = certificate', () => { @@ -521,29 +554,31 @@ Object { ).toBeUndefined(); expect(clientConfig).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object {}, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "checkServerIdentity": [Function], - "rejectUnauthorized": true, - }, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + "ssl": Object { + "ca": undefined, + "checkServerIdentity": [Function], + "rejectUnauthorized": true, + }, + } + `); }); test('#verificationMode = full', () => { @@ -562,28 +597,30 @@ Object { logger.get() ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object {}, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": undefined, - "rejectUnauthorized": true, - }, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + "ssl": Object { + "ca": undefined, + "rejectUnauthorized": true, + }, + } + `); }); test('#verificationMode is unknown', () => { @@ -628,30 +665,32 @@ Object { { ignoreCertAndKey: true } ) ).toMatchInlineSnapshot(` -Object { - "apiVersion": "v7.0.0", - "hosts": Array [ - Object { - "headers": Object {}, - "host": "es.local", - "path": "/", - "port": "443", - "protocol": "https:", - "query": null, - }, - ], - "keepAlive": true, - "log": [Function], - "sniffOnConnectionFault": true, - "sniffOnStart": true, - "ssl": Object { - "ca": Array [ - "content-of-ca-path", - ], - "checkServerIdentity": [Function], - "rejectUnauthorized": true, - }, -} -`); + Object { + "apiVersion": "v7.0.0", + "hosts": Array [ + Object { + "headers": Object { + "x-elastic-product-origin": "kibana", + }, + "host": "es.local", + "path": "/", + "port": "443", + "protocol": "https:", + "query": null, + }, + ], + "keepAlive": true, + "log": [Function], + "sniffOnConnectionFault": true, + "sniffOnStart": true, + "ssl": Object { + "ca": Array [ + "content-of-ca-path", + ], + "checkServerIdentity": [Function], + "rejectUnauthorized": true, + }, + } + `); }); }); diff --git a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts index 6896c0a2e301f..35681ac7a247d 100644 --- a/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts +++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts @@ -25,6 +25,7 @@ import url from 'url'; import { pick } from '@kbn/std'; import { Logger } from '../../logging'; import { ElasticsearchConfig } from '../elasticsearch_config'; +import { DEFAULT_HEADERS } from '../default_headers'; /** * @privateRemarks Config that consumers can pass to the Elasticsearch JS client is complex and includes @@ -130,7 +131,10 @@ export function parseElasticsearchClientConfig( protocol: uri.protocol, path: uri.pathname, query: uri.query, - headers: config.customHeaders, + headers: { + ...DEFAULT_HEADERS, + ...config.customHeaders, + }, }; if (needsAuth) {