mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[http] Do not do client version check on serverless as we do for onprem (#159101)
## Summary This PR introduces two changes: (1) Refactors the handler resolution logic to _not_ depend on the `--serverless` cli arg by adding a new piece of config `server.versioned.routeResolution` that accepts `newest | oldest`. This piece of config is passed down instead of the `serverless` cli arg as well as updating test cases (2) Adds a new piece of config to turn off the client version checking. This will be needed for rolling upgrades to allow old browser traffic to reach new Kibana servers when there is stack version change. Close https://github.com/elastic/kibana/issues/158723 ## Open questions * Do we want to make the version check still take _major_ version bumps into account?
This commit is contained in:
parent
d082d78e60
commit
7d07149323
22 changed files with 361 additions and 244 deletions
|
@ -37,8 +37,12 @@ server.securityResponseHeaders.strictTransportSecurity: max-age=31536000; includ
|
|||
# Disable embedding for serverless MVP
|
||||
server.securityResponseHeaders.disableEmbedding: true
|
||||
|
||||
# default to newest routes
|
||||
server.versioned.versionResolution: newest
|
||||
# do not enforce client version check
|
||||
server.versioned.strictClientVersionCheck: false
|
||||
|
||||
# Enforce single "default" space
|
||||
xpack.spaces.maxSpaces: 1
|
||||
|
||||
# Allow unauthenticated access to task manager utilization API for auto-scaling
|
||||
xpack.task_manager.unsafe.authenticate_background_task_utilization: false
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
export { filterHeaders } from './src/headers';
|
||||
export { Router } from './src/router';
|
||||
export { Router, type RouterOptions } from './src/router';
|
||||
export { isKibanaRequest, isRealRequest, ensureRawRequest, CoreKibanaRequest } from './src/request';
|
||||
export { isSafeMethod } from './src/route';
|
||||
export { HapiResponseAdapter } from './src/response_adapter';
|
||||
|
|
|
@ -6,17 +6,22 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Router } from './router';
|
||||
import { Router, type RouterOptions } from './router';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
const routerOptions: RouterOptions = {
|
||||
isDev: false,
|
||||
versionedRouteResolution: 'oldest',
|
||||
};
|
||||
|
||||
describe('Router', () => {
|
||||
describe('Options', () => {
|
||||
it('throws if validation for a route is not defined explicitly', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
expect(
|
||||
// we use 'any' because validate is a required field
|
||||
() => router.get({ path: '/' } as any, (context, req, res) => res.ok({}))
|
||||
|
@ -25,7 +30,7 @@ describe('Router', () => {
|
|||
);
|
||||
});
|
||||
it('throws if validation for a route is declared wrong', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
expect(() =>
|
||||
router.get(
|
||||
// we use 'any' because validate requires valid Type or function usage
|
||||
|
@ -41,7 +46,7 @@ describe('Router', () => {
|
|||
});
|
||||
|
||||
it('throws if options.body.output is not a valid value', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
expect(() =>
|
||||
router.post(
|
||||
// we use 'any' because TS already checks we cannot provide this body.output
|
||||
|
@ -58,14 +63,14 @@ describe('Router', () => {
|
|||
});
|
||||
|
||||
it('should default `output: "stream" and parse: false` when no body validation is required but not a GET', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post({ path: '/', validate: {} }, (context, req, res) => res.ok({}));
|
||||
const [route] = router.getRoutes();
|
||||
expect(route.options).toEqual({ body: { output: 'stream', parse: false } });
|
||||
});
|
||||
|
||||
it('should NOT default `output: "stream" and parse: false` when the user has specified body options (he cares about it)', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{ path: '/', options: { body: { maxBytes: 1 } }, validate: {} },
|
||||
(context, req, res) => res.ok({})
|
||||
|
@ -75,7 +80,7 @@ describe('Router', () => {
|
|||
});
|
||||
|
||||
it('should NOT default `output: "stream" and parse: false` when no body validation is required and GET', () => {
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: {} }, (context, req, res) => res.ok({}));
|
||||
const [route] = router.getRoutes();
|
||||
expect(route.options).toEqual({});
|
||||
|
|
|
@ -119,11 +119,14 @@ function validOptions(
|
|||
}
|
||||
|
||||
/** @internal */
|
||||
interface RouterOptions {
|
||||
export interface RouterOptions {
|
||||
/** Whether we are running in development */
|
||||
isDev?: boolean;
|
||||
/** Whether we are running in a serverless */
|
||||
isServerless?: boolean;
|
||||
/**
|
||||
* Which route resolution algo to use
|
||||
* @default 'oldest'
|
||||
*/
|
||||
versionedRouteResolution?: 'newest' | 'oldest';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +146,7 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
|
|||
public readonly routerPath: string,
|
||||
private readonly log: Logger,
|
||||
private readonly enhanceWithContext: ContextEnhancer<any, any, any, any, any>,
|
||||
private readonly options: RouterOptions = { isDev: false, isServerless: false }
|
||||
private readonly options: RouterOptions
|
||||
) {
|
||||
const buildMethod =
|
||||
<Method extends RouteMethod>(method: Method) =>
|
||||
|
@ -220,7 +223,7 @@ export class Router<Context extends RequestHandlerContextBase = RequestHandlerCo
|
|||
this.versionedRouter = CoreVersionedRouter.from({
|
||||
router: this,
|
||||
isDev: this.options.isDev,
|
||||
defaultHandlerResolutionStrategy: this.options.isServerless ? 'newest' : 'oldest',
|
||||
defaultHandlerResolutionStrategy: this.options.versionedRouteResolution,
|
||||
});
|
||||
}
|
||||
return this.versionedRouter;
|
||||
|
|
|
@ -18,8 +18,12 @@ export type {
|
|||
} from './src/types';
|
||||
export { BasePath } from './src/base_path_service';
|
||||
|
||||
export { cspConfig, CspConfig } from './src/csp';
|
||||
export { cspConfig, CspConfig, type CspConfigType } from './src/csp';
|
||||
|
||||
export { externalUrlConfig, ExternalUrlConfig } from './src/external_url';
|
||||
export {
|
||||
externalUrlConfig,
|
||||
ExternalUrlConfig,
|
||||
type ExternalUrlConfigType,
|
||||
} from './src/external_url';
|
||||
|
||||
export { createCookieSessionStorageFactory } from './src/cookie_session_storage';
|
||||
|
|
|
@ -126,6 +126,10 @@ Object {
|
|||
],
|
||||
"truststore": Object {},
|
||||
},
|
||||
"versioned": Object {
|
||||
"strictClientVersionCheck": true,
|
||||
"versionResolution": "oldest",
|
||||
},
|
||||
"xsrf": Object {
|
||||
"allowlist": Array [],
|
||||
"disableProtection": false,
|
||||
|
|
|
@ -167,6 +167,14 @@ const configSchema = schema.object(
|
|||
}
|
||||
),
|
||||
restrictInternalApis: schema.boolean({ defaultValue: false }), // allow access to internal routes by default to prevent breaking changes in current offerings
|
||||
versioned: schema.object({
|
||||
// Which handler resolution algo to use: "newest" or "oldest"
|
||||
versionResolution: schema.oneOf([schema.literal('newest'), schema.literal('oldest')], {
|
||||
defaultValue: 'oldest',
|
||||
}),
|
||||
// Whether we enforce version checks on client requests
|
||||
strictClientVersionCheck: schema.boolean({ defaultValue: true }),
|
||||
}),
|
||||
},
|
||||
{
|
||||
validate: (rawConfig) => {
|
||||
|
@ -239,6 +247,7 @@ export class HttpConfig implements IHttpConfig {
|
|||
public externalUrl: IExternalUrlConfig;
|
||||
public xsrf: { disableProtection: boolean; allowlist: string[] };
|
||||
public requestId: { allowFromAnyIp: boolean; ipAllowlist: string[] };
|
||||
public versioned: { versionResolution: 'newest' | 'oldest'; strictClientVersionCheck: boolean };
|
||||
public shutdownTimeout: Duration;
|
||||
public restrictInternalApis: boolean;
|
||||
|
||||
|
@ -286,6 +295,7 @@ export class HttpConfig implements IHttpConfig {
|
|||
|
||||
this.restrictInternalApis = rawHttpConfig.restrictInternalApis;
|
||||
this.eluMonitor = rawHttpConfig.eluMonitor;
|
||||
this.versioned = rawHttpConfig.versioned;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import type {
|
|||
RouteValidationFunction,
|
||||
RequestHandlerContextBase,
|
||||
} from '@kbn/core-http-server';
|
||||
import { Router } from '@kbn/core-http-router-server-internal';
|
||||
import { Router, type RouterOptions } from '@kbn/core-http-router-server-internal';
|
||||
import { HttpConfig } from './http_config';
|
||||
import { HttpServer } from './http_server';
|
||||
import { Readable } from 'stream';
|
||||
|
@ -30,6 +30,11 @@ import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
|
|||
import moment from 'moment';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
const routerOptions: RouterOptions = {
|
||||
isDev: false,
|
||||
versionedRouteResolution: 'oldest',
|
||||
};
|
||||
|
||||
const cookieOptions = {
|
||||
name: 'sid',
|
||||
encryptionKey: 'something_at_least_32_characters',
|
||||
|
@ -144,14 +149,14 @@ test('does not allow router registration after server is listening', async () =>
|
|||
|
||||
const { registerRouter } = await server.setup(config);
|
||||
|
||||
const router1 = new Router('/foo', logger, enhanceWithContext);
|
||||
const router1 = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
expect(() => registerRouter(router1)).not.toThrowError();
|
||||
|
||||
await server.start();
|
||||
|
||||
expect(server.isListening()).toBe(true);
|
||||
|
||||
const router2 = new Router('/bar', logger, enhanceWithContext);
|
||||
const router2 = new Router('/bar', logger, enhanceWithContext, routerOptions);
|
||||
expect(() => registerRouter(router2)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Routers can be registered only when HTTP server is stopped."`
|
||||
);
|
||||
|
@ -162,19 +167,19 @@ test('allows router registration after server is listening via `registerRouterAf
|
|||
|
||||
const { registerRouterAfterListening } = await server.setup(config);
|
||||
|
||||
const router1 = new Router('/foo', logger, enhanceWithContext);
|
||||
const router1 = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
expect(() => registerRouterAfterListening(router1)).not.toThrowError();
|
||||
|
||||
await server.start();
|
||||
|
||||
expect(server.isListening()).toBe(true);
|
||||
|
||||
const router2 = new Router('/bar', logger, enhanceWithContext);
|
||||
const router2 = new Router('/bar', logger, enhanceWithContext, routerOptions);
|
||||
expect(() => registerRouterAfterListening(router2)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('valid params', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.get(
|
||||
{
|
||||
|
@ -204,7 +209,7 @@ test('valid params', async () => {
|
|||
});
|
||||
|
||||
test('invalid params', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.get(
|
||||
{
|
||||
|
@ -238,7 +243,7 @@ test('invalid params', async () => {
|
|||
});
|
||||
|
||||
test('valid query', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.get(
|
||||
{
|
||||
|
@ -269,7 +274,7 @@ test('valid query', async () => {
|
|||
});
|
||||
|
||||
test('invalid query', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.get(
|
||||
{
|
||||
|
@ -303,7 +308,7 @@ test('invalid query', async () => {
|
|||
});
|
||||
|
||||
test('valid body', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.post(
|
||||
{
|
||||
|
@ -338,7 +343,7 @@ test('valid body', async () => {
|
|||
});
|
||||
|
||||
test('valid body with validate function', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.post(
|
||||
{
|
||||
|
@ -376,7 +381,7 @@ test('valid body with validate function', async () => {
|
|||
});
|
||||
|
||||
test('not inline validation - specifying params', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
const bodyValidation = (
|
||||
{ bar, baz }: any = {},
|
||||
|
@ -419,7 +424,7 @@ test('not inline validation - specifying params', async () => {
|
|||
});
|
||||
|
||||
test('not inline validation - specifying validation handler', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
const bodyValidation: RouteValidationFunction<{ bar: string; baz: number }> = (
|
||||
{ bar, baz } = {},
|
||||
|
@ -463,7 +468,7 @@ test('not inline validation - specifying validation handler', async () => {
|
|||
|
||||
// https://github.com/elastic/kibana/issues/47047
|
||||
test('not inline handler - KibanaRequest', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
const handler = (
|
||||
context: RequestHandlerContextBase,
|
||||
|
@ -512,7 +517,7 @@ test('not inline handler - KibanaRequest', async () => {
|
|||
});
|
||||
|
||||
test('not inline handler - RequestHandler', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
const handler: RequestHandler<unknown, unknown, { bar: string; baz: number }> = (
|
||||
context,
|
||||
|
@ -561,7 +566,7 @@ test('not inline handler - RequestHandler', async () => {
|
|||
});
|
||||
|
||||
test('invalid body', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.post(
|
||||
{
|
||||
|
@ -596,7 +601,7 @@ test('invalid body', async () => {
|
|||
});
|
||||
|
||||
test('handles putting', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.put(
|
||||
{
|
||||
|
@ -627,7 +632,7 @@ test('handles putting', async () => {
|
|||
});
|
||||
|
||||
test('handles deleting', async () => {
|
||||
const router = new Router('/foo', logger, enhanceWithContext);
|
||||
const router = new Router('/foo', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.delete(
|
||||
{
|
||||
|
@ -667,7 +672,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
|
|||
rewriteBasePath: false,
|
||||
} as HttpConfig;
|
||||
|
||||
const router = new Router('/', logger, enhanceWithContext);
|
||||
const router = new Router('/', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'value:/' }));
|
||||
router.get({ path: '/foo', validate: false }, (context, req, res) =>
|
||||
res.ok({ body: 'value:/foo' })
|
||||
|
@ -722,7 +727,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
|
|||
rewriteBasePath: true,
|
||||
} as HttpConfig;
|
||||
|
||||
const router = new Router('/', logger, enhanceWithContext);
|
||||
const router = new Router('/', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'value:/' }));
|
||||
router.get({ path: '/foo', validate: false }, (context, req, res) =>
|
||||
res.ok({ body: 'value:/foo' })
|
||||
|
@ -772,7 +777,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
|
|||
});
|
||||
|
||||
test('with defined `redirectHttpFromPort`', async () => {
|
||||
const router = new Router('/', logger, enhanceWithContext);
|
||||
const router = new Router('/', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: 'value:/' }));
|
||||
|
||||
const { registerRouter } = await server.setup(configWithSSL);
|
||||
|
@ -802,7 +807,7 @@ test('allows attaching metadata to attach meta-data tag strings to a route', asy
|
|||
const tags = ['my:tag'];
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/with-tags', validate: false, options: { tags } }, (context, req, res) =>
|
||||
res.ok({ body: { tags: req.route.options.tags } })
|
||||
);
|
||||
|
@ -821,7 +826,7 @@ test('allows declaring route access to flag a route as public or internal', asyn
|
|||
const access = 'internal';
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/with-access', validate: false, options: { access } }, (context, req, res) =>
|
||||
res.ok({ body: { access: req.route.options.access } })
|
||||
);
|
||||
|
@ -839,7 +844,7 @@ test('allows declaring route access to flag a route as public or internal', asyn
|
|||
test('infers access flag from path if not defined', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/internal/foo', validate: false }, (context, req, res) =>
|
||||
res.ok({ body: { access: req.route.options.access } })
|
||||
);
|
||||
|
@ -870,7 +875,7 @@ test('infers access flag from path if not defined', async () => {
|
|||
test('exposes route details of incoming request to a route handler', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: req.route }));
|
||||
registerRouter(router);
|
||||
|
||||
|
@ -893,7 +898,7 @@ test('exposes route details of incoming request to a route handler', async () =>
|
|||
describe('conditional compression', () => {
|
||||
async function setupServer(innerConfig: HttpConfig) {
|
||||
const { registerRouter, server: innerServer } = await server.setup(innerConfig);
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
// we need the large body here so that compression would normally be used
|
||||
const largeRequest = {
|
||||
body: 'hello'.repeat(500),
|
||||
|
@ -1001,7 +1006,7 @@ describe('response headers', () => {
|
|||
keepaliveTimeout: 100_000,
|
||||
});
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: req.route }));
|
||||
registerRouter(router);
|
||||
|
||||
|
@ -1018,7 +1023,7 @@ describe('response headers', () => {
|
|||
test('default headers', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get({ path: '/', validate: false }, (context, req, res) => res.ok({ body: req.route }));
|
||||
registerRouter(router);
|
||||
|
||||
|
@ -1040,7 +1045,7 @@ describe('response headers', () => {
|
|||
test('exposes route details of incoming request to a route handler (POST + payload options)', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1080,7 +1085,7 @@ describe('body options', () => {
|
|||
test('should reject the request because the Content-Type in the request is not valid', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1102,7 +1107,7 @@ describe('body options', () => {
|
|||
test('should reject the request because the payload is too large', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1124,7 +1129,7 @@ describe('body options', () => {
|
|||
test('should not parse the content in the request', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1153,7 +1158,7 @@ describe('timeout options', () => {
|
|||
test('POST routes set the payload timeout', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1187,7 +1192,7 @@ describe('timeout options', () => {
|
|||
test('DELETE routes set the payload timeout', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.delete(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1220,7 +1225,7 @@ describe('timeout options', () => {
|
|||
test('PUT routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.put(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1253,7 +1258,7 @@ describe('timeout options', () => {
|
|||
test('PATCH routes set the payload timeout and automatically adjusts the idle socket timeout', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.patch(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1291,7 +1296,7 @@ describe('timeout options', () => {
|
|||
socketTimeout: 11000,
|
||||
});
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1324,7 +1329,7 @@ describe('timeout options', () => {
|
|||
socketTimeout: 11000,
|
||||
});
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.get(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1355,7 +1360,7 @@ describe('timeout options', () => {
|
|||
test('idleSocket timeout can be smaller than the payload timeout', async () => {
|
||||
const { registerRouter } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1382,7 +1387,7 @@ describe('timeout options', () => {
|
|||
test('should return a stream in the body', async () => {
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
router.put(
|
||||
{
|
||||
path: '/',
|
||||
|
@ -1409,7 +1414,7 @@ test('closes sockets on timeout', async () => {
|
|||
...config,
|
||||
socketTimeout: 1000,
|
||||
});
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, routerOptions);
|
||||
|
||||
router.get({ path: '/a', validate: false }, async (context, req, res) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
|
|
@ -17,6 +17,6 @@ jest.mock('./http_server', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('./lifecycle_handlers', () => ({
|
||||
jest.mock('./register_lifecycle_handlers', () => ({
|
||||
registerCoreHandlers: jest.fn(),
|
||||
}));
|
||||
|
|
|
@ -18,6 +18,7 @@ import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
|||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
|
||||
import { Router } from '@kbn/core-http-router-server-internal';
|
||||
jest.mock('@kbn/core-http-router-server-internal');
|
||||
import { HttpService } from './http_service';
|
||||
import { HttpConfigType, config } from './http_config';
|
||||
import { cspConfig } from './csp';
|
||||
|
@ -480,3 +481,43 @@ test('does not start http server if configured with `autoListen:false`', async (
|
|||
expect(httpServer.start).not.toHaveBeenCalled();
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
test('passes versioned config to router', async () => {
|
||||
const configService = createConfigService({
|
||||
versioned: {
|
||||
versionResolution: 'newest',
|
||||
strictClientVersionCheck: false,
|
||||
},
|
||||
});
|
||||
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
setup: jest.fn().mockReturnValue({ server: fakeHapiServer, registerRouter: jest.fn() }),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
const prebootHttpServer = {
|
||||
isListening: () => false,
|
||||
setup: jest.fn().mockReturnValue({ server: fakeHapiServer, registerStaticDir: jest.fn() }),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mockHttpServer.mockImplementationOnce(() => prebootHttpServer);
|
||||
mockHttpServer.mockImplementationOnce(() => httpServer);
|
||||
|
||||
const service = new HttpService({ coreId, configService, env, logger });
|
||||
await service.preboot(prebootDeps);
|
||||
const { createRouter } = await service.setup(setupDeps);
|
||||
await service.stop();
|
||||
|
||||
createRouter('/foo');
|
||||
|
||||
expect(Router).toHaveBeenCalledTimes(1);
|
||||
expect(Router).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/foo',
|
||||
expect.any(Object), // logger
|
||||
expect.any(Function), // context enhancer
|
||||
expect.objectContaining({ isDev: true, versionedRouteResolution: 'newest' })
|
||||
);
|
||||
});
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
InternalHttpServiceSetup,
|
||||
InternalHttpServiceStart,
|
||||
} from './types';
|
||||
import { registerCoreHandlers } from './lifecycle_handlers';
|
||||
import { registerCoreHandlers } from './register_lifecycle_handlers';
|
||||
import { ExternalUrlConfigType, externalUrlConfig, ExternalUrlConfig } from './external_url';
|
||||
|
||||
export interface PrebootDeps {
|
||||
|
@ -129,7 +129,7 @@ export class HttpService
|
|||
path,
|
||||
this.log,
|
||||
prebootServerRequestHandlerContext.createHandler.bind(null, this.coreContext.coreId),
|
||||
{ isDev: this.env.mode.dev, isServerless: this.env.cliArgs.serverless }
|
||||
{ isDev: this.env.mode.dev, versionedRouteResolution: config.versioned.versionResolution }
|
||||
);
|
||||
|
||||
registerCallback(router);
|
||||
|
@ -175,7 +175,7 @@ export class HttpService
|
|||
const enhanceHandler = this.requestHandlerContext!.createHandler.bind(null, pluginId);
|
||||
const router = new Router<Context>(path, this.log, enhanceHandler, {
|
||||
isDev: this.env.mode.dev,
|
||||
isServerless: this.env.cliArgs.serverless,
|
||||
versionedRouteResolution: config.versioned.versionResolution,
|
||||
});
|
||||
registerRouter(router);
|
||||
return router;
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
createVersionCheckPostAuthHandler,
|
||||
createXsrfPostAuthHandler,
|
||||
} from './lifecycle_handlers';
|
||||
|
||||
import { HttpConfig } from './http_config';
|
||||
|
||||
type ToolkitMock = jest.Mocked<OnPreResponseToolkit & OnPostAuthToolkit & OnPreRoutingToolkit>;
|
||||
|
@ -55,6 +56,10 @@ const forgeRequest = ({
|
|||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('xsrf post-auth handler', () => {
|
||||
let toolkit: ToolkitMock;
|
||||
let responseFactory: ReturnType<typeof mockRouter.createResponseFactory>;
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Env } from '@kbn/config';
|
||||
import type { OnPostAuthHandler, OnPreResponseHandler } from '@kbn/core-http-server';
|
||||
import { isSafeMethod } from '@kbn/core-http-router-server-internal';
|
||||
import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common/src/constants';
|
||||
import { HttpConfig } from './http_config';
|
||||
import { LifecycleRegistrar } from './http_server';
|
||||
|
||||
const VERSION_HEADER = 'kbn-version';
|
||||
const XSRF_HEADER = 'kbn-xsrf';
|
||||
|
@ -100,18 +98,3 @@ export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPre
|
|||
return toolkit.next({ headers: additionalHeaders });
|
||||
};
|
||||
};
|
||||
|
||||
export const registerCoreHandlers = (
|
||||
registrar: LifecycleRegistrar,
|
||||
config: HttpConfig,
|
||||
env: Env
|
||||
) => {
|
||||
// add headers based on config
|
||||
registrar.registerOnPreResponse(createCustomHeadersPreResponseHandler(config));
|
||||
// add extra request checks stuff
|
||||
registrar.registerOnPostAuth(createXsrfPostAuthHandler(config));
|
||||
// add check on version
|
||||
registrar.registerOnPostAuth(createVersionCheckPostAuthHandler(env.packageInfo.version));
|
||||
// add check on header if the route is internal
|
||||
registrar.registerOnPostAuth(createRestrictInternalRoutesPostAuthHandler(config)); // strictly speaking, we should have access to route.options.access from the request on postAuth
|
||||
};
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
jest.mock('./lifecycle_handlers', () => {
|
||||
const actual = jest.requireActual('./lifecycle_handlers');
|
||||
return {
|
||||
...actual,
|
||||
createVersionCheckPostAuthHandler: jest.fn(actual.createVersionCheckPostAuthHandler),
|
||||
};
|
||||
});
|
||||
|
||||
import { createTestEnv } from '@kbn/config-mocks';
|
||||
import type { HttpConfig } from './http_config';
|
||||
import { registerCoreHandlers } from './register_lifecycle_handlers';
|
||||
|
||||
import { createVersionCheckPostAuthHandler } from './lifecycle_handlers';
|
||||
|
||||
describe('registerCoreHandlers', () => {
|
||||
it('will not register client version checking if disabled via config', () => {
|
||||
const registrarMock = {
|
||||
registerAuth: jest.fn(),
|
||||
registerOnPostAuth: jest.fn(),
|
||||
registerOnPreAuth: jest.fn(),
|
||||
registerOnPreResponse: jest.fn(),
|
||||
registerOnPreRouting: jest.fn(),
|
||||
};
|
||||
|
||||
const config = {
|
||||
csp: { header: '' },
|
||||
xsrf: {},
|
||||
versioned: {
|
||||
versionResolution: 'newest',
|
||||
strictClientVersionCheck: false,
|
||||
},
|
||||
} as unknown as HttpConfig;
|
||||
|
||||
registerCoreHandlers(registrarMock, config, createTestEnv());
|
||||
expect(createVersionCheckPostAuthHandler).toHaveBeenCalledTimes(0);
|
||||
|
||||
config.versioned.strictClientVersionCheck = true;
|
||||
registerCoreHandlers(registrarMock, config, createTestEnv());
|
||||
expect(createVersionCheckPostAuthHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 type { Env } from '@kbn/config';
|
||||
import type { HttpConfig } from './http_config';
|
||||
import type { LifecycleRegistrar } from './http_server';
|
||||
import {
|
||||
createCustomHeadersPreResponseHandler,
|
||||
createRestrictInternalRoutesPostAuthHandler,
|
||||
createVersionCheckPostAuthHandler,
|
||||
createXsrfPostAuthHandler,
|
||||
} from './lifecycle_handlers';
|
||||
|
||||
export const registerCoreHandlers = (
|
||||
registrar: LifecycleRegistrar,
|
||||
config: HttpConfig,
|
||||
env: Env
|
||||
) => {
|
||||
// add headers based on config
|
||||
registrar.registerOnPreResponse(createCustomHeadersPreResponseHandler(config));
|
||||
// add extra request checks stuff
|
||||
registrar.registerOnPostAuth(createXsrfPostAuthHandler(config));
|
||||
if (config.versioned.strictClientVersionCheck !== false) {
|
||||
// add check on version
|
||||
registrar.registerOnPostAuth(createVersionCheckPostAuthHandler(env.packageInfo.version));
|
||||
}
|
||||
// add check on header if the route is internal
|
||||
registrar.registerOnPostAuth(createRestrictInternalRoutesPostAuthHandler(config)); // strictly speaking, we should have access to route.options.access from the request on postAuth
|
||||
};
|
|
@ -17,4 +17,4 @@ export type {
|
|||
InternalHttpServiceSetupMock,
|
||||
InternalHttpServiceStartMock,
|
||||
} from './src/http_service.mock';
|
||||
export { createCoreContext, createHttpServer } from './src/test_utils';
|
||||
export { createCoreContext, createHttpServer, createConfigService } from './src/test_utils';
|
||||
|
|
|
@ -14,14 +14,27 @@ import { Env } from '@kbn/config';
|
|||
import { getEnvOptions, configServiceMock } from '@kbn/config-mocks';
|
||||
import type { CoreContext } from '@kbn/core-base-server-internal';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { HttpService } from '@kbn/core-http-server-internal';
|
||||
import {
|
||||
type HttpConfigType,
|
||||
type ExternalUrlConfigType,
|
||||
type CspConfigType,
|
||||
HttpService,
|
||||
} from '@kbn/core-http-server-internal';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
const env = Env.createDefault(REPO_ROOT, getEnvOptions());
|
||||
|
||||
const logger = loggingSystemMock.create();
|
||||
|
||||
const createConfigService = () => {
|
||||
export const createConfigService = ({
|
||||
server,
|
||||
externalUrl,
|
||||
csp,
|
||||
}: Partial<{
|
||||
server: Partial<HttpConfigType>;
|
||||
externalUrl: Partial<ExternalUrlConfigType>;
|
||||
csp: Partial<CspConfigType>;
|
||||
}> = {}) => {
|
||||
const configService = configServiceMock.create();
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'server') {
|
||||
|
@ -51,11 +64,17 @@ const createConfigService = () => {
|
|||
keepaliveTimeout: 120_000,
|
||||
socketTimeout: 120_000,
|
||||
restrictInternalApis: false,
|
||||
versioned: {
|
||||
versionResolution: 'oldest',
|
||||
strictClientVersionCheck: true,
|
||||
},
|
||||
...server,
|
||||
} as any);
|
||||
}
|
||||
if (path === 'externalUrl') {
|
||||
return new BehaviorSubject({
|
||||
policy: [],
|
||||
...externalUrl,
|
||||
} as any);
|
||||
}
|
||||
if (path === 'csp') {
|
||||
|
@ -63,6 +82,7 @@ const createConfigService = () => {
|
|||
strict: false,
|
||||
disableEmbedding: false,
|
||||
warnLegacyBrowsers: true,
|
||||
...csp,
|
||||
});
|
||||
}
|
||||
throw new Error(`Unexpected config path: ${path}`);
|
||||
|
|
|
@ -8,26 +8,48 @@
|
|||
|
||||
import { parse as parseCookie } from 'tough-cookie';
|
||||
import supertest from 'supertest';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { duration as momentDuration } from 'moment';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { Env } from '@kbn/config';
|
||||
import { getEnvOptions, configServiceMock } from '@kbn/config-mocks';
|
||||
import { getEnvOptions } from '@kbn/config-mocks';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
import type { CoreContext } from '@kbn/core-base-server-internal';
|
||||
import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
|
||||
import { ensureRawRequest } from '@kbn/core-http-router-server-internal';
|
||||
import { HttpService, createCookieSessionStorageFactory } from '@kbn/core-http-server-internal';
|
||||
import { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import { httpServerMock, createConfigService } from '@kbn/core-http-server-mocks';
|
||||
|
||||
let server: HttpService;
|
||||
|
||||
let logger: ReturnType<typeof loggingSystemMock.create>;
|
||||
let env: Env;
|
||||
let coreContext: CoreContext;
|
||||
const configService = configServiceMock.create();
|
||||
|
||||
const configService = createConfigService({
|
||||
server: {
|
||||
hosts: ['http://1.2.3.4'],
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
shutdownTimeout: momentDuration('5s'),
|
||||
autoListen: true,
|
||||
healthCheck: {
|
||||
delay: 2000,
|
||||
},
|
||||
ssl: {
|
||||
verificationMode: 'none',
|
||||
} as any,
|
||||
compression: { enabled: true, brotli: { enabled: false } as any },
|
||||
xsrf: {
|
||||
disableProtection: true,
|
||||
allowlist: [],
|
||||
},
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
} as any,
|
||||
});
|
||||
const contextSetup = contextServiceMock.createSetupContract();
|
||||
const contextPreboot = contextServiceMock.createPrebootContract();
|
||||
|
||||
|
@ -40,50 +62,6 @@ const prebootDeps = {
|
|||
context: contextPreboot,
|
||||
};
|
||||
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'server') {
|
||||
return new BehaviorSubject({
|
||||
hosts: ['http://1.2.3.4'],
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
shutdownTimeout: momentDuration('5s'),
|
||||
autoListen: true,
|
||||
healthCheck: {
|
||||
delay: 2000,
|
||||
},
|
||||
ssl: {
|
||||
verificationMode: 'none',
|
||||
},
|
||||
compression: { enabled: true, brotli: { enabled: false } },
|
||||
xsrf: {
|
||||
disableProtection: true,
|
||||
allowlist: [],
|
||||
},
|
||||
customResponseHeaders: {},
|
||||
securityResponseHeaders: {},
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
if (path === 'externalUrl') {
|
||||
return new BehaviorSubject({
|
||||
policy: [],
|
||||
} as any);
|
||||
}
|
||||
if (path === 'csp') {
|
||||
return new BehaviorSubject({
|
||||
strict: false,
|
||||
disableEmbedding: false,
|
||||
warnLegacyBrowsers: true,
|
||||
});
|
||||
}
|
||||
throw new Error(`Unexpected config path: ${path}`);
|
||||
});
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
roles?: string[];
|
||||
|
|
|
@ -54,7 +54,10 @@ describe('Http server', () => {
|
|||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
innerServerListener = innerServer.listener;
|
||||
|
||||
const router = new Router('', logger, enhanceWithContext);
|
||||
const router = new Router('', logger, enhanceWithContext, {
|
||||
isDev: false,
|
||||
versionedRouteResolution: 'oldest',
|
||||
});
|
||||
router.post(
|
||||
{
|
||||
path: '/',
|
||||
|
|
|
@ -7,14 +7,10 @@
|
|||
*/
|
||||
|
||||
import supertest from 'supertest';
|
||||
import moment from 'moment';
|
||||
import { kibanaPackageJson } from '@kbn/repo-info';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { configServiceMock } from '@kbn/config-mocks';
|
||||
import type { IRouter, RouteRegistrar } from '@kbn/core-http-server';
|
||||
import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
|
||||
import { createHttpServer } from '@kbn/core-http-server-mocks';
|
||||
import { createConfigService, createHttpServer } from '@kbn/core-http-server-mocks';
|
||||
import { HttpService, HttpServerSetup } from '@kbn/core-http-server-internal';
|
||||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
|
||||
|
@ -31,97 +27,29 @@ const setupDeps = {
|
|||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
interface HttpConfigTestOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
const setUpDefaultServerConfig = ({ enabled }: HttpConfigTestOptions = {}) =>
|
||||
({
|
||||
hosts: ['localhost'],
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
shutdownTimeout: moment.duration(30, 'seconds'),
|
||||
autoListen: true,
|
||||
ssl: {
|
||||
enabled: false,
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
compression: { enabled: true, brotli: { enabled: false } },
|
||||
name: kibanaName,
|
||||
securityResponseHeaders: {
|
||||
// reflects default config
|
||||
strictTransportSecurity: null,
|
||||
xContentTypeOptions: 'nosniff',
|
||||
referrerPolicy: 'strict-origin-when-cross-origin',
|
||||
permissionsPolicy: null,
|
||||
crossOriginOpenerPolicy: 'same-origin',
|
||||
},
|
||||
customResponseHeaders: {
|
||||
'some-header': 'some-value',
|
||||
'referrer-policy': 'strict-origin', // overrides a header that is defined by securityResponseHeaders
|
||||
},
|
||||
xsrf: { disableProtection: false, allowlist: [allowlistedTestPath] },
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
restrictInternalApis: enabled ?? false, // reflects default for public routes
|
||||
} as any);
|
||||
|
||||
describe('core lifecycle handlers', () => {
|
||||
let server: HttpService;
|
||||
let innerServer: HttpServerSetup['server'];
|
||||
let router: IRouter;
|
||||
|
||||
beforeEach(async () => {
|
||||
const configService = configServiceMock.create();
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'server') {
|
||||
return new BehaviorSubject({
|
||||
hosts: ['localhost'],
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
shutdownTimeout: moment.duration(30, 'seconds'),
|
||||
autoListen: true,
|
||||
ssl: {
|
||||
enabled: false,
|
||||
},
|
||||
cors: {
|
||||
enabled: false,
|
||||
},
|
||||
compression: { enabled: true, brotli: { enabled: false } },
|
||||
name: kibanaName,
|
||||
securityResponseHeaders: {
|
||||
// reflects default config
|
||||
strictTransportSecurity: null,
|
||||
xContentTypeOptions: 'nosniff',
|
||||
referrerPolicy: 'strict-origin-when-cross-origin',
|
||||
permissionsPolicy: null,
|
||||
crossOriginOpenerPolicy: 'same-origin',
|
||||
},
|
||||
customResponseHeaders: {
|
||||
'some-header': 'some-value',
|
||||
'referrer-policy': 'strict-origin', // overrides a header that is defined by securityResponseHeaders
|
||||
},
|
||||
xsrf: { disableProtection: false, allowlist: [allowlistedTestPath] },
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
ipAllowlist: [],
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
if (path === 'externalUrl') {
|
||||
return new BehaviorSubject({
|
||||
policy: [],
|
||||
} as any);
|
||||
}
|
||||
if (path === 'csp') {
|
||||
return new BehaviorSubject({
|
||||
strict: false,
|
||||
disableEmbedding: false,
|
||||
warnLegacyBrowsers: true,
|
||||
});
|
||||
}
|
||||
throw new Error(`Unexpected config path: ${path}`);
|
||||
const configService = createConfigService({
|
||||
server: {
|
||||
name: kibanaName,
|
||||
securityResponseHeaders: {
|
||||
// reflects default config
|
||||
strictTransportSecurity: null,
|
||||
xContentTypeOptions: 'nosniff',
|
||||
referrerPolicy: 'strict-origin-when-cross-origin',
|
||||
permissionsPolicy: null,
|
||||
crossOriginOpenerPolicy: 'same-origin',
|
||||
} as any,
|
||||
customResponseHeaders: {
|
||||
'some-header': 'some-value',
|
||||
'referrer-policy': 'strict-origin', // overrides a header that is defined by securityResponseHeaders
|
||||
},
|
||||
xsrf: { disableProtection: false, allowlist: [allowlistedTestPath] },
|
||||
},
|
||||
});
|
||||
server = createHttpServer({ configService });
|
||||
|
||||
|
@ -321,32 +249,13 @@ describe('core lifecycle handlers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('core lifecyle handers with restrict internal routes enforced', () => {
|
||||
describe('core lifecycle handlers with restrict internal routes enforced', () => {
|
||||
let server: HttpService;
|
||||
let innerServer: HttpServerSetup['server'];
|
||||
let router: IRouter;
|
||||
|
||||
beforeEach(async () => {
|
||||
const configService = configServiceMock.create();
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'server') {
|
||||
return new BehaviorSubject(setUpDefaultServerConfig({ enabled: true }));
|
||||
}
|
||||
if (path === 'externalUrl') {
|
||||
return new BehaviorSubject({
|
||||
policy: [],
|
||||
} as any);
|
||||
}
|
||||
if (path === 'csp') {
|
||||
return new BehaviorSubject({
|
||||
strict: false,
|
||||
disableEmbedding: false,
|
||||
warnLegacyBrowsers: true,
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected config path: ${path}`);
|
||||
});
|
||||
const configService = createConfigService({ server: { restrictInternalApis: true } });
|
||||
server = createHttpServer({ configService });
|
||||
|
||||
await server.preboot({ context: contextServiceMock.createPrebootContract() });
|
||||
|
@ -391,3 +300,45 @@ describe('core lifecyle handers with restrict internal routes enforced', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('core lifecycle handlers with no strict client version check', () => {
|
||||
const testRoute = '/version_check/test/route';
|
||||
let server: HttpService;
|
||||
let innerServer: HttpServerSetup['server'];
|
||||
let router: IRouter;
|
||||
|
||||
beforeEach(async () => {
|
||||
const configService = createConfigService({
|
||||
server: {
|
||||
versioned: {
|
||||
strictClientVersionCheck: false,
|
||||
versionResolution: 'newest',
|
||||
},
|
||||
},
|
||||
});
|
||||
server = createHttpServer({ configService });
|
||||
await server.preboot({ context: contextServiceMock.createPrebootContract() });
|
||||
const serverSetup = await server.setup(setupDeps);
|
||||
router = serverSetup.createRouter('/');
|
||||
router.get({ path: testRoute, validate: false }, (context, req, res) => {
|
||||
return res.ok({ body: 'ok' });
|
||||
});
|
||||
innerServer = serverSetup.server;
|
||||
await server.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('accepts requests that do not include a version header', async () => {
|
||||
await supertest(innerServer.listener).get(testRoute).expect(200, 'ok');
|
||||
});
|
||||
|
||||
it('accepts requests with any version passed in the version header', async () => {
|
||||
await supertest(innerServer.listener)
|
||||
.get(testRoute)
|
||||
.set(versionHeader, 'what-have-you')
|
||||
.expect(200, 'ok');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1891,7 +1891,10 @@ describe('registerRouterAfterListening', () => {
|
|||
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
const otherRouter = new Router('/test', loggerMock.create(), enhanceWithContext);
|
||||
const otherRouter = new Router('/test', loggerMock.create(), enhanceWithContext, {
|
||||
isDev: false,
|
||||
versionedRouteResolution: 'oldest',
|
||||
});
|
||||
otherRouter.get({ path: '/afterListening', validate: false }, (context, req, res) => {
|
||||
return res.ok({ body: 'hello from other router' });
|
||||
});
|
||||
|
@ -1923,7 +1926,10 @@ describe('registerRouterAfterListening', () => {
|
|||
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
const otherRouter = new Router('/test', loggerMock.create(), enhanceWithContext);
|
||||
const otherRouter = new Router('/test', loggerMock.create(), enhanceWithContext, {
|
||||
isDev: false,
|
||||
versionedRouteResolution: 'oldest',
|
||||
});
|
||||
otherRouter.get({ path: '/afterListening', validate: false }, (context, req, res) => {
|
||||
return res.ok({ body: 'hello from other router' });
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
import { contextServiceMock } from '@kbn/core-http-context-server-mocks';
|
||||
import { createHttpServer } from '@kbn/core-http-server-mocks';
|
||||
import { createHttpServer, createConfigService } from '@kbn/core-http-server-mocks';
|
||||
import type { HttpService } from '@kbn/core-http-server-internal';
|
||||
import type { IRouter } from '@kbn/core-http-server';
|
||||
import type { CliArgs } from '@kbn/config';
|
||||
|
@ -30,6 +30,18 @@ describe('Routing versioned requests', () => {
|
|||
server = createHttpServer({
|
||||
logger,
|
||||
env: createTestEnv({ envOptions: getEnvOptions({ cliArgs }) }),
|
||||
configService: createConfigService({
|
||||
// We manually sync the config in our mock at this point
|
||||
server:
|
||||
cliArgs.serverless === true
|
||||
? {
|
||||
versioned: {
|
||||
versionResolution: 'newest',
|
||||
strictClientVersionCheck: false,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
}),
|
||||
});
|
||||
await server.preboot({ context: contextServiceMock.createPrebootContract() });
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue