Add a default 'User-Agent' HTTP header (#141631)

(cherry picked from commit 681e2b5c72)

Co-authored-by: Seth Michael Larson <seth.larson@elastic.co>
This commit is contained in:
Kibana Machine 2022-09-23 08:30:51 -06:00 committed by GitHub
parent 172f7aa68a
commit cf881f8cf7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 214 additions and 76 deletions

View file

@ -15,5 +15,6 @@ export {
PRODUCT_RESPONSE_HEADER,
DEFAULT_HEADERS,
PRODUCT_ORIGIN_HEADER,
USER_AGENT_HEADER,
RESERVED_HEADERS,
} from './src/headers';

View file

@ -9,7 +9,7 @@
import { duration } from 'moment';
import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { parseClientOptions } from './client_config';
import { DEFAULT_HEADERS } from './headers';
import { getDefaultHeaders } from './headers';
const createConfig = (
parts: Partial<ElasticsearchClientConfig> = {}
@ -27,15 +27,16 @@ const createConfig = (
};
};
const kibanaVersion = '1.0.0';
const defaultHeaders = getDefaultHeaders(kibanaVersion);
describe('parseClientOptions', () => {
it('includes headers designing the HTTP request as originating from Kibana by default', () => {
const config = createConfig({});
expect(parseClientOptions(config, false)).toEqual(
expect(parseClientOptions(config, false, kibanaVersion)).toEqual(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
},
headers: defaultHeaders,
})
);
});
@ -43,7 +44,7 @@ describe('parseClientOptions', () => {
it('specifies `headers.maxSockets` Infinity and `keepAlive` true by default', () => {
const config = createConfig({});
expect(parseClientOptions(config, false)).toEqual(
expect(parseClientOptions(config, false, kibanaVersion)).toEqual(
expect.objectContaining({
agent: {
keepAlive: true,
@ -62,10 +63,10 @@ describe('parseClientOptions', () => {
},
});
expect(parseClientOptions(config, false)).toEqual(
expect(parseClientOptions(config, false, kibanaVersion)).toEqual(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
foo: 'bar',
hello: 'dolly',
},
@ -74,16 +75,20 @@ describe('parseClientOptions', () => {
});
it('`customHeaders` take precedence to default kibana headers', () => {
const customHeader = {
[Object.keys(DEFAULT_HEADERS)[0]]: 'foo',
};
const customHeader: Record<string, string> = {};
for (const header in defaultHeaders) {
if (defaultHeaders.hasOwnProperty(header)) {
customHeader[header] = 'foo';
}
}
const config = createConfig({
customHeaders: {
...customHeader,
},
});
expect(parseClientOptions(config, false)).toEqual(
expect(parseClientOptions(config, false, kibanaVersion)).toEqual(
expect.objectContaining({
headers: {
...customHeader,
@ -94,41 +99,57 @@ describe('parseClientOptions', () => {
describe('`keepAlive` option', () => {
it('`keepAlive` is true', () => {
const options = parseClientOptions(createConfig({ keepAlive: true }), false);
const options = parseClientOptions(createConfig({ keepAlive: true }), false, kibanaVersion);
expect(options.agent).toHaveProperty('keepAlive', true);
});
it('`keepAlive` is false', () => {
const options = parseClientOptions(createConfig({ keepAlive: false }), false);
const options = parseClientOptions(
createConfig({ keepAlive: false }),
false,
kibanaVersion
);
expect(options.agent).toHaveProperty('keepAlive', false);
});
it('`keepAlive` is undefined', () => {
const options = parseClientOptions(createConfig({}), false);
const options = parseClientOptions(createConfig({}), false, kibanaVersion);
expect(options.agent).toHaveProperty('keepAlive', true);
});
});
describe('`maxSockets` option', () => {
it('uses the specified config value', () => {
const options = parseClientOptions(createConfig({ maxSockets: 1024 }), false);
const options = parseClientOptions(
createConfig({ maxSockets: 1024 }),
false,
kibanaVersion
);
expect(options.agent).toHaveProperty('maxSockets', 1024);
});
it('defaults to `Infinity` if not specified by the config', () => {
const options = parseClientOptions(createConfig({}), false);
const options = parseClientOptions(createConfig({}), false, kibanaVersion);
expect(options.agent).toHaveProperty('maxSockets', Infinity);
});
});
describe('`compression` option', () => {
it('`compression` is true', () => {
const options = parseClientOptions(createConfig({ compression: true }), false);
const options = parseClientOptions(
createConfig({ compression: true }),
false,
kibanaVersion
);
expect(options.compression).toBe(true);
});
it('`compression` is false', () => {
const options = parseClientOptions(createConfig({ compression: false }), false);
const options = parseClientOptions(
createConfig({ compression: false }),
false,
kibanaVersion
);
expect(options.compression).toBe(false);
});
});
@ -139,7 +160,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffOnStart: true,
}),
false
false,
kibanaVersion
).sniffOnStart
).toEqual(true);
@ -148,7 +170,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffOnStart: false,
}),
false
false,
kibanaVersion
).sniffOnStart
).toEqual(false);
});
@ -158,7 +181,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffOnConnectionFault: true,
}),
false
false,
kibanaVersion
).sniffOnConnectionFault
).toEqual(true);
@ -167,7 +191,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffOnConnectionFault: false,
}),
false
false,
kibanaVersion
).sniffOnConnectionFault
).toEqual(false);
});
@ -177,7 +202,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffInterval: false,
}),
false
false,
kibanaVersion
).sniffInterval
).toEqual(false);
@ -186,7 +212,8 @@ describe('parseClientOptions', () => {
createConfig({
sniffInterval: duration(100, 'ms'),
}),
false
false,
kibanaVersion
).sniffInterval
).toEqual(100);
});
@ -196,7 +223,8 @@ describe('parseClientOptions', () => {
createConfig({
hosts: ['http://node-A:9200', 'http://node-B', 'https://node-C'],
}),
false
false,
kibanaVersion
);
expect(options.nodes).toMatchInlineSnapshot(`
@ -215,7 +243,11 @@ describe('parseClientOptions', () => {
});
it('`caFingerprint` option', () => {
const options = parseClientOptions(createConfig({ caFingerprint: 'ab:cd:ef' }), false);
const options = parseClientOptions(
createConfig({ caFingerprint: 'ab:cd:ef' }),
false,
kibanaVersion
);
expect(options.caFingerprint).toBe('ab:cd:ef');
});
@ -229,7 +261,8 @@ describe('parseClientOptions', () => {
createConfig({
username: 'user',
}),
false
false,
kibanaVersion
).auth
).toBeUndefined();
@ -238,7 +271,8 @@ describe('parseClientOptions', () => {
createConfig({
password: 'pass',
}),
false
false,
kibanaVersion
).auth
).toBeUndefined();
@ -248,7 +282,8 @@ describe('parseClientOptions', () => {
username: 'user',
password: 'pass',
}),
false
false,
kibanaVersion
)
).toEqual(
expect.objectContaining({
@ -266,7 +301,8 @@ describe('parseClientOptions', () => {
createConfig({
serviceAccountToken: 'ABC123',
}),
false
false,
kibanaVersion
)
).toEqual(
expect.objectContaining({
@ -283,7 +319,8 @@ describe('parseClientOptions', () => {
serviceAccountToken: 'ABC123',
hosts: ['http://node-A:9200'],
}),
true
true,
kibanaVersion
);
expect(options.nodes).toMatchInlineSnapshot(`
Array [
@ -302,7 +339,8 @@ describe('parseClientOptions', () => {
username: 'user',
password: 'pass',
}),
true
true,
kibanaVersion
).auth
).toBeUndefined();
});
@ -314,7 +352,8 @@ describe('parseClientOptions', () => {
password: 'pass',
hosts: ['http://node-A:9200'],
}),
true
true,
kibanaVersion
);
expect(options.nodes).toMatchInlineSnapshot(`
Array [
@ -331,7 +370,8 @@ describe('parseClientOptions', () => {
createConfig({
serviceAccountToken: 'ABC123',
}),
true
true,
kibanaVersion
).headers
).not.toHaveProperty('authorization');
});
@ -342,7 +382,8 @@ describe('parseClientOptions', () => {
serviceAccountToken: 'ABC123',
hosts: ['http://node-A:9200'],
}),
true
true,
kibanaVersion
);
expect(options.nodes).toMatchInlineSnapshot(`
Array [
@ -357,8 +398,8 @@ describe('parseClientOptions', () => {
describe('tls config', () => {
it('does not generate tls option is ssl config is not set', () => {
expect(parseClientOptions(createConfig({}), false).tls).toBeUndefined();
expect(parseClientOptions(createConfig({}), true).tls).toBeUndefined();
expect(parseClientOptions(createConfig({}), false, kibanaVersion).tls).toBeUndefined();
expect(parseClientOptions(createConfig({}), true, kibanaVersion).tls).toBeUndefined();
});
it('handles the `certificateAuthorities` option', () => {
@ -367,7 +408,8 @@ describe('parseClientOptions', () => {
createConfig({
ssl: { verificationMode: 'full', certificateAuthorities: ['content-of-ca-path'] },
}),
false
false,
kibanaVersion
).tls!.ca
).toEqual(['content-of-ca-path']);
expect(
@ -375,7 +417,8 @@ describe('parseClientOptions', () => {
createConfig({
ssl: { verificationMode: 'full', certificateAuthorities: ['content-of-ca-path'] },
}),
true
true,
kibanaVersion
).tls!.ca
).toEqual(['content-of-ca-path']);
});
@ -389,7 +432,8 @@ describe('parseClientOptions', () => {
verificationMode: 'none',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -406,7 +450,8 @@ describe('parseClientOptions', () => {
verificationMode: 'certificate',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -424,7 +469,8 @@ describe('parseClientOptions', () => {
verificationMode: 'full',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -442,7 +488,8 @@ describe('parseClientOptions', () => {
verificationMode: 'unknown' as any,
},
}),
false
false,
kibanaVersion
).tls
).toThrowErrorMatchingInlineSnapshot(`"Unknown ssl verificationMode: unknown"`);
});
@ -455,7 +502,8 @@ describe('parseClientOptions', () => {
verificationMode: undefined as any,
},
}),
false
false,
kibanaVersion
).tls
).toThrowErrorMatchingInlineSnapshot(`"Unknown ssl verificationMode: undefined"`);
});
@ -472,7 +520,8 @@ describe('parseClientOptions', () => {
keyPassphrase: 'passphrase',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -492,7 +541,8 @@ describe('parseClientOptions', () => {
keyPassphrase: 'passphrase',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -513,7 +563,8 @@ describe('parseClientOptions', () => {
keyPassphrase: 'passphrase',
},
}),
false
false,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -537,7 +588,8 @@ describe('parseClientOptions', () => {
keyPassphrase: 'passphrase',
},
}),
true
true,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {
@ -557,7 +609,8 @@ describe('parseClientOptions', () => {
alwaysPresentCertificate: true,
},
}),
true
true,
kibanaVersion
).tls
).toMatchInlineSnapshot(`
Object {

View file

@ -11,7 +11,7 @@ import { URL } from 'url';
import { Duration } from 'moment';
import type { ClientOptions, HttpAgentOptions } from '@elastic/elasticsearch';
import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { DEFAULT_HEADERS } from './headers';
import { getDefaultHeaders } from './headers';
export type ParsedClientOptions = Omit<ClientOptions, 'agent'> & { agent: HttpAgentOptions };
@ -24,13 +24,14 @@ export type ParsedClientOptions = Omit<ClientOptions, 'agent'> & { agent: HttpAg
*/
export function parseClientOptions(
config: ElasticsearchClientConfig,
scoped: boolean
scoped: boolean,
kibanaVersion: string
): ParsedClientOptions {
const clientOptions: ParsedClientOptions = {
sniffOnStart: config.sniffOnStart,
sniffOnConnectionFault: config.sniffOnConnectionFault,
headers: {
...DEFAULT_HEADERS,
...getDefaultHeaders(kibanaVersion),
...config.customHeaders,
},
// do not make assumption on user-supplied data content

View file

@ -16,7 +16,7 @@ import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks';
import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { ClusterClient } from './cluster_client';
import { DEFAULT_HEADERS } from './headers';
import { DEFAULT_HEADERS, getDefaultHeaders } from './headers';
import { AgentManager } from './agent_manager';
const createConfig = (
@ -35,6 +35,9 @@ const createConfig = (
};
};
const kibanaVersion = '1.0.0';
const defaultHeaders = getDefaultHeaders(kibanaVersion);
const createClient = () =>
({ close: jest.fn(), child: jest.fn() } as unknown as jest.Mocked<Client>);
@ -82,18 +85,21 @@ describe('ClusterClient', () => {
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
agentManager,
kibanaVersion,
});
expect(configureClientMock).toHaveBeenCalledTimes(2);
expect(configureClientMock).toHaveBeenCalledWith(config, {
logger,
agentManager,
kibanaVersion,
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
});
expect(configureClientMock).toHaveBeenCalledWith(config, {
logger,
agentManager,
kibanaVersion,
type: 'custom-type',
getExecutionContext: getExecutionContextMock,
scoped: true,
@ -108,6 +114,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
expect(clusterClient.asInternalUser).toBe(internalClient);
@ -122,6 +129,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@ -148,6 +156,7 @@ describe('ClusterClient', () => {
getExecutionContext,
getUnauthorizedErrorHandler,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@ -171,6 +180,7 @@ describe('ClusterClient', () => {
getExecutionContext,
getUnauthorizedErrorHandler,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@ -203,6 +213,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@ -227,6 +238,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
headers: {
@ -240,7 +252,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: { ...DEFAULT_HEADERS, foo: 'bar', 'x-opaque-id': expect.any(String) },
headers: { ...defaultHeaders, foo: 'bar', 'x-opaque-id': expect.any(String) },
})
);
});
@ -260,6 +272,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@ -269,7 +282,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
authorization: 'auth',
other: 'yep',
'x-opaque-id': expect.any(String),
@ -293,6 +306,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
headers: {
@ -306,7 +320,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
authorization: 'auth',
other: 'yep',
'x-opaque-id': expect.any(String),
@ -331,6 +345,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@ -340,7 +355,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
foo: 'bar',
hello: 'dolly',
'x-opaque-id': expect.any(String),
@ -359,6 +374,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' },
@ -370,7 +386,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
'x-opaque-id': 'my-fake-id',
},
})
@ -395,6 +411,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({});
@ -404,7 +421,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
foo: 'auth',
hello: 'dolly',
'x-opaque-id': expect.any(String),
@ -429,6 +446,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
headers: { foo: 'request' },
@ -440,7 +458,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
foo: 'request',
hello: 'dolly',
'x-opaque-id': expect.any(String),
@ -453,6 +471,7 @@ describe('ClusterClient', () => {
const headerKey = Object.keys(DEFAULT_HEADERS)[0];
const config = createConfig({
customHeaders: {
...defaultHeaders,
[headerKey]: 'foo',
},
});
@ -464,6 +483,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest();
@ -473,6 +493,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...defaultHeaders,
[headerKey]: 'foo',
'x-opaque-id': expect.any(String),
},
@ -493,6 +514,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
headers: { [headerKey]: 'foo' },
@ -504,6 +526,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...defaultHeaders,
[headerKey]: 'foo',
'x-opaque-id': expect.any(String),
},
@ -525,6 +548,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = httpServerMock.createKibanaRequest({
headers: { foo: 'request' },
@ -537,7 +561,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
...DEFAULT_HEADERS,
...defaultHeaders,
'x-opaque-id': 'from request',
},
})
@ -556,6 +580,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = {
headers: {
@ -569,7 +594,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: { ...DEFAULT_HEADERS, authorization: 'auth' },
headers: { ...defaultHeaders, authorization: 'auth' },
})
);
});
@ -588,6 +613,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
const request = {
headers: {
@ -601,7 +627,7 @@ describe('ClusterClient', () => {
expect(scopedClient.child).toHaveBeenCalledTimes(1);
expect(scopedClient.child).toHaveBeenCalledWith(
expect.objectContaining({
headers: { ...DEFAULT_HEADERS, foo: 'bar' },
headers: { ...defaultHeaders, foo: 'bar' },
})
);
});
@ -615,6 +641,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
await clusterClient.close();
@ -632,6 +659,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
let internalClientClosed = false;
@ -676,6 +704,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
internalClient.close.mockRejectedValue(new Error('error closing client'));
@ -692,6 +721,7 @@ describe('ClusterClient', () => {
type: 'custom-type',
authHeaders,
agentManager,
kibanaVersion,
});
await clusterClient.close();

View file

@ -23,7 +23,7 @@ import type {
import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server';
import { configureClient } from './configure_client';
import { ScopedClusterClient } from './scoped_cluster_client';
import { DEFAULT_HEADERS } from './headers';
import { getDefaultHeaders } from './headers';
import { createInternalErrorHandler, InternalUnauthorizedErrorHandler } from './retry_unauthorized';
import { createTransport } from './create_transport';
import { AgentManager } from './agent_manager';
@ -35,6 +35,7 @@ export class ClusterClient implements ICustomClusterClient {
private readonly config: ElasticsearchClientConfig;
private readonly authHeaders?: IAuthHeadersStorage;
private readonly rootScopedClient: Client;
private readonly kibanaVersion: string;
private readonly getUnauthorizedErrorHandler: () => UnauthorizedErrorHandler | undefined;
private readonly getExecutionContext: () => string | undefined;
private isClosed = false;
@ -49,6 +50,7 @@ export class ClusterClient implements ICustomClusterClient {
getExecutionContext = noop,
getUnauthorizedErrorHandler = noop,
agentManager,
kibanaVersion,
}: {
config: ElasticsearchClientConfig;
logger: Logger;
@ -57,9 +59,11 @@ export class ClusterClient implements ICustomClusterClient {
getExecutionContext?: () => string | undefined;
getUnauthorizedErrorHandler?: () => UnauthorizedErrorHandler | undefined;
agentManager: AgentManager;
kibanaVersion: string;
}) {
this.config = config;
this.authHeaders = authHeaders;
this.kibanaVersion = kibanaVersion;
this.getExecutionContext = getExecutionContext;
this.getUnauthorizedErrorHandler = getUnauthorizedErrorHandler;
@ -68,6 +72,7 @@ export class ClusterClient implements ICustomClusterClient {
type,
getExecutionContext,
agentManager,
kibanaVersion,
});
this.rootScopedClient = configureClient(config, {
logger,
@ -75,6 +80,7 @@ export class ClusterClient implements ICustomClusterClient {
getExecutionContext,
scoped: true,
agentManager,
kibanaVersion,
});
}
@ -132,7 +138,7 @@ export class ClusterClient implements ICustomClusterClient {
}
return {
...DEFAULT_HEADERS,
...getDefaultHeaders(this.kibanaVersion),
...this.config.customHeaders,
...scopedHeaders,
};

View file

@ -27,6 +27,7 @@ import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecati
import { AgentManager } from './agent_manager';
const AgentManagerMock = AgentManager as jest.Mock<AgentManager>;
const kibanaVersion = '1.0.0';
const createFakeConfig = (
parts: Partial<ElasticsearchClientConfig> = {}
@ -79,17 +80,17 @@ describe('configureClient', () => {
});
it('calls `parseClientOptions` with the correct parameters', () => {
configureClient(config, { logger, type: 'test', scoped: false, agentManager });
configureClient(config, { logger, type: 'test', scoped: false, agentManager, kibanaVersion });
expect(parseClientOptionsMock).toHaveBeenCalledTimes(1);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, false);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, false, kibanaVersion);
parseClientOptionsMock.mockClear();
configureClient(config, { logger, type: 'test', scoped: true, agentManager });
configureClient(config, { logger, type: 'test', scoped: true, agentManager, kibanaVersion });
expect(parseClientOptionsMock).toHaveBeenCalledTimes(1);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, true);
expect(parseClientOptionsMock).toHaveBeenCalledWith(config, true, kibanaVersion);
});
it('constructs a client using the options returned by `parseClientOptions`', () => {
@ -98,7 +99,13 @@ describe('configureClient', () => {
};
parseClientOptionsMock.mockReturnValue(parsedOptions);
const client = configureClient(config, { logger, type: 'test', scoped: false, agentManager });
const client = configureClient(config, {
logger,
type: 'test',
scoped: false,
agentManager,
kibanaVersion,
});
expect(ClientMock).toHaveBeenCalledTimes(1);
expect(ClientMock).toHaveBeenCalledWith(expect.objectContaining(parsedOptions));
@ -112,6 +119,7 @@ describe('configureClient', () => {
type: 'test',
scoped: false,
agentManager: customAgentManager,
kibanaVersion,
});
expect(ClientMock).toHaveBeenCalledTimes(1);
@ -127,6 +135,7 @@ describe('configureClient', () => {
scoped: false,
getExecutionContext,
agentManager,
kibanaVersion,
});
expect(createTransportMock).toHaveBeenCalledTimes(1);
@ -140,6 +149,7 @@ describe('configureClient', () => {
scoped: true,
getExecutionContext,
agentManager,
kibanaVersion,
});
expect(createTransportMock).toHaveBeenCalledTimes(1);
@ -150,7 +160,13 @@ describe('configureClient', () => {
const mockedTransport = { mockTransport: true };
createTransportMock.mockReturnValue(mockedTransport);
const client = configureClient(config, { logger, type: 'test', scoped: false, agentManager });
const client = configureClient(config, {
logger,
type: 'test',
scoped: false,
agentManager,
kibanaVersion,
});
expect(ClientMock).toHaveBeenCalledTimes(1);
expect(ClientMock).toHaveBeenCalledWith(
@ -165,7 +181,13 @@ describe('configureClient', () => {
const mockedTransport = { mockTransport: true };
createTransportMock.mockReturnValue(mockedTransport);
const client = configureClient(config, { logger, type: 'test', scoped: false, agentManager });
const client = configureClient(config, {
logger,
type: 'test',
scoped: false,
agentManager,
kibanaVersion,
});
expect(ClientMock).toHaveBeenCalledTimes(1);
expect(ClientMock).toHaveBeenCalledWith(
@ -177,7 +199,13 @@ describe('configureClient', () => {
});
it('calls instrumentEsQueryAndDeprecationLogger', () => {
const client = configureClient(config, { logger, type: 'test', scoped: false, agentManager });
const client = configureClient(config, {
logger,
type: 'test',
scoped: false,
agentManager,
kibanaVersion,
});
expect(instrumentEsQueryAndDeprecationLogger).toHaveBeenCalledTimes(1);
expect(instrumentEsQueryAndDeprecationLogger).toHaveBeenCalledWith({

View file

@ -24,15 +24,17 @@ export const configureClient = (
scoped = false,
getExecutionContext = noop,
agentManager,
kibanaVersion,
}: {
logger: Logger;
type: string;
scoped?: boolean;
getExecutionContext?: () => string | undefined;
agentManager: AgentManager;
kibanaVersion: string;
}
): Client => {
const clientOptions = parseClientOptions(config, scoped);
const clientOptions = parseClientOptions(config, scoped, kibanaVersion);
const KibanaTransport = createTransport({ getExecutionContext });
const client = new Client({
...clientOptions,

View file

@ -21,7 +21,12 @@ export const PRODUCT_ORIGIN_HEADER = 'x-elastic-product-origin';
/**
* @internal
*/
export const RESERVED_HEADERS = deepFreeze([PRODUCT_ORIGIN_HEADER]);
export const USER_AGENT_HEADER = 'user-agent';
/**
* @internal
*/
export const RESERVED_HEADERS = deepFreeze([PRODUCT_ORIGIN_HEADER, USER_AGENT_HEADER]);
/**
* @internal
@ -31,3 +36,13 @@ export const DEFAULT_HEADERS = deepFreeze({
// access system indices using the standard ES APIs.
[PRODUCT_ORIGIN_HEADER]: 'kibana',
});
/**
* @internal
*/
export function getDefaultHeaders(kibanaVersion: string) {
return {
...DEFAULT_HEADERS,
[USER_AGENT_HEADER]: `Kibana/${kibanaVersion}`,
};
}

View file

@ -183,6 +183,7 @@ export class ElasticsearchService
getExecutionContext: () => this.executionContextClient?.getAsHeader(),
getUnauthorizedErrorHandler: () => this.unauthorizedErrorHandler,
agentManager: this.agentManager,
kibanaVersion: this.kibanaVersion,
});
}
}

View file

@ -49,6 +49,7 @@ export const elasticsearch = new ElasticsearchService(logger, kibanaPackageJson.
type,
// we use an independent AgentManager for cli_setup, no need to track performance of this one
agentManager: new AgentManager(),
kibanaVersion: kibanaPackageJson.version,
});
},
},