From 416a9b7bb75a739f4f5098366a77dc931a8e3d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Mon, 4 Oct 2021 16:19:03 +0100 Subject: [PATCH] =?UTF-8?q?[console]=C2=A0Deprecate=20"proxyFilter"=20and?= =?UTF-8?q?=20"proxyConfig"=20on=208.x=20(#113555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/console/common/constants/index.ts | 9 ++ .../console/common/constants/plugin.ts | 9 ++ src/plugins/console/kibana.json | 4 +- src/plugins/console/server/config.ts | 87 ++++++++++----- src/plugins/console/server/index.ts | 9 +- src/plugins/console/server/plugin.ts | 22 +++- .../api/console/proxy/create_handler.ts | 44 +++++--- .../server/routes/api/console/proxy/mocks.ts | 32 ++++-- .../routes/api/console/proxy/params.test.ts | 104 ++++++++++-------- src/plugins/console/server/routes/index.ts | 6 +- 10 files changed, 214 insertions(+), 112 deletions(-) create mode 100644 src/plugins/console/common/constants/index.ts create mode 100644 src/plugins/console/common/constants/plugin.ts diff --git a/src/plugins/console/common/constants/index.ts b/src/plugins/console/common/constants/index.ts new file mode 100644 index 0000000000000..0a8dac9b7fff3 --- /dev/null +++ b/src/plugins/console/common/constants/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { MAJOR_VERSION } from './plugin'; diff --git a/src/plugins/console/common/constants/plugin.ts b/src/plugins/console/common/constants/plugin.ts new file mode 100644 index 0000000000000..cd301ec296395 --- /dev/null +++ b/src/plugins/console/common/constants/plugin.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export const MAJOR_VERSION = '8.0.0'; diff --git a/src/plugins/console/kibana.json b/src/plugins/console/kibana.json index 69c7176ff6a47..e2345514d76b9 100644 --- a/src/plugins/console/kibana.json +++ b/src/plugins/console/kibana.json @@ -1,12 +1,14 @@ { "id": "console", - "version": "kibana", + "version": "8.0.0", + "kibanaVersion": "kibana", "server": true, "ui": true, "owner": { "name": "Stack Management", "githubTeam": "kibana-stack-management" }, + "configPath": ["console"], "requiredPlugins": ["devTools", "share"], "optionalPlugins": ["usageCollection", "home"], "requiredBundles": ["esUiShared", "kibanaReact", "kibanaUtils", "home"] diff --git a/src/plugins/console/server/config.ts b/src/plugins/console/server/config.ts index 4e42e3c21d2ad..6d667fed081e8 100644 --- a/src/plugins/console/server/config.ts +++ b/src/plugins/console/server/config.ts @@ -6,37 +6,70 @@ * Side Public License, v 1. */ +import { SemVer } from 'semver'; import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from 'kibana/server'; -export type ConfigType = TypeOf; +import { MAJOR_VERSION } from '../common/constants'; -export const config = schema.object( - { - enabled: schema.boolean({ defaultValue: true }), - proxyFilter: schema.arrayOf(schema.string(), { defaultValue: ['.*'] }), - ssl: schema.object({ verify: schema.boolean({ defaultValue: false }) }, {}), - proxyConfig: schema.arrayOf( - schema.object({ - match: schema.object({ - protocol: schema.string({ defaultValue: '*' }), - host: schema.string({ defaultValue: '*' }), - port: schema.string({ defaultValue: '*' }), - path: schema.string({ defaultValue: '*' }), - }), - - timeout: schema.number(), - ssl: schema.object( - { - verify: schema.boolean(), - ca: schema.arrayOf(schema.string()), - cert: schema.string(), - key: schema.string(), - }, - { defaultValue: undefined } - ), +const kibanaVersion = new SemVer(MAJOR_VERSION); + +const baseSettings = { + enabled: schema.boolean({ defaultValue: true }), + ssl: schema.object({ verify: schema.boolean({ defaultValue: false }) }, {}), +}; + +// Settings only available in 7.x +const deprecatedSettings = { + proxyFilter: schema.arrayOf(schema.string(), { defaultValue: ['.*'] }), + proxyConfig: schema.arrayOf( + schema.object({ + match: schema.object({ + protocol: schema.string({ defaultValue: '*' }), + host: schema.string({ defaultValue: '*' }), + port: schema.string({ defaultValue: '*' }), + path: schema.string({ defaultValue: '*' }), }), - { defaultValue: [] } - ), + + timeout: schema.number(), + ssl: schema.object( + { + verify: schema.boolean(), + ca: schema.arrayOf(schema.string()), + cert: schema.string(), + key: schema.string(), + }, + { defaultValue: undefined } + ), + }), + { defaultValue: [] } + ), +}; + +const configSchema = schema.object( + { + ...baseSettings, }, { defaultValue: undefined } ); + +const configSchema7x = schema.object( + { + ...baseSettings, + ...deprecatedSettings, + }, + { defaultValue: undefined } +); + +export type ConfigType = TypeOf; +export type ConfigType7x = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: kibanaVersion.major < 8 ? configSchema7x : configSchema, + deprecations: ({ deprecate, unused }) => [ + deprecate('enabled', '8.0.0'), + deprecate('proxyFilter', '8.0.0'), + deprecate('proxyConfig', '8.0.0'), + unused('ssl'), + ], +}; diff --git a/src/plugins/console/server/index.ts b/src/plugins/console/server/index.ts index cd05652c62838..6ae518f5dc796 100644 --- a/src/plugins/console/server/index.ts +++ b/src/plugins/console/server/index.ts @@ -6,16 +6,11 @@ * Side Public License, v 1. */ -import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; +import { PluginInitializerContext } from 'kibana/server'; -import { ConfigType, config as configSchema } from './config'; import { ConsoleServerPlugin } from './plugin'; export { ConsoleSetup, ConsoleStart } from './types'; +export { config } from './config'; export const plugin = (ctx: PluginInitializerContext) => new ConsoleServerPlugin(ctx); - -export const config: PluginConfigDescriptor = { - deprecations: ({ deprecate, unused, rename }) => [deprecate('enabled', '8.0.0'), unused('ssl')], - schema: configSchema, -}; diff --git a/src/plugins/console/server/plugin.ts b/src/plugins/console/server/plugin.ts index a5f1ca6107600..613337b286fbf 100644 --- a/src/plugins/console/server/plugin.ts +++ b/src/plugins/console/server/plugin.ts @@ -7,10 +7,11 @@ */ import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { SemVer } from 'semver'; import { ProxyConfigCollection } from './lib'; import { SpecDefinitionsService, EsLegacyConfigService } from './services'; -import { ConfigType } from './config'; +import { ConfigType, ConfigType7x } from './config'; import { registerRoutes } from './routes'; @@ -23,7 +24,7 @@ export class ConsoleServerPlugin implements Plugin { esLegacyConfigService = new EsLegacyConfigService(); - constructor(private readonly ctx: PluginInitializerContext) { + constructor(private readonly ctx: PluginInitializerContext) { this.log = this.ctx.logger.get(); } @@ -34,10 +35,17 @@ export class ConsoleServerPlugin implements Plugin { save: true, }, })); - + const kibanaVersion = new SemVer(this.ctx.env.packageInfo.version); const config = this.ctx.config.get(); const globalConfig = this.ctx.config.legacy.get(); - const proxyPathFilters = config.proxyFilter.map((str: string) => new RegExp(str)); + + let pathFilters: RegExp[] | undefined; + let proxyConfigCollection: ProxyConfigCollection | undefined; + if (kibanaVersion.major < 8) { + // "pathFilters" and "proxyConfig" are only used in 7.x + pathFilters = (config as ConfigType7x).proxyFilter.map((str: string) => new RegExp(str)); + proxyConfigCollection = new ProxyConfigCollection((config as ConfigType7x).proxyConfig); + } this.esLegacyConfigService.setup(elasticsearch.legacy.config$); @@ -51,7 +59,6 @@ export class ConsoleServerPlugin implements Plugin { specDefinitionService: this.specDefinitionsService, }, proxy: { - proxyConfigCollection: new ProxyConfigCollection(config.proxyConfig), readLegacyESConfig: async (): Promise => { const legacyConfig = await this.esLegacyConfigService.readConfig(); return { @@ -59,8 +66,11 @@ export class ConsoleServerPlugin implements Plugin { ...legacyConfig, }; }, - pathFilters: proxyPathFilters, + // Deprecated settings (only used in 7.x): + proxyConfigCollection, + pathFilters, }, + kibanaVersion, }); return { diff --git a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts index 8ca5720d559ce..9ece066246e4a 100644 --- a/src/plugins/console/server/routes/api/console/proxy/create_handler.ts +++ b/src/plugins/console/server/routes/api/console/proxy/create_handler.ts @@ -9,6 +9,7 @@ import { Agent, IncomingMessage } from 'http'; import * as url from 'url'; import { pick, trimStart, trimEnd } from 'lodash'; +import { SemVer } from 'semver'; import { KibanaRequest, RequestHandler } from 'kibana/server'; @@ -58,17 +59,22 @@ function filterHeaders(originalHeaders: object, headersToKeep: string[]): object function getRequestConfig( headers: object, esConfig: ESConfigForProxy, - proxyConfigCollection: ProxyConfigCollection, - uri: string + uri: string, + kibanaVersion: SemVer, + proxyConfigCollection?: ProxyConfigCollection ): { agent: Agent; timeout: number; headers: object; rejectUnauthorized?: boolean } { const filteredHeaders = filterHeaders(headers, esConfig.requestHeadersWhitelist); const newHeaders = setHeaders(filteredHeaders, esConfig.customHeaders); - if (proxyConfigCollection.hasConfig()) { - return { - ...proxyConfigCollection.configForUri(uri), - headers: newHeaders, - }; + if (kibanaVersion.major < 8) { + // In 7.x we still support the proxyConfig setting defined in kibana.yml + // From 8.x we don't support it anymore so we don't try to read it here. + if (proxyConfigCollection!.hasConfig()) { + return { + ...proxyConfigCollection!.configForUri(uri), + headers: newHeaders, + }; + } } return { @@ -106,18 +112,23 @@ export const createHandler = ({ log, proxy: { readLegacyESConfig, pathFilters, proxyConfigCollection }, + kibanaVersion, }: RouteDependencies): RequestHandler => async (ctx, request, response) => { const { body, query } = request; const { path, method } = query; - if (!pathFilters.some((re) => re.test(path))) { - return response.forbidden({ - body: `Error connecting to '${path}':\n\nUnable to send requests to that path.`, - headers: { - 'Content-Type': 'text/plain', - }, - }); + if (kibanaVersion.major < 8) { + // The "console.proxyFilter" setting in kibana.yaml has been deprecated in 8.x + // We only read it on the 7.x branch + if (!pathFilters!.some((re) => re.test(path))) { + return response.forbidden({ + body: `Error connecting to '${path}':\n\nUnable to send requests to that path.`, + headers: { + 'Content-Type': 'text/plain', + }, + }); + } } const legacyConfig = await readLegacyESConfig(); @@ -134,8 +145,9 @@ export const createHandler = const { timeout, agent, headers, rejectUnauthorized } = getRequestConfig( request.headers, legacyConfig, - proxyConfigCollection, - uri.toString() + uri.toString(), + kibanaVersion, + proxyConfigCollection ); const requestHeaders = { diff --git a/src/plugins/console/server/routes/api/console/proxy/mocks.ts b/src/plugins/console/server/routes/api/console/proxy/mocks.ts index 010e35ab505af..d06ca90adf556 100644 --- a/src/plugins/console/server/routes/api/console/proxy/mocks.ts +++ b/src/plugins/console/server/routes/api/console/proxy/mocks.ts @@ -5,28 +5,41 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { SemVer } from 'semver'; jest.mock('../../../../lib/proxy_request', () => ({ proxyRequest: jest.fn(), })); import { duration } from 'moment'; +import { MAJOR_VERSION } from '../../../../../common/constants'; import { ProxyConfigCollection } from '../../../../lib'; import { RouteDependencies, ProxyDependencies } from '../../../../routes'; import { EsLegacyConfigService, SpecDefinitionsService } from '../../../../services'; import { coreMock, httpServiceMock } from '../../../../../../../core/server/mocks'; -const defaultProxyValue = Object.freeze({ - readLegacyESConfig: async () => ({ - requestTimeout: duration(30000), - customHeaders: {}, - requestHeadersWhitelist: [], - hosts: ['http://localhost:9200'], - }), - pathFilters: [/.*/], - proxyConfigCollection: new ProxyConfigCollection([]), +const kibanaVersion = new SemVer(MAJOR_VERSION); + +const readLegacyESConfig = async () => ({ + requestTimeout: duration(30000), + customHeaders: {}, + requestHeadersWhitelist: [], + hosts: ['http://localhost:9200'], +}); + +let defaultProxyValue = Object.freeze({ + readLegacyESConfig, }); +if (kibanaVersion.major < 8) { + // In 7.x we still support the "pathFilter" and "proxyConfig" kibana.yml settings + defaultProxyValue = Object.freeze({ + readLegacyESConfig, + pathFilters: [/.*/], + proxyConfigCollection: new ProxyConfigCollection([]), + }); +} + interface MockDepsArgument extends Partial> { proxy?: Partial; } @@ -51,5 +64,6 @@ export const getProxyRouteHandlerDeps = ({ } : defaultProxyValue, log, + kibanaVersion, }; }; diff --git a/src/plugins/console/server/routes/api/console/proxy/params.test.ts b/src/plugins/console/server/routes/api/console/proxy/params.test.ts index e08d2f8adecbf..edefb2f11f1f1 100644 --- a/src/plugins/console/server/routes/api/console/proxy/params.test.ts +++ b/src/plugins/console/server/routes/api/console/proxy/params.test.ts @@ -5,14 +5,17 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +import { SemVer } from 'semver'; import { kibanaResponseFactory } from '../../../../../../../core/server'; -import { getProxyRouteHandlerDeps } from './mocks'; -import { createResponseStub } from './stubs'; +import { getProxyRouteHandlerDeps } from './mocks'; // import need to come first +import { createResponseStub } from './stubs'; // import needs to come first +import { MAJOR_VERSION } from '../../../../../common/constants'; import * as requestModule from '../../../../lib/proxy_request'; - import { createHandler } from './create_handler'; +const kibanaVersion = new SemVer(MAJOR_VERSION); + describe('Console Proxy Route', () => { let handler: ReturnType; @@ -21,58 +24,71 @@ describe('Console Proxy Route', () => { }); describe('params', () => { - describe('pathFilters', () => { - describe('no matches', () => { - it('rejects with 403', async () => { - handler = createHandler( - getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] } }) - ); + if (kibanaVersion.major < 8) { + describe('pathFilters', () => { + describe('no matches', () => { + it('rejects with 403', async () => { + handler = createHandler( + getProxyRouteHandlerDeps({ + proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] }, + }) + ); - const { status } = await handler( - {} as any, - { query: { method: 'POST', path: '/baz/id' } } as any, - kibanaResponseFactory - ); + const { status } = await handler( + {} as any, + { query: { method: 'POST', path: '/baz/id' } } as any, + kibanaResponseFactory + ); - expect(status).toBe(403); + expect(status).toBe(403); + }); }); - }); - describe('one match', () => { - it('allows the request', async () => { - handler = createHandler( - getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] } }) - ); - (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); + describe('one match', () => { + it('allows the request', async () => { + handler = createHandler( + getProxyRouteHandlerDeps({ + proxy: { pathFilters: [/^\/foo\//, /^\/bar\//] }, + }) + ); + + (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); - const { status } = await handler( - {} as any, - { headers: {}, query: { method: 'POST', path: '/foo/id' } } as any, - kibanaResponseFactory - ); + const { status } = await handler( + {} as any, + { headers: {}, query: { method: 'POST', path: '/foo/id' } } as any, + kibanaResponseFactory + ); - expect(status).toBe(200); - expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + expect(status).toBe(200); + expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + }); }); - }); - describe('all match', () => { - it('allows the request', async () => { - handler = createHandler( - getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//] } }) - ); - (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); + describe('all match', () => { + it('allows the request', async () => { + handler = createHandler( + getProxyRouteHandlerDeps({ proxy: { pathFilters: [/^\/foo\//] } }) + ); + + (requestModule.proxyRequest as jest.Mock).mockResolvedValue(createResponseStub('foo')); - const { status } = await handler( - {} as any, - { headers: {}, query: { method: 'GET', path: '/foo/id' } } as any, - kibanaResponseFactory - ); + const { status } = await handler( + {} as any, + { headers: {}, query: { method: 'GET', path: '/foo/id' } } as any, + kibanaResponseFactory + ); - expect(status).toBe(200); - expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + expect(status).toBe(200); + expect((requestModule.proxyRequest as jest.Mock).mock.calls.length).toBe(1); + }); }); }); - }); + } else { + // jest requires to have at least one test in the file + test('dummy required test', () => { + expect(true).toBe(true); + }); + } }); }); diff --git a/src/plugins/console/server/routes/index.ts b/src/plugins/console/server/routes/index.ts index 2c46547f92f1b..3911e8cfabc60 100644 --- a/src/plugins/console/server/routes/index.ts +++ b/src/plugins/console/server/routes/index.ts @@ -7,6 +7,7 @@ */ import { IRouter, Logger } from 'kibana/server'; +import { SemVer } from 'semver'; import { EsLegacyConfigService, SpecDefinitionsService } from '../services'; import { ESConfigForProxy } from '../types'; @@ -18,8 +19,8 @@ import { registerSpecDefinitionsRoute } from './api/console/spec_definitions'; export interface ProxyDependencies { readLegacyESConfig: () => Promise; - pathFilters: RegExp[]; - proxyConfigCollection: ProxyConfigCollection; + pathFilters?: RegExp[]; // Only present in 7.x + proxyConfigCollection?: ProxyConfigCollection; // Only present in 7.x } export interface RouteDependencies { @@ -30,6 +31,7 @@ export interface RouteDependencies { esLegacyConfigService: EsLegacyConfigService; specDefinitionService: SpecDefinitionsService; }; + kibanaVersion: SemVer; } export const registerRoutes = (dependencies: RouteDependencies) => {