Skip to content

Commit

Permalink
add option to disable execution_context service on the server side
Browse files Browse the repository at this point in the history
  • Loading branch information
mshustov committed Jul 6, 2021
1 parent 168cc22 commit 7013cb6
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 5 deletions.
11 changes: 8 additions & 3 deletions src/core/server/elasticsearch/client/cluster_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { Client } from '@elastic/elasticsearch';
import { Logger } from '../../logging';
import { GetAuthHeaders, Headers, isRealRequest } from '../../http';
import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http';
import { ensureRawRequest, filterHeaders } from '../../http/router';
import type { IExecutionContextContainer } from '../../execution_context';
import { ScopeableRequest } from '../types';
Expand Down Expand Up @@ -73,7 +73,8 @@ export class ClusterClient implements ICustomClusterClient {
getExecutionContext,
scoped: true,
});
this.allowListHeaders = this.config.requestHeadersWhitelist;

this.allowListHeaders = ['x-opaque-id', ...this.config.requestHeadersWhitelist];
}

asScoped(request: ScopeableRequest) {
Expand All @@ -96,9 +97,13 @@ export class ClusterClient implements ICustomClusterClient {
let scopedHeaders: Headers;
if (isRealRequest(request)) {
const requestHeaders = ensureRawRequest(request).headers;
const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {};
const authHeaders = this.getAuthHeaders(request);

scopedHeaders = filterHeaders({ ...requestHeaders, ...authHeaders }, this.allowListHeaders);
scopedHeaders = filterHeaders(
{ ...requestHeaders, ...requestIdHeaders, ...authHeaders },
this.allowListHeaders
);
} else {
scopedHeaders = filterHeaders(request?.headers ?? {}, this.config.requestHeadersWhitelist);
}
Expand Down
1 change: 1 addition & 0 deletions src/core/server/elasticsearch/client/configure_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const configureClient = (
const opts = options || {};
const opaqueId = getExecutionContext()?.toString();
if (opaqueId && !opts.opaqueId) {
// rewrites headers['x-opaque-id'] if it presents
opts.opaqueId = opaqueId;
}
return super.request(params, opts);
Expand Down
24 changes: 24 additions & 0 deletions src/core/server/execution_context/execution_context_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
*/

import { TypeOf, schema } from '@kbn/config-schema';
import { ServiceConfigDescriptor } from '../internal_types';

const configSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
});

/**
* @internal
*/
export type ExecutionContextConfigType = TypeOf<typeof configSchema>;

export const config: ServiceConfigDescriptor<ExecutionContextConfigType> = {
path: 'execution_context',
schema: configSchema,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { BehaviorSubject } from 'rxjs';
import {
ExecutionContextService,
InternalExecutionContextSetup,
Expand All @@ -16,6 +17,7 @@ describe('ExecutionContextService', () => {
describe('setup', () => {
let service: InternalExecutionContextSetup;
const core = mockCoreContext.create();
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true }));
beforeEach(() => {
service = new ExecutionContextService(core).setup();
});
Expand Down Expand Up @@ -81,4 +83,49 @@ describe('ExecutionContextService', () => {
]);
});
});

describe('config', () => {
it('can be disabled', async () => {
const core = mockCoreContext.create();
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: false }));
const service = new ExecutionContextService(core).setup();
const chainA = await Promise.resolve().then(async () => {
service.set({
requestId: '0000',
});
await delay(100);
return service.get();
});

expect(chainA).toBeUndefined();
});

it('reacts to config changes', async () => {
const core = mockCoreContext.create();
const config$ = new BehaviorSubject({ enabled: false });
core.configService.atPath.mockReturnValue(config$);
const service = new ExecutionContextService(core).setup();
function exec() {
return Promise.resolve().then(async () => {
service.set({
requestId: '0000',
});
await delay(100);
return service.get();
});
}
expect(await exec()).toBeUndefined();

config$.next({
enabled: true,
});
expect(await exec()).toBeDefined();

config$.next({
enabled: false,
});

expect(await exec()).toBeUndefined();
});
});
});
24 changes: 22 additions & 2 deletions src/core/server/execution_context/execution_context_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
* Side Public License, v 1.
*/
import { AsyncLocalStorage } from 'async_hooks';
import type { Subscription } from 'rxjs';

import type { CoreService, KibanaExecutionContext } from '../../types';
import type { CoreContext } from '../core_context';
import type { Logger } from '../logging';
import type { ExecutionContextConfigType } from './execution_context_config';

import {
ExecutionContextContainer,
Expand Down Expand Up @@ -69,13 +71,21 @@ export class ExecutionContextService
implements CoreService<InternalExecutionContextSetup, InternalExecutionContextStart> {
private readonly log: Logger;
private readonly asyncLocalStorage: AsyncLocalStorage<IExecutionContextContainer>;
private enabled = false;
private configSubscription?: Subscription;

constructor(coreContext: CoreContext) {
constructor(private readonly coreContext: CoreContext) {
this.log = coreContext.logger.get('execution_context');
this.asyncLocalStorage = new AsyncLocalStorage<IExecutionContextContainer>();
}

setup(): InternalExecutionContextSetup {
this.configSubscription = this.coreContext.configService
.atPath<ExecutionContextConfigType>('execution_context')
.subscribe((config) => {
this.enabled = config.enabled;
});

return {
getParentContextFrom,
set: this.set.bind(this),
Expand All @@ -92,9 +102,17 @@ export class ExecutionContextService
get: this.get.bind(this),
};
}
stop() {}

stop() {
this.enabled = false;
if (this.configSubscription) {
this.configSubscription.unsubscribe();
this.configSubscription = undefined;
}
}

private set(context: KibanaServerExecutionContext) {
if (!this.enabled) return;
const prevValue = this.asyncLocalStorage.getStore();
// merges context objects shallowly. repeats the deafult logic of apm.setCustomContext(ctx)
const contextContainer = new ExecutionContextContainer({ ...prevValue?.toJSON(), ...context });
Expand All @@ -105,11 +123,13 @@ export class ExecutionContextService
}

private reset() {
if (!this.enabled) return;
// @ts-expect-error "undefined" is not supported in type definitions, which is wrong
this.asyncLocalStorage.enterWith(undefined);
}

private get(): IExecutionContextContainer | undefined {
if (!this.enabled) return;
return this.asyncLocalStorage.getStore();
}
}
1 change: 1 addition & 0 deletions src/core/server/execution_context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export type {
KibanaServerExecutionContext,
} from './execution_context_service';
export type { IExecutionContextContainer } from './execution_context_container';
export { config } from './execution_context_config';
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,72 @@ describe('trace', () => {
const header = response.body['x-opaque-id'];
expect(header).toBe('new-opaque-id');
});

it('passed to Elasticsearch scoped client calls even if ExecutionContext Serivce is disabled', async () => {
const kibana = kbnTestServer.createRootWithCorePlugins({
execution_context: { enabled: false },
plugins: { initialize: false },
server: {
requestId: {
allowFromAnyIp: true,
},
},
});
const { http } = await kibana.setup();
const { createRouter } = http;

const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
return res.ok({ body: headers || {} });
});

await kibana.start();

const myOpaqueId = 'my-opaque-id';
const response = await kbnTestServer.request
.get(kibana, '/execution-context')
.set('x-opaque-id', myOpaqueId)
.expect(200);

const header = response.body['x-opaque-id'];
expect(header).toBe(myOpaqueId);
});

it('does not pass context if ExecutionContext Serivce is disabled', async () => {
const kibana = kbnTestServer.createRootWithCorePlugins({
execution_context: { enabled: false },
plugins: { initialize: false },
server: {
requestId: {
allowFromAnyIp: true,
},
},
});
const { http, executionContext } = await kibana.setup();
const { createRouter } = http;

const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
executionContext.set(parentContext);
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
return res.ok({
body: { context: executionContext.get()?.toJSON(), header: headers?.['x-opaque-id'] },
});
});

await kibana.start();

const myOpaqueId = 'my-opaque-id';
const response = await kbnTestServer.request
.get(kibana, '/execution-context')
.set('x-opaque-id', myOpaqueId)
.expect(200);

expect(response.body).toEqual({
header: 'my-opaque-id',
});
});
});

describe('execution context', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { CoreUsageDataService } from './core_usage_data';
import { DeprecationsService } from './deprecations';
import { CoreRouteHandlerContext } from './core_route_handler_context';
import { config as externalUrlConfig } from './external_url';
import { config as executionContextConfig } from './execution_context';

const coreId = Symbol('core');
const rootConfigPath = '';
Expand Down Expand Up @@ -306,6 +307,7 @@ export class Server {

public setupCoreConfig() {
const configDescriptors: Array<ServiceConfigDescriptor<unknown>> = [
executionContextConfig,
pathConfig,
cspConfig,
elasticsearchConfig,
Expand Down

0 comments on commit 7013cb6

Please sign in to comment.