diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index e042142a54a0f..718638e1ba62b 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -5,27 +5,29 @@ * 2.0. */ -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import * as Rx from 'rxjs'; +import { CoreSetup, HttpServerInfo, PluginInitializerContext } from 'src/core/server'; import { coreMock } from 'src/core/server/mocks'; -import { LevelLogger } from '../lib'; -import { createMockConfigSchema } from '../test_helpers'; +import { LevelLogger } from '../lib/level_logger'; +import { createMockConfigSchema, createMockLevelLogger } from '../test_helpers'; +import { ReportingConfigType } from './'; import { createConfig$ } from './create_config'; +const createMockConfig = ( + mockInitContext: PluginInitializerContext +): Rx.Observable => mockInitContext.config.create(); + describe('Reporting server createConfig$', () => { let mockCoreSetup: CoreSetup; let mockInitContext: PluginInitializerContext; - let mockLogger: LevelLogger; + let mockLogger: jest.Mocked; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockInitContext = coreMock.createPluginInitializerContext( createMockConfigSchema({ kibanaServer: {} }) ); - mockLogger = { - warn: jest.fn(), - debug: jest.fn(), - clone: jest.fn().mockImplementation(() => mockLogger), - } as unknown as LevelLogger; + mockLogger = createMockLevelLogger(); }); afterEach(() => { @@ -37,19 +39,12 @@ describe('Reporting server createConfig$', () => { ...createMockConfigSchema({ kibanaServer: {} }), encryptionKey: undefined, }); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters - expect(result.kibanaServer).toMatchInlineSnapshot(` - Object { - "hostname": "localhost", - "port": 80, - "protocol": "http", - } - `); - expect((mockLogger.warn as any).mock.calls.length).toBe(1); - expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([ + expect(mockLogger.warn.mock.calls.length).toBe(1); + expect(mockLogger.warn.mock.calls[0]).toMatchObject([ 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', ]); }); @@ -60,10 +55,10 @@ describe('Reporting server createConfig$', () => { encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses the user-provided encryption key, reporting kibanaServer settings to override server info', async () => { @@ -77,7 +72,7 @@ describe('Reporting server createConfig$', () => { }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result).toMatchInlineSnapshot(` @@ -108,7 +103,7 @@ describe('Reporting server createConfig$', () => { }, } `); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses user-provided disableSandbox: false', async () => { @@ -118,11 +113,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: false } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses user-provided disableSandbox: true', async () => { @@ -132,11 +127,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: true } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('provides a default for disableSandbox', async () => { @@ -145,10 +140,49 @@ describe('Reporting server createConfig$', () => { encryptionKey: '888888888888888888888888888888888', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); + + for (const hostname of [ + '0', + '0.0', + '0.0.0', + '0.0.0.0', + '0000:0000:0000:0000:0000:0000:0000:0000', + '::', + ]) { + it(`apply failover logic when hostname is given as ${hostname}`, async () => { + mockInitContext = coreMock.createPluginInitializerContext( + createMockConfigSchema({ + encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa', + // overwrite settings added by createMockConfigSchema and apply the default settings + // TODO make createMockConfigSchema _not_ default xpack.reporting.kibanaServer.hostname to 'localhost' + kibanaServer: { + hostname: undefined, + port: undefined, + }, + }) + ); + mockCoreSetup.http.getServerInfo = jest.fn().mockImplementation( + (): HttpServerInfo => ({ + name: 'cool server', + hostname, + port: 5601, + protocol: 'http', + }) + ); + + const mockConfig$ = createMockConfig(mockInitContext); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); + expect(result.kibanaServer).toMatchObject({ + hostname: 'localhost', + port: 5601, + protocol: 'http', + }); + }); + } }); diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index b1d69d17334be..5de54a43582ab 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import crypto from 'crypto'; -import { upperFirst } from 'lodash'; +import ipaddr from 'ipaddr.js'; +import { sum, upperFirst } from 'lodash'; import { Observable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; import { CoreSetup } from 'src/core/server'; @@ -33,20 +33,29 @@ export function createConfig$( let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.randomEncryptionKey', { - defaultMessage: - 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + - 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', - }) + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + + 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' ); encryptionKey = crypto.randomBytes(16).toString('hex'); } const { kibanaServer: reportingServer } = config; const serverInfo = core.http.getServerInfo(); - // kibanaServer.hostname, default to server.host - const kibanaServerHostname = reportingServer.hostname + // set kibanaServer.hostname, default to server.host, don't allow "0.0.0.0" as it breaks in Windows + let kibanaServerHostname = reportingServer.hostname ? reportingServer.hostname : serverInfo.hostname; + + if ( + ipaddr.isValid(kibanaServerHostname) && + !sum(ipaddr.parse(kibanaServerHostname).toByteArray()) + ) { + logger.warn( + `Found 'server.host: "0.0.0.0"' in Kibana configuration. Reporting is not able to use this as the Kibana server hostname.` + + ` To enable PNG/PDF Reporting to work, 'xpack.reporting.kibanaServer.hostname: localhost' is automatically set in the configuration.` + + ` You can prevent this message by adding 'xpack.reporting.kibanaServer.hostname: localhost' in kibana.yml.` + ); + kibanaServerHostname = 'localhost'; + } // kibanaServer.port, default to server.port const kibanaServerPort = reportingServer.port ? reportingServer.port : serverInfo.port; // kibanaServer.protocol, default to server.protocol @@ -66,36 +75,24 @@ export function createConfig$( mergeMap(async (config) => { if (config.capture.browser.chromium.disableSandbox != null) { // disableSandbox was set by user - return config; + return { ...config }; } // disableSandbox was not set by user, apply default for OS const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' '); - logger.debug( - i18n.translate('xpack.reporting.serverConfig.osDetected', { - defaultMessage: `Running on OS: '{osName}'`, - values: { osName }, - }) - ); + logger.debug(`Running on OS: '{osName}'`); if (disableSandbox === true) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxDisabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`, - values: { - configKey: 'xpack.reporting.capture.browser.chromium.disableSandbox', - osName, - }, - }) + `Chromium sandbox provides an additional layer of protection, but is not supported for ${osName} OS.` + + ` Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` ); } else { logger.info( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxEnabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`, - values: { osName }, - }) + `Chromium sandbox provides an additional layer of protection, and is supported for ${osName} OS.` + + ` Automatically enabling Chromium sandbox.` ); } diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index 6ad7d03bd1a8f..7f1da5b55ccb6 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -288,11 +288,25 @@ describe('Reporting Config Schema', () => { `); }); - it(`logs the proper validation messages`, () => { - // kibanaServer - const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } }); - expect(throwValidationErr).toThrowError( - `[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).` - ); - }); + for (const address of ['0', '0.0', '0.0.0']) { + it(`fails to validate "kibanaServer.hostname" with an invalid hostname: "${address}"`, () => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError(`[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).`); + }); + } + + for (const address of ['0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::']) { + it(`fails to validate "kibanaServer.hostname" hostname as zero: "${address}"`, () => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError( + `[kibanaServer.hostname]: cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead` + ); + }); + } }); diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index 5b15260be06cb..4c56fc4c6db60 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -6,10 +6,22 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import ipaddr from 'ipaddr.js'; +import { sum } from 'lodash'; import moment from 'moment'; const KibanaServerSchema = schema.object({ - hostname: schema.maybe(schema.string({ hostname: true })), + hostname: schema.maybe( + schema.string({ + hostname: true, + validate(value) { + if (ipaddr.isValid(value) && !sum(ipaddr.parse(value).toByteArray())) { + // prevent setting a hostname that fails in Chromium on Windows + return `cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead`; + } + }, + }) + ), port: schema.maybe(schema.number()), protocol: schema.maybe( schema.string({ diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts index 1a8bfe7b70208..a6e6be47bdfcd 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts @@ -16,7 +16,6 @@ export function createMockLevelLogger() { const logger = new LevelLogger(loggingSystemMock.create()) as jest.Mocked; - logger.clone.mockImplementation(createMockLevelLogger); // logger.debug.mockImplementation(consoleLogger('debug')); // uncomment this to see debug logs in jest tests logger.info.mockImplementation(consoleLogger('info')); logger.warn.mockImplementation(consoleLogger('warn')); @@ -24,5 +23,7 @@ export function createMockLevelLogger() { logger.error.mockImplementation(consoleLogger('error')); logger.trace.mockImplementation(consoleLogger('trace')); + logger.clone.mockImplementation(() => logger); + return logger; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 857e97daa3515..47876265894ec 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19243,10 +19243,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全ページレイアウト", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "複数のページを使用します。ページごとに最大2のビジュアライゼーションが表示されます", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromiumサンドボックスは保護が強化されていますが、{osName} OSではサポートされていません。自動的に'{configKey}: true'を設定しています。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromiumサンドボックスは保護が強化され、{osName} OSでサポートされています。自動的にChromiumサンドボックスを有効にしています。", - "xpack.reporting.serverConfig.osDetected": "OSは'{osName}'で実行しています", - "xpack.reporting.serverConfig.randomEncryptionKey": "xpack.reporting.encryptionKey のランダムキーを生成しています。再起動時にセッションが無効にならないようにするには、kibana.yml でxpack.reporting.encryptionKey を設定するか、bin/kibana-encryption-keys コマンドを使用してください。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6c860a5b2cc4d..0fd4fea956a11 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19520,10 +19520,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全页面布局", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "使用多页,每页最多显示 2 个可视化", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromium 沙盒提供附加保护层,但不受 {osName} OS 支持。自动设置“{configKey}: true”。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromium 沙盒提供附加保护层,受 {osName} OS 支持。自动启用 Chromium 沙盒。", - "xpack.reporting.serverConfig.osDetected": "正在以下 OS 上运行:“{osName}”", - "xpack.reporting.serverConfig.randomEncryptionKey": "为 xpack.reporting.encryptionKey 生成随机密钥。为防止会话在重启时失效,请在 kibana.yml 中设置 xpack.reporting.encryptionKey 或使用 bin/kibana-encryption-keys 命令。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告",