diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md b/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md index b05d28899f9d2..589529cf2a7f7 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.getstartservices.md @@ -1,17 +1,17 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md) - -## CoreSetup.getStartServices() method - -Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle. - -Signature: - -```typescript -getStartServices(): Promise<[CoreStart, TPluginsStart]>; -``` -Returns: - -`Promise<[CoreStart, TPluginsStart]>` - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md) + +## CoreSetup.getStartServices() method + +Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle. + +Signature: + +```typescript +getStartServices(): Promise<[CoreStart, TPluginsStart]>; +``` +Returns: + +`Promise<[CoreStart, TPluginsStart]>` + diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c36d649837e8a..325f7216122b5 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -1,32 +1,32 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) - -## CoreSetup interface - -Context passed to the plugins `setup` method. - -Signature: - -```typescript -export interface CoreSetup -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | -| [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | -| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | -| [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | -| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | -| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | -| [uuid](./kibana-plugin-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | - -## Methods - -| Method | Description | -| --- | --- | -| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | - + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) + +## CoreSetup interface + +Context passed to the plugins `setup` method. + +Signature: + +```typescript +export interface CoreSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) | +| [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | +| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | +| [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | +| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | +| [uuid](./kibana-plugin-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | + +## Methods + +| Method | Description | +| --- | --- | +| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md index 7e491cb0df912..6f12d64676ce9 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -20,9 +20,9 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [DEFAULT](./kibana-plugin-server.cspconfig.default.md) | static | CspConfig | | | [header](./kibana-plugin-server.cspconfig.header.md) | | string | | | [rules](./kibana-plugin-server.cspconfig.rules.md) | | string[] | | +| [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md) | | boolean | | | [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | | [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md b/docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md similarity index 56% rename from docs/development/core/server/kibana-plugin-server.cspconfig.default.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md index 56e6cf35cdd13..3320785935b21 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.ruleschangedfromdefault.md @@ -1,11 +1,11 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [DEFAULT](./kibana-plugin-server.cspconfig.default.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md) -## CspConfig.DEFAULT property +## CspConfig.rulesChangedFromDefault property Signature: ```typescript -static readonly DEFAULT: CspConfig; +readonly rulesChangedFromDefault: boolean; ``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.md b/docs/development/core/server/kibana-plugin-server.icspconfig.md index fb8188386a376..00107d561873f 100644 --- a/docs/development/core/server/kibana-plugin-server.icspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.md @@ -18,6 +18,7 @@ export interface ICspConfig | --- | --- | --- | | [header](./kibana-plugin-server.icspconfig.header.md) | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | | [rules](./kibana-plugin-server.icspconfig.rules.md) | string[] | The CSP rules used for Kibana. | +| [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md) | boolean | Flag indicating that the configuraion changes the csp rules from the defaults | | [strict](./kibana-plugin-server.icspconfig.strict.md) | boolean | Specify whether browsers that do not support CSP should be able to use Kibana. Use true to block and false to allow. | | [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) | boolean | Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. | diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md b/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md new file mode 100644 index 0000000000000..427c07d2d1e4d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.ruleschangedfromdefault.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md) + +## ICspConfig.rulesChangedFromDefault property + +Flag indicating that the configuraion changes the csp rules from the defaults + +Signature: + +```typescript +readonly rulesChangedFromDefault: boolean; +``` diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts index 22baf4a330416..3775aecb2db09 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts @@ -32,6 +32,7 @@ interface Status { interface ApiResponseStatus { name: string; uuid: string; + running_from_source?: true; version: { number: string; build_hash: string; @@ -58,6 +59,11 @@ export class KbnClientStatus { }); } + public async isDistributable() { + const status = await this.get(); + return !status.running_from_source; + } + /** * Get the overall/merged state */ diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 8bded9d403c21..364b91a30841c 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -43639,6 +43639,10 @@ class KbnClientStatus { path: 'api/status', }); } + async isDistributable() { + const status = await this.get(); + return !status.running_from_source; + } /** * Get the overall/merged state */ diff --git a/src/core/server/config/env.mock.ts b/src/core/server/config/env.mock.ts new file mode 100644 index 0000000000000..f9b4e0732c1cb --- /dev/null +++ b/src/core/server/config/env.mock.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Env } from './env'; + +export function createMockEnv(options: { dist?: boolean } = {}): Env { + return { + // required by CspConfig + packageInfo: { + dist: options.dist ?? true, + }, + } as any; +} diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 45fa8445791b0..15cddc6fccd34 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -18,6 +18,7 @@ */ import { CspConfig } from '.'; +import { createMockEnv } from '../config/env.mock'; // CSP rules aren't strictly additive, so any change can potentially expand or // restrict the policy in a way we consider a breaking change. For that reason, @@ -33,23 +34,10 @@ import { CspConfig } from '.'; // the nature of a change in defaults during a PR review. describe('CspConfig', () => { - test('DEFAULT', () => { - expect(CspConfig.DEFAULT).toMatchInlineSnapshot(` - CspConfig { - "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - "rules": Array [ - "script-src 'unsafe-eval' 'self'", - "worker-src blob: 'self'", - "style-src 'unsafe-inline' 'self'", - ], - "strict": true, - "warnLegacyBrowsers": true, - } - `); - }); - test('defaults from config', () => { - expect(new CspConfig()).toMatchInlineSnapshot(` + const cspConfig = new CspConfig(createMockEnv()); + + expect(cspConfig).toMatchInlineSnapshot(` CspConfig { "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", "rules": Array [ @@ -57,6 +45,7 @@ describe('CspConfig', () => { "worker-src blob: 'self'", "style-src 'unsafe-inline' 'self'", ], + "rulesChangedFromDefault": false, "strict": true, "warnLegacyBrowsers": true, } @@ -64,7 +53,9 @@ describe('CspConfig', () => { }); test('creates from partial config', () => { - expect(new CspConfig({ strict: false, warnLegacyBrowsers: false })).toMatchInlineSnapshot(` + const cspConfig = new CspConfig(createMockEnv(), { strict: false, warnLegacyBrowsers: false }); + + expect(cspConfig).toMatchInlineSnapshot(` CspConfig { "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", "rules": Array [ @@ -72,6 +63,7 @@ describe('CspConfig', () => { "worker-src blob: 'self'", "style-src 'unsafe-inline' 'self'", ], + "rulesChangedFromDefault": false, "strict": false, "warnLegacyBrowsers": false, } @@ -79,7 +71,7 @@ describe('CspConfig', () => { }); test('computes header from rules', () => { - const cspConfig = new CspConfig({ rules: ['alpha', 'beta', 'gamma'] }); + const cspConfig = new CspConfig(createMockEnv(), { rules: ['alpha', 'beta', 'gamma'] }); expect(cspConfig).toMatchInlineSnapshot(` CspConfig { @@ -89,6 +81,25 @@ describe('CspConfig', () => { "beta", "gamma", ], + "rulesChangedFromDefault": true, + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); + + test(`includes blob: style-src if env indicates we're running from source`, () => { + const cspConfig = new CspConfig(createMockEnv({ dist: false })); + + expect(cspConfig).toMatchInlineSnapshot(` + CspConfig { + "header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src blob: 'unsafe-inline' 'self'", + "rules": Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src blob: 'unsafe-inline' 'self'", + ], + "rulesChangedFromDefault": false, "strict": true, "warnLegacyBrowsers": true, } diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index bb57702a4a241..592a7209f4364 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -18,6 +18,7 @@ */ import { config } from './config'; +import { Env } from '../config'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); @@ -48,6 +49,12 @@ export interface ICspConfig { * in a `Content-Security-Policy` header. */ readonly header: string; + + /** + * Flag indicating that the configuraion changes the csp + * rules from the defaults + */ + readonly rulesChangedFromDefault: boolean; } /** @@ -55,23 +62,37 @@ export interface ICspConfig { * @public */ export class CspConfig implements ICspConfig { - static readonly DEFAULT = new CspConfig(); - public readonly rules: string[]; public readonly strict: boolean; public readonly warnLegacyBrowsers: boolean; public readonly header: string; + public readonly rulesChangedFromDefault: boolean; /** * Returns the default CSP configuration when passed with no config * @internal */ - constructor(rawCspConfig: Partial> = {}) { + constructor(env: Env, rawCspConfig?: Partial>) { const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; - this.rules = source.rules; + this.rules = source.rules.map(rule => { + // if we receive an env, and it indicates that this isn't a distributable, add `blob:` to the style csp rules + if (env && !env.packageInfo.dist && rule.startsWith('style-src ')) { + return rule.replace(/^style-src /, 'style-src blob: '); + } + + return rule; + }); this.strict = source.strict; this.warnLegacyBrowsers = source.warnLegacyBrowsers; - this.header = source.rules.join('; '); + this.header = this.rules.join('; '); + + // only check to see if the csp values are customized when `rawCspConfig` was received. + if (!rawCspConfig) { + this.rulesChangedFromDefault = false; + } else { + const defaultCsp = new CspConfig(env); + this.rulesChangedFromDefault = defaultCsp.header !== this.header; + } } } diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 73f44f3c5ab5c..b249dbb5b6a04 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -22,6 +22,7 @@ import { hostname } from 'os'; import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; +import { Env } from '../config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; @@ -148,7 +149,7 @@ export class HttpConfig { /** * @internal */ - constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType) { + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { this.autoListen = rawHttpConfig.autoListen; this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; @@ -162,7 +163,7 @@ export class HttpConfig { this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.ssl = new SslConfig(rawHttpConfig.ssl || {}); this.compression = rawHttpConfig.compression; - this.csp = new CspConfig(rawCspConfig); + this.csp = new CspConfig(env, rawCspConfig); this.xsrf = rawHttpConfig.xsrf; } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 6db1ca80ab437..7fc2f5d990824 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -21,6 +21,7 @@ import { Server } from 'hapi'; import { CspConfig } from '../csp'; import { mockRouter } from './router/router.mock'; import { configMock } from '../config/config.mock'; +import { createMockEnv } from '../config/env.mock'; import { InternalHttpServiceSetup } from './types'; import { HttpService } from './http_service'; import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; @@ -61,7 +62,7 @@ const createSetupContractMock = () => { registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), - csp: CspConfig.DEFAULT, + csp: new CspConfig(createMockEnv()), auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index ae9d53f9fd3db..8a86667a1a6bc 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -70,7 +70,7 @@ export class HttpService implements CoreService(httpConfig.path), configService.atPath(cspConfig.path), - ]).pipe(map(([http, csp]) => new HttpConfig(http, csp))); + ]).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); this.httpServer = new HttpServer(logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index c1322a5aa94db..d552339f1ea1c 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -29,6 +29,7 @@ import { defaultValidationErrorHandler, HapiValidationError, getServerOptions } import { HttpServer } from './http_server'; import { HttpConfig, config } from './http_config'; import { Router } from './router'; +import { createMockEnv } from '../config/env.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { ByteSizeValue } from '@kbn/config-schema'; @@ -120,7 +121,8 @@ describe('getServerOptions', () => { certificate: 'some-certificate-path', }, }), - {} as any + {} as any, + createMockEnv() ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` @@ -149,7 +151,8 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), - {} as any + {} as any, + createMockEnv() ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 07cc933033054..7862412fbad06 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -86,7 +86,7 @@ export class LegacyService implements CoreService { public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { - const { logger, configService } = coreContext; + const { logger, configService, env } = coreContext; this.log = logger.get('legacy-service'); this.devConfig$ = configService @@ -95,7 +95,7 @@ export class LegacyService implements CoreService { this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); } public async discoverPlugins(): Promise { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c0a8973d98a54..846c2e4d8c507 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -33,6 +33,7 @@ import { capabilitiesServiceMock } from './capabilities/capabilities_service.moc export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; export { configServiceMock } from './config/config_service.mock'; +import { createMockEnv } from './config/env.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; @@ -97,7 +98,7 @@ function createCoreSetupMock() { registerOnPostAuth: httpService.registerOnPostAuth, registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, - csp: CspConfig.DEFAULT, + csp: new CspConfig(createMockEnv()), isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), registerRouteHandlerContext: jest.fn(), diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index dce5ec64bfa66..aa9ed8ecdbd40 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -583,14 +583,14 @@ export interface CoreStart { // @public export class CspConfig implements ICspConfig { // @internal - constructor(rawCspConfig?: Partial>); - // (undocumented) - static readonly DEFAULT: CspConfig; + constructor(env: Env, rawCspConfig?: Partial>); // (undocumented) readonly header: string; // (undocumented) readonly rules: string[]; // (undocumented) + readonly rulesChangedFromDefault: boolean; + // (undocumented) readonly strict: boolean; // (undocumented) readonly warnLegacyBrowsers: boolean; @@ -774,6 +774,7 @@ export type IContextProvider, TContextName export interface ICspConfig { readonly header: string; readonly rules: string[]; + readonly rulesChangedFromDefault: boolean; readonly strict: boolean; readonly warnLegacyBrowsers: boolean; } diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts index 395cb60587832..584621a1ce13f 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts @@ -18,6 +18,8 @@ */ import { CspConfig, ICspConfig } from '../../../../../../core/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createMockEnv } from '../../../../../../core/server/config/env.mock'; import { createCspCollector } from './csp_collector'; const createMockKbnServer = () => ({ @@ -25,7 +27,7 @@ const createMockKbnServer = () => ({ setup: { core: { http: { - csp: new CspConfig(), + csp: new CspConfig(createMockEnv()), }, }, }, @@ -36,7 +38,7 @@ describe('csp collector', () => { let kbnServer: ReturnType; function updateCsp(config: Partial) { - kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config); + kbnServer.newPlatform.setup.core.http.csp = new CspConfig(createMockEnv(), config); } beforeEach(() => { diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts index 6622ed4bef478..dce4c7fe6fefc 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts @@ -18,7 +18,6 @@ */ import { Server } from 'hapi'; -import { CspConfig } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -26,7 +25,11 @@ export function createCspCollector(server: Server) { type: 'csp', isReady: () => true, async fetch() { - const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp; + const { + strict, + warnLegacyBrowsers, + rulesChangedFromDefault, + } = server.newPlatform.setup.core.http.csp; return { strict, @@ -34,7 +37,7 @@ export function createCspCollector(server: Server) { // It's important that we do not send the value of csp.header here as it // can be customized with values that can be identifiable to given // installs, such as URLs - rulesChangedFromDefault: header !== CspConfig.DEFAULT.header, + rulesChangedFromDefault, }; }, }; diff --git a/src/legacy/server/status/routes/api/register_status.js b/src/legacy/server/status/routes/api/register_status.js index 259a00667810f..55bf81f2c5f9a 100644 --- a/src/legacy/server/status/routes/api/register_status.js +++ b/src/legacy/server/status/routes/api/register_status.js @@ -18,6 +18,7 @@ */ import { wrapAuthConfig } from '../../wrap_auth_config'; +import { IS_KIBANA_DISTRIBUTABLE } from '../../../../utils/artifact_type'; const matchSnapshot = /-SNAPSHOT$/; @@ -35,6 +36,8 @@ export function registerStatusApi(kbnServer, server, config) { return { name: config.get('server.name'), uuid: config.get('server.uuid'), + // flag to help tests know that kibana is running from source, not included in distributable response + ...(IS_KIBANA_DISTRIBUTABLE ? {} : { running_from_source: true }), version: { number: config.get('pkg.version').replace(matchSnapshot, ''), build_hash: config.get('pkg.buildSha'), diff --git a/test/api_integration/apis/general/csp.js b/test/api_integration/apis/general/csp.js index 8c191703070d8..38893c371fb52 100644 --- a/test/api_integration/apis/general/csp.js +++ b/test/api_integration/apis/general/csp.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; export default function({ getService }) { const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); describe('csp smoke test', () => { it('app response sends content security policy headers', async () => { @@ -36,11 +37,12 @@ export default function({ getService }) { }) ); + const isDist = await kibanaServer.status.isDistributable(); const entries = Array.from(parsed.entries()); expect(entries).to.eql([ ['script-src', ["'unsafe-eval'", "'self'"]], ['worker-src', ['blob:', "'self'"]], - ['style-src', ["'unsafe-inline'", "'self'"]], + ['style-src', [...(isDist ? [] : ['blob:']), "'unsafe-inline'", "'self'"]], ]); }); }); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 1f5a64835416a..87979ca643a19 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -15,6 +15,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const config = getService('config'); + const kibanaServer = getService('kibanaServer'); describe('OpenID Connect Implicit Flow authentication', () => { describe('finishing handshake', () => { @@ -56,12 +57,17 @@ export default function({ getService }: FtrProviderContext) { }); await (dom.window as Record).__isScriptExecuted__; + const isDist = await kibanaServer.status.isDistributable(); // Check that proxy page is returned with proper headers. expect(response.headers['content-type']).to.be('text/html; charset=utf-8'); expect(response.headers['cache-control']).to.be('private, no-cache, no-store'); expect(response.headers['content-security-policy']).to.be( - `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'` + [ + `script-src 'unsafe-eval' 'self';`, + `worker-src blob: 'self';`, + `style-src ${isDist ? '' : 'blob: '}'unsafe-inline' 'self'`, + ].join(' ') ); // Check that script that forwards URL fragment worked correctly. diff --git a/x-pack/test/saml_api_integration/apis/security/saml_login.ts b/x-pack/test/saml_api_integration/apis/security/saml_login.ts index 0436d59906ea8..d4de4d601a36c 100644 --- a/x-pack/test/saml_api_integration/apis/security/saml_login.ts +++ b/x-pack/test/saml_api_integration/apis/security/saml_login.ts @@ -17,6 +17,7 @@ export default function({ getService }: FtrProviderContext) { const randomness = getService('randomness'); const supertest = getService('supertestWithoutAuth'); const config = getService('config'); + const kibanaServer = getService('kibanaServer'); const kibanaServerConfig = config.get('servers.kibana'); @@ -137,12 +138,17 @@ export default function({ getService }: FtrProviderContext) { }); await (dom.window as Record).__isScriptExecuted__; + const isDist = await kibanaServer.status.isDistributable(); // Check that proxy page is returned with proper headers. expect(response.headers['content-type']).to.be('text/html; charset=utf-8'); expect(response.headers['cache-control']).to.be('private, no-cache, no-store'); expect(response.headers['content-security-policy']).to.be( - `script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'` + [ + `script-src 'unsafe-eval' 'self';`, + `worker-src blob: 'self';`, + `style-src ${isDist ? '' : 'blob: '}'unsafe-inline' 'self'`, + ].join(' ') ); // Check that script that forwards URL fragment worked correctly. diff --git a/x-pack/test/saml_api_integration/config.ts b/x-pack/test/saml_api_integration/config.ts index 6ea29b0d9e56e..1d83f374e966c 100644 --- a/x-pack/test/saml_api_integration/config.ts +++ b/x-pack/test/saml_api_integration/config.ts @@ -20,6 +20,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('./apis')], servers: xPackAPITestsConfig.get('servers'), services: { + kibanaServer: kibanaAPITestsConfig.get('services.kibanaServer'), randomness: kibanaAPITestsConfig.get('services.randomness'), legacyEs: kibanaAPITestsConfig.get('services.legacyEs'), supertestWithoutAuth: xPackAPITestsConfig.get('services.supertestWithoutAuth'),