From ce943945564166b1ab64916eaaa9dfc971c7d713 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Tue, 10 Dec 2019 15:30:21 -0600 Subject: [PATCH 01/13] Move CSP options to new platform --- .github/CODEOWNERS | 1 + ...ana-plugin-server.cspoptions.directives.md | 13 + .../server/kibana-plugin-server.cspoptions.md | 23 ++ .../kibana-plugin-server.cspoptions.rules.md | 13 + .../kibana-plugin-server.cspoptions.strict.md | 13 + ...in-server.cspoptions.warnlegacybrowsers.md | 13 + ...ibana-plugin-server.default_csp_options.md | 13 + .../core/server/kibana-plugin-server.md | 2 + ...kibana-plugin-server.sharedglobalconfig.md | 1 + .../server/csp/csp.test.ts} | 46 ++-- src/core/server/csp/csp.ts | 86 +++++++ src/{legacy => core}/server/csp/index.ts | 14 +- src/core/server/index.ts | 1 + src/core/server/mocks.ts | 2 + src/core/server/plugins/plugin_context.ts | 7 +- src/core/server/plugins/types.ts | 3 + src/core/server/server.api.md | 24 +- src/core/server/server.ts | 2 + src/core/server/types.ts | 1 + .../csp_usage_collector/csp_collector.test.ts | 4 +- .../lib/csp_usage_collector/csp_collector.ts | 6 +- src/legacy/server/config/schema.js | 12 +- src/legacy/ui/ui_render/ui_render_mixin.js | 4 +- x-pack/legacy/plugins/security/index.js | 5 +- .../server/routes/api/v1/authenticate.js | 226 ++++++++++++++++++ 25 files changed, 474 insertions(+), 61 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.directives.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.rules.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.strict.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md create mode 100644 docs/development/core/server/kibana-plugin-server.default_csp_options.md rename src/{legacy/server/csp/index.test.ts => core/server/csp/csp.test.ts} (63%) create mode 100644 src/core/server/csp/csp.ts rename src/{legacy => core}/server/csp/index.ts (70%) create mode 100644 x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36a2cda841fa8..1f41ce968483e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,6 +88,7 @@ /src/legacy/server/status/ @elastic/kibana-platform # Security +/src/core/server/csp/ @elastic/kibana-security /x-pack/legacy/plugins/security/ @elastic/kibana-security /x-pack/legacy/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.directives.md b/docs/development/core/server/kibana-plugin-server.cspoptions.directives.md new file mode 100644 index 0000000000000..37ba72f09919c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspoptions.directives.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [directives](./kibana-plugin-server.cspoptions.directives.md) + +## CspOptions.directives property + +The CSP rules in a formatted directives string for use in a `Content-Security-Policy` header. + +Signature: + +```typescript +directives: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.md b/docs/development/core/server/kibana-plugin-server.cspoptions.md new file mode 100644 index 0000000000000..ad4062eb0c338 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspoptions.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) + +## CspOptions interface + +The CSP options used for Kibana. + +Signature: + +```typescript +export interface CspOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [directives](./kibana-plugin-server.cspoptions.directives.md) | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | +| [rules](./kibana-plugin-server.cspoptions.rules.md) | string[] | The CSP rules used for Kibana. | +| [strict](./kibana-plugin-server.cspoptions.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.cspoptions.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.cspoptions.rules.md b/docs/development/core/server/kibana-plugin-server.cspoptions.rules.md new file mode 100644 index 0000000000000..ae5a0012b99ef --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspoptions.rules.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [rules](./kibana-plugin-server.cspoptions.rules.md) + +## CspOptions.rules property + +The CSP rules used for Kibana. + +Signature: + +```typescript +rules: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.strict.md b/docs/development/core/server/kibana-plugin-server.cspoptions.strict.md new file mode 100644 index 0000000000000..ca113045e1819 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspoptions.strict.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [strict](./kibana-plugin-server.cspoptions.strict.md) + +## CspOptions.strict property + +Specify whether browsers that do not support CSP should be able to use Kibana. Use `true` to block and `false` to allow. + +Signature: + +```typescript +strict: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md new file mode 100644 index 0000000000000..b149e63860974 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspoptions.warnlegacybrowsers.md) + +## CspOptions.warnLegacyBrowsers property + +Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. + +Signature: + +```typescript +warnLegacyBrowsers: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.default_csp_options.md b/docs/development/core/server/kibana-plugin-server.default_csp_options.md new file mode 100644 index 0000000000000..b4e954ccf29ca --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.default_csp_options.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DEFAULT\_CSP\_OPTIONS](./kibana-plugin-server.default_csp_options.md) + +## DEFAULT\_CSP\_OPTIONS variable + +The default set of CSP options used for Kibana. + +Signature: + +```typescript +DEFAULT_CSP_OPTIONS: Readonly +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 06dcede0f2dfe..c4d57b5414eb9 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -50,6 +50,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | +| [CspOptions](./kibana-plugin-server.cspoptions.md) | The CSP options used for Kibana. | | [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | @@ -141,6 +142,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | +| [DEFAULT\_CSP\_OPTIONS](./kibana-plugin-server.default_csp_options.md) | The default set of CSP options used for Kibana. | | [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | | [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | diff --git a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md index 418d406d4c890..76657626f43d0 100644 --- a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md +++ b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md @@ -12,5 +12,6 @@ export declare type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; path: Pick; + csp: Pick; }>; ``` diff --git a/src/legacy/server/csp/index.test.ts b/src/core/server/csp/csp.test.ts similarity index 63% rename from src/legacy/server/csp/index.test.ts rename to src/core/server/csp/csp.test.ts index fbb63bd49bf6f..3dce0620898f5 100644 --- a/src/legacy/server/csp/index.test.ts +++ b/src/core/server/csp/csp.test.ts @@ -17,17 +17,12 @@ * under the License. */ -import { - createCSPRuleString, - DEFAULT_CSP_RULES, - DEFAULT_CSP_STRICT, - DEFAULT_CSP_WARN_LEGACY_BROWSERS, -} from './'; +import { createCspDirectives, DEFAULT_CSP_OPTIONS } from './csp'; // 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, // we test the default rules exactly so any change to those rules gets flagged -// for manual review. In otherwords, this test is intentionally fragile to draw +// for manual review. In other words, this test is intentionally fragile to draw // extra attention if defaults are modified in any way. // // A test failure here does not necessarily mean this change cannot be made, @@ -37,25 +32,28 @@ import { // The tests use inline snapshots to make it as easy as possible to identify // the nature of a change in defaults during a PR review. test('default CSP rules', () => { - expect(DEFAULT_CSP_RULES).toMatchInlineSnapshot(` - Array [ - "script-src 'unsafe-eval' 'self'", - "worker-src blob: 'self'", - "style-src 'unsafe-inline' 'self'", - ] + expect(DEFAULT_CSP_OPTIONS).toMatchInlineSnapshot(` + Object { + "directives": "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('CSP strict mode defaults to disabled', () => { - expect(DEFAULT_CSP_STRICT).toBe(true); -}); - -test('CSP legacy browser warning defaults to enabled', () => { - expect(DEFAULT_CSP_WARN_LEGACY_BROWSERS).toBe(true); -}); - -test('createCSPRuleString() converts an array of rules into a CSP header string', () => { - const csp = createCSPRuleString([`string-src 'self'`, 'worker-src blob:', 'img-src data: blob:']); +test('createCspDirectives() converts an array of rules into a CSP header string', () => { + const directives = createCspDirectives([ + `string-src 'self'`, + 'worker-src blob:', + 'img-src data: blob:', + ]); - expect(csp).toMatchInlineSnapshot(`"string-src 'self'; worker-src blob:; img-src data: blob:"`); + expect(directives).toMatchInlineSnapshot( + `"string-src 'self'; worker-src blob:; img-src data: blob:"` + ); }); diff --git a/src/core/server/csp/csp.ts b/src/core/server/csp/csp.ts new file mode 100644 index 0000000000000..e7803f83ea0db --- /dev/null +++ b/src/core/server/csp/csp.ts @@ -0,0 +1,86 @@ +/* + * 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 { TypeOf, schema } from '@kbn/config-schema'; + +/** + * The CSP options used for Kibana. + * @public + */ +export interface CspOptions { + /** + * The CSP rules in a formatted directives string for use + * in a `Content-Security-Policy` header. + */ + directives: string; + + /** + * The CSP rules used for Kibana. + */ + rules: string[]; + + /** + * Specify whether browsers that do not support CSP should be + * able to use Kibana. Use `true` to block and `false` to allow. + */ + strict: boolean; + + /** + * Specify whether users with legacy browsers should be warned + * about their lack of Kibana security compliance. + */ + warnLegacyBrowsers: boolean; +} + +export type CspConfigType = TypeOf; + +const DEFAULT_RULES = [ + `script-src 'unsafe-eval' 'self'`, + `worker-src blob: 'self'`, + `style-src 'unsafe-inline' 'self'`, +]; +const defaults = { + directives: createCspDirectives(DEFAULT_RULES), + rules: DEFAULT_RULES, + strict: true, + warnLegacyBrowsers: true, +}; +export const config = { + path: 'csp', + schema: schema.object({ + rules: schema.arrayOf(schema.string(), { defaultValue: defaults.rules }), + strict: schema.boolean({ defaultValue: defaults.strict }), + warnLegacyBrowsers: schema.boolean({ defaultValue: defaults.warnLegacyBrowsers }), + }), +}; + +/** + * The default set of CSP options used for Kibana. + * @public + */ +export const DEFAULT_CSP_OPTIONS: Readonly = Object.freeze(defaults); + +/** + * Converts an array of rules into a formatted directives string for use + * in a `Content-Security-Policy` header. + * @internal + */ +export function createCspDirectives(rules: string[]) { + return rules.join('; '); +} diff --git a/src/legacy/server/csp/index.ts b/src/core/server/csp/index.ts similarity index 70% rename from src/legacy/server/csp/index.ts rename to src/core/server/csp/index.ts index ae5cb63ad6ff8..db2d54d97bce1 100644 --- a/src/legacy/server/csp/index.ts +++ b/src/core/server/csp/index.ts @@ -17,16 +17,4 @@ * under the License. */ -export const DEFAULT_CSP_RULES = Object.freeze([ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, -]); - -export const DEFAULT_CSP_STRICT = true; - -export const DEFAULT_CSP_WARN_LEGACY_BROWSERS = true; - -export function createCSPRuleString(rules: string[]) { - return rules.join('; '); -} +export * from './csp'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index c304958f78bb7..a44428e51623f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,6 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; +export { CspOptions, DEFAULT_CSP_OPTIONS, createCspDirectives } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c07caaa04ba52..8aab2a829c594 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -19,6 +19,7 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { PluginInitializerContext, CoreSetup, CoreStart } from '.'; +import { DEFAULT_CSP_OPTIONS } from './csp'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -48,6 +49,7 @@ export function pluginInitializerContextConfigMock(config: T) { startupTimeout: duration('30s'), }, path: { data: '/tmp' }, + csp: DEFAULT_CSP_OPTIONS, }; const mock: jest.Mocked['config']> = { diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 6829784e6e0a1..20e23d5ab1f6c 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -28,6 +28,7 @@ import { PluginOpaqueId, SharedGlobalConfigKeys, } from './types'; +import { CspConfigType, config as cspConfig } from '../csp'; import { PathConfigType, config as pathConfig } from '../path'; import { KibanaConfigType, config as kibanaConfig } from '../kibana_config'; import { @@ -88,13 +89,15 @@ export function createPluginInitializerContext( globalConfig$: combineLatest( coreContext.configService.atPath(kibanaConfig.path), coreContext.configService.atPath(elasticsearchConfig.path), - coreContext.configService.atPath(pathConfig.path) + coreContext.configService.atPath(pathConfig.path), + coreContext.configService.atPath(cspConfig.path) ).pipe( - map(([kibana, elasticsearch, path]) => + map(([kibana, elasticsearch, path, csp]) => deepFreeze({ kibana: pick(kibana, SharedGlobalConfigKeys.kibana), elasticsearch: pick(elasticsearch, SharedGlobalConfigKeys.elasticsearch), path: pick(path, SharedGlobalConfigKeys.path), + csp: pick(csp, SharedGlobalConfigKeys.csp), }) ) ), diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index e717871912f46..91b29526fa455 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -24,6 +24,7 @@ import { RecursiveReadonly } from 'kibana/public'; import { ConfigPath, EnvironmentMode, PackageInfo, ConfigDeprecationProvider } from '../config'; import { LoggerFactory } from '../logging'; import { KibanaConfigType } from '../kibana_config'; +import { CspConfigType } from '../csp'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; import { PathConfigType } from '../path'; import { CoreSetup, CoreStart } from '..'; @@ -212,6 +213,7 @@ export const SharedGlobalConfigKeys = { kibana: ['defaultAppId', 'index'] as const, elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout', 'startupTimeout'] as const, path: ['data'] as const, + csp: ['rules', 'strict', 'warnLegacyBrowsers'] as const, }; /** @@ -221,6 +223,7 @@ export type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; path: Pick; + csp: Pick; }>; /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 18e76324ff309..d52ab9f8e27a2 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -574,6 +574,17 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; } +// @internal +export function createCspDirectives(rules: string[]): string; + +// @public +export interface CspOptions { + directives: string; + rules: string[]; + strict: boolean; + warnLegacyBrowsers: boolean; +} + // @public export interface CustomHttpResponseOptions { body?: T; @@ -582,6 +593,9 @@ export interface CustomHttpResponseOptions; + // @public (undocumented) export interface DeprecationAPIClientParams extends GenericParams { // (undocumented) @@ -1774,6 +1788,7 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ kibana: Pick; elasticsearch: Pick; path: Pick; + csp: Pick; }>; // @public @@ -1813,9 +1828,10 @@ export const validBodyOutput: readonly ["data", "stream"]; // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:224:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:225:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/server.ts b/src/core/server/server.ts index e7bc57ea5fb94..725a45f131992 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -35,6 +35,7 @@ import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; +import { config as cspConfig } from './csp'; import { config as elasticsearchConfig } from './elasticsearch'; import { config as httpConfig } from './http'; import { config as loggingConfig } from './logging'; @@ -218,6 +219,7 @@ export class Server { public async setupCoreConfig() { const schemas: Array<[ConfigPath, Type]> = [ [pathConfig.path, pathConfig.schema], + [cspConfig.path, cspConfig.schema], [elasticsearchConfig.path, elasticsearchConfig.schema], [loggingConfig.path, loggingConfig.schema], [httpConfig.path, httpConfig.schema], diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 4878fb9ccae19..640e87e73617f 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -22,3 +22,4 @@ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; +export { CspOptions } from './csp'; 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 36e7dc81d4708..7659325264e0c 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 @@ -19,7 +19,7 @@ import sinon from 'sinon'; import { Server } from 'hapi'; -import { DEFAULT_CSP_RULES } from '../../../../../server/csp'; +import { DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; import { createCspCollector } from './csp_collector'; interface MockConfig { @@ -85,7 +85,7 @@ test('does not arbitrarily fetch other csp configurations (e.g. whitelist only)' function setupCollector() { const mockConfig = { get: sinon.stub() }; - mockConfig.get.withArgs('csp.rules').returns(DEFAULT_CSP_RULES); + mockConfig.get.withArgs('csp.rules').returns(DEFAULT_CSP_OPTIONS.rules); mockConfig.get.withArgs('csp.strict').returns(true); mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(true); 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 9890aaf187a13..85522d8dac032 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,7 @@ */ import { Server } from 'hapi'; -import { createCSPRuleString, DEFAULT_CSP_RULES } from '../../../../../server/csp'; +import { createCspDirectives, DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -31,8 +31,8 @@ export function createCspCollector(server: Server) { // It's important that we do not send the value of csp.rules here as it // can be customized with values that can be identifiable to given // installs, such as URLs - const defaultRulesString = createCSPRuleString([...DEFAULT_CSP_RULES]); - const actualRulesString = createCSPRuleString(config.get('csp.rules')); + const defaultRulesString = createCspDirectives([...DEFAULT_CSP_OPTIONS.rules]); + const actualRulesString = createCspDirectives(config.get('csp.rules')); return { strict: config.get('csp.strict'), diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a19a39da0f6dd..102f09b9e2a4b 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -20,13 +20,9 @@ import Joi from 'joi'; import os from 'os'; import { join } from 'path'; +import { DEFAULT_CSP_OPTIONS } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getDataPath } from '../../../core/server/path'; // Still used by optimize config schema -import { - DEFAULT_CSP_RULES, - DEFAULT_CSP_STRICT, - DEFAULT_CSP_WARN_LEGACY_BROWSERS, -} from '../csp'; const HANDLED_IN_NEW_PLATFORM = Joi.any().description('This key is handled in the new platform ONLY'); export default () => Joi.object({ @@ -53,9 +49,9 @@ export default () => Joi.object({ }).default(), csp: Joi.object({ - rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_RULES), - strict: Joi.boolean().default(DEFAULT_CSP_STRICT), - warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_WARN_LEGACY_BROWSERS), + rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_OPTIONS.rules), + strict: Joi.boolean().default(DEFAULT_CSP_OPTIONS.strict), + warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_OPTIONS.warnLegacyBrowsers), }).default(), cpu: Joi.object({ diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 47e1e9e17c5c9..803e5839690c3 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -26,9 +26,9 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { AppBootstrap } from './bootstrap'; import { mergeVariables } from './lib'; +import { createCspDirectives } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; -import { createCSPRuleString } from '../../server/csp'; export function uiRenderMixin(kbnServer, server, config) { function replaceInjectedVars(request, injectedVars) { @@ -283,7 +283,7 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - const csp = createCSPRuleString(config.get('csp.rules')); + const csp = createCspDirectives(config.get('csp.rules')); response.header('content-security-policy', csp); return response; diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 3a6f3692bc0b6..40879b6d11a94 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -11,8 +11,7 @@ import { initLogoutView } from './server/routes/views/logout'; import { initLoggedOutView } from './server/routes/views/logged_out'; import { AuditLogger } from '../../server/lib/audit_logger'; import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; -import { KibanaRequest } from '../../../../src/core/server'; -import { createCSPRuleString } from '../../../../src/legacy/server/csp'; +import { KibanaRequest, createCspDirectives } from '../../../../src/core/server'; export const security = (kibana) => new kibana.Plugin({ id: 'security', @@ -127,7 +126,7 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: createCSPRuleString(config.get('csp.rules')), + cspRules: createCspDirectives(config.get('csp.rules')), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js new file mode 100644 index 0000000000000..9fd4a09ceb9da --- /dev/null +++ b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js @@ -0,0 +1,226 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import Joi from 'joi'; +import { schema } from '@kbn/config-schema'; +import { canRedirectRequest, wrapError, OIDCAuthenticationFlow } from '../../../../../../../plugins/security/server'; +import { KibanaRequest, createCspDirectives } from '../../../../../../../../src/core/server'; + +export function initAuthenticateApi({ authc: { login, logout }, __legacyCompat: { config } }, server) { + function prepareCustomResourceResponse(response, contentType) { + return response + .header('cache-control', 'private, no-cache, no-store') + .header('content-security-policy', createCspDirectives(server.config().get('csp.rules'))) + .type(contentType); + } + + server.route({ + method: 'POST', + path: '/api/security/v1/login', + config: { + auth: false, + validate: { + payload: Joi.object({ + username: Joi.string().required(), + password: Joi.string().required() + }) + }, + response: { + emptyStatusCode: 204, + } + }, + async handler(request, h) { + const { username, password } = request.payload; + + try { + // We should prefer `token` over `basic` if possible. + const providerToLoginWith = config.authc.providers.includes('token') + ? 'token' + : 'basic'; + const authenticationResult = await login(KibanaRequest.from(request), { + provider: providerToLoginWith, + value: { username, password } + }); + + if (!authenticationResult.succeeded()) { + throw Boom.unauthorized(authenticationResult.error); + } + + return h.response(); + } catch(err) { + throw wrapError(err); + } + } + }); + + /** + * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow + * is used, so that we can extract authentication response from URL fragment and send it to + * the `/api/security/v1/oidc` route. + */ + server.route({ + method: 'GET', + path: '/api/security/v1/oidc/implicit', + config: { auth: false }, + async handler(request, h) { + return prepareCustomResourceResponse( + h.response(` + + Kibana OpenID Connect Login + + `), + 'text/html' + ); + } + }); + + /** + * The route that accompanies `/api/security/v1/oidc/implicit` and renders a JavaScript snippet + * that extracts fragment part from the URL and send it to the `/api/security/v1/oidc` route. + * We need this separate endpoint because of default CSP policy that forbids inline scripts. + */ + server.route({ + method: 'GET', + path: '/api/security/v1/oidc/implicit.js', + config: { auth: false }, + async handler(request, h) { + return prepareCustomResourceResponse( + h.response(` + window.location.replace( + '${server.config().get('server.basePath')}/api/security/v1/oidc?authenticationResponseURI=' + + encodeURIComponent(window.location.href) + ); + `), + 'text/javascript' + ); + } + }); + + server.route({ + // POST is only allowed for Third Party initiated authentication + // Consider splitting this route into two (GET and POST) when it's migrated to New Platform. + method: ['GET', 'POST'], + path: '/api/security/v1/oidc', + config: { + auth: false, + validate: { + query: Joi.object().keys({ + iss: Joi.string().uri({ scheme: 'https' }), + login_hint: Joi.string(), + target_link_uri: Joi.string().uri(), + code: Joi.string(), + error: Joi.string(), + error_description: Joi.string(), + error_uri: Joi.string().uri(), + state: Joi.string(), + authenticationResponseURI: Joi.string(), + }).unknown(), + } + }, + async handler(request, h) { + try { + const query = request.query || {}; + const payload = request.payload || {}; + + // An HTTP GET request with a query parameter named `authenticationResponseURI` that includes URL fragment OpenID + // Connect Provider sent during implicit authentication flow to the Kibana own proxy page that extracted that URL + // fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details + // at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth + let loginAttempt; + if (query.authenticationResponseURI) { + loginAttempt = { + flow: OIDCAuthenticationFlow.Implicit, + authenticationResponseURI: query.authenticationResponseURI, + }; + } else if (query.code || query.error) { + // An HTTP GET request with a query parameter named `code` (or `error`) as the response to a successful (or + // failed) authentication from an OpenID Connect Provider during authorization code authentication flow. + // See more details at https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. + loginAttempt = { + flow: OIDCAuthenticationFlow.AuthorizationCode, + // We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway. + authenticationResponseURI: request.url.path, + }; + } else if (query.iss || payload.iss) { + // An HTTP GET request with a query parameter named `iss` or an HTTP POST request with the same parameter in the + // payload as part of a 3rd party initiated authentication. See more details at + // https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin + loginAttempt = { + flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, + iss: query.iss || payload.iss, + loginHint: query.login_hint || payload.login_hint, + }; + } + + if (!loginAttempt) { + throw Boom.badRequest('Unrecognized login attempt.'); + } + + // We handle the fact that the user might get redirected to Kibana while already having an session + // Return an error notifying the user they are already logged in. + const authenticationResult = await login(KibanaRequest.from(request), { + provider: 'oidc', + value: loginAttempt + }); + if (authenticationResult.succeeded()) { + return Boom.forbidden( + 'Sorry, you already have an active Kibana session. ' + + 'If you want to start a new one, please logout from the existing session first.' + ); + } + + if (authenticationResult.redirected()) { + return h.redirect(authenticationResult.redirectURL); + } + + throw Boom.unauthorized(authenticationResult.error); + } catch (err) { + throw wrapError(err); + } + } + }); + + server.route({ + method: 'GET', + path: '/api/security/v1/logout', + config: { + auth: false + }, + async handler(request, h) { + if (!canRedirectRequest(KibanaRequest.from(request))) { + throw Boom.badRequest('Client should be able to process redirect response.'); + } + + try { + const deauthenticationResult = await logout( + // Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any + // set of query string parameters (e.g. SAML/OIDC logout request parameters). + KibanaRequest.from(request, { + query: schema.object({}, { allowUnknowns: true }), + }) + ); + if (deauthenticationResult.failed()) { + throw wrapError(deauthenticationResult.error); + } + + return h.redirect( + deauthenticationResult.redirectURL || `${server.config().get('server.basePath')}/` + ); + } catch (err) { + throw wrapError(err); + } + } + }); + + server.route({ + method: 'GET', + path: '/api/security/v1/me', + handler(request) { + return request.auth.credentials; + } + }); +} From 51f2fb111e0f1f2fc595f529c560d5e34c79f7bd Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Wed, 11 Dec 2019 10:31:51 -0600 Subject: [PATCH 02/13] Expose SharedGlobalConfig from root --- .../core/server/kibana-plugin-server.sharedglobalconfig.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md index 76657626f43d0..9eb46eb220acc 100644 --- a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md +++ b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md @@ -4,7 +4,6 @@ ## SharedGlobalConfig type - Signature: ```typescript From 6b1f50f192aac94eb3ce2f6676d539b84d8a4952 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Thu, 12 Dec 2019 12:08:39 -0600 Subject: [PATCH 03/13] Derive CSP options from config --- .github/CODEOWNERS | 4 +- src/core/server/csp/config.ts | 44 ++++ src/core/server/csp/csp.test.ts | 14 +- src/core/server/csp/csp.ts | 65 +---- src/core/server/csp/index.ts | 2 + src/core/server/csp/types.ts | 47 ++++ src/core/server/index.ts | 2 +- src/core/server/plugins/types.ts | 2 +- src/core/server/server.api.md | 3 - .../lib/csp_usage_collector/csp_collector.ts | 13 +- src/legacy/server/config/schema.js | 7 +- src/legacy/ui/ui_render/ui_render_mixin.js | 4 +- x-pack/legacy/plugins/security/index.js | 4 +- .../server/routes/api/v1/authenticate.js | 226 ------------------ 14 files changed, 109 insertions(+), 328 deletions(-) create mode 100644 src/core/server/csp/config.ts create mode 100644 src/core/server/csp/types.ts delete mode 100644 x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1f41ce968483e..3142e0ff97749 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -80,7 +80,6 @@ /x-pack/plugins/licensing/ @elastic/kibana-platform /packages/kbn-config-schema/ @elastic/kibana-platform /src/legacy/server/config/ @elastic/kibana-platform -/src/legacy/server/csp/ @elastic/kibana-platform /src/legacy/server/http/ @elastic/kibana-platform /src/legacy/server/i18n/ @elastic/kibana-platform /src/legacy/server/logging/ @elastic/kibana-platform @@ -88,13 +87,12 @@ /src/legacy/server/status/ @elastic/kibana-platform # Security -/src/core/server/csp/ @elastic/kibana-security +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform /x-pack/legacy/plugins/security/ @elastic/kibana-security /x-pack/legacy/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security /x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security -/src/legacy/server/csp/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts new file mode 100644 index 0000000000000..d9b8735ab0e1c --- /dev/null +++ b/src/core/server/csp/config.ts @@ -0,0 +1,44 @@ +/* + * 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 { TypeOf, schema } from '@kbn/config-schema'; + +export type CspConfigType = TypeOf; + +const rules = [ + `script-src 'unsafe-eval' 'self'`, + `worker-src blob: 'self'`, + `style-src 'unsafe-inline' 'self'`, +]; + +export const config = { + // TODO: Move this to server.csp using config deprecations + // ? https://github.com/elastic/kibana/pull/52251 + path: 'csp', + schema: schema.object({ + rules: schema.arrayOf(schema.string(), { defaultValue: rules }), + directives: schema.string({ + // TODO: Consider making this bidirectional in the future + validate: value => (value ? 'Specify `csp.rules` to compute `csp.directives`' : undefined), + defaultValue: (context: { rules: string[] } = { rules }) => context.rules.join('; '), + }), + strict: schema.boolean({ defaultValue: true }), + warnLegacyBrowsers: schema.boolean({ defaultValue: true }), + }), +}; diff --git a/src/core/server/csp/csp.test.ts b/src/core/server/csp/csp.test.ts index 3dce0620898f5..2d399aeaf3cc1 100644 --- a/src/core/server/csp/csp.test.ts +++ b/src/core/server/csp/csp.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createCspDirectives, DEFAULT_CSP_OPTIONS } from './csp'; +import { DEFAULT_CSP_OPTIONS } from './csp'; // 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, @@ -45,15 +45,3 @@ test('default CSP rules', () => { } `); }); - -test('createCspDirectives() converts an array of rules into a CSP header string', () => { - const directives = createCspDirectives([ - `string-src 'self'`, - 'worker-src blob:', - 'img-src data: blob:', - ]); - - expect(directives).toMatchInlineSnapshot( - `"string-src 'self'; worker-src blob:; img-src data: blob:"` - ); -}); diff --git a/src/core/server/csp/csp.ts b/src/core/server/csp/csp.ts index e7803f83ea0db..aa4b18095c12a 100644 --- a/src/core/server/csp/csp.ts +++ b/src/core/server/csp/csp.ts @@ -17,70 +17,11 @@ * under the License. */ -import { TypeOf, schema } from '@kbn/config-schema'; - -/** - * The CSP options used for Kibana. - * @public - */ -export interface CspOptions { - /** - * The CSP rules in a formatted directives string for use - * in a `Content-Security-Policy` header. - */ - directives: string; - - /** - * The CSP rules used for Kibana. - */ - rules: string[]; - - /** - * Specify whether browsers that do not support CSP should be - * able to use Kibana. Use `true` to block and `false` to allow. - */ - strict: boolean; - - /** - * Specify whether users with legacy browsers should be warned - * about their lack of Kibana security compliance. - */ - warnLegacyBrowsers: boolean; -} - -export type CspConfigType = TypeOf; - -const DEFAULT_RULES = [ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, -]; -const defaults = { - directives: createCspDirectives(DEFAULT_RULES), - rules: DEFAULT_RULES, - strict: true, - warnLegacyBrowsers: true, -}; -export const config = { - path: 'csp', - schema: schema.object({ - rules: schema.arrayOf(schema.string(), { defaultValue: defaults.rules }), - strict: schema.boolean({ defaultValue: defaults.strict }), - warnLegacyBrowsers: schema.boolean({ defaultValue: defaults.warnLegacyBrowsers }), - }), -}; +import { config } from './config'; +import { CspOptions } from './types'; /** * The default set of CSP options used for Kibana. * @public */ -export const DEFAULT_CSP_OPTIONS: Readonly = Object.freeze(defaults); - -/** - * Converts an array of rules into a formatted directives string for use - * in a `Content-Security-Policy` header. - * @internal - */ -export function createCspDirectives(rules: string[]) { - return rules.join('; '); -} +export const DEFAULT_CSP_OPTIONS: Readonly = Object.freeze(config.schema.validate({})); diff --git a/src/core/server/csp/index.ts b/src/core/server/csp/index.ts index db2d54d97bce1..1c72bd1acfc53 100644 --- a/src/core/server/csp/index.ts +++ b/src/core/server/csp/index.ts @@ -17,4 +17,6 @@ * under the License. */ +export * from './config'; +export * from './types'; export * from './csp'; diff --git a/src/core/server/csp/types.ts b/src/core/server/csp/types.ts new file mode 100644 index 0000000000000..67617a4f89cd2 --- /dev/null +++ b/src/core/server/csp/types.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/** + * The CSP options used for Kibana. + * @public + */ +export interface CspOptions { + /** + * The CSP rules in a formatted directives string for use + * in a `Content-Security-Policy` header. + */ + directives: string; + + /** + * The CSP rules used for Kibana. + */ + rules: string[]; + + /** + * Specify whether browsers that do not support CSP should be + * able to use Kibana. Use `true` to block and `false` to allow. + */ + strict: boolean; + + /** + * Specify whether users with legacy browsers should be warned + * about their lack of Kibana security compliance. + */ + warnLegacyBrowsers: boolean; +} diff --git a/src/core/server/index.ts b/src/core/server/index.ts index a44428e51623f..efea5af7fbc33 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,7 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; -export { CspOptions, DEFAULT_CSP_OPTIONS, createCspDirectives } from './csp'; +export { CspOptions, DEFAULT_CSP_OPTIONS } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 91b29526fa455..36457cbef200c 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -213,7 +213,7 @@ export const SharedGlobalConfigKeys = { kibana: ['defaultAppId', 'index'] as const, elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout', 'startupTimeout'] as const, path: ['data'] as const, - csp: ['rules', 'strict', 'warnLegacyBrowsers'] as const, + csp: ['directives', 'rules'] as const, }; /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d52ab9f8e27a2..321a4064e829c 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -574,9 +574,6 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; } -// @internal -export function createCspDirectives(rules: string[]): string; - // @public export interface CspOptions { directives: string; 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 85522d8dac032..f80a49a54dbda 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,7 @@ */ import { Server } from 'hapi'; -import { createCspDirectives, DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; +import { DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -28,16 +28,13 @@ export function createCspCollector(server: Server) { async fetch() { const config = server.config(); - // It's important that we do not send the value of csp.rules here as it - // can be customized with values that can be identifiable to given - // installs, such as URLs - const defaultRulesString = createCspDirectives([...DEFAULT_CSP_OPTIONS.rules]); - const actualRulesString = createCspDirectives(config.get('csp.rules')); - return { strict: config.get('csp.strict'), warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), - rulesChangedFromDefault: defaultRulesString !== actualRulesString, + // It's important that we do not send the value of csp.directives here as it + // can be customized with values that can be identifiable to given + // installs, such as URLs + rulesChangedFromDefault: DEFAULT_CSP_OPTIONS.directives !== config.get('csp.directives'), }; }, }; diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 102f09b9e2a4b..398c774f15324 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -20,7 +20,6 @@ import Joi from 'joi'; import os from 'os'; import { join } from 'path'; -import { DEFAULT_CSP_OPTIONS } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getDataPath } from '../../../core/server/path'; // Still used by optimize config schema @@ -48,11 +47,7 @@ export default () => Joi.object({ exclusive: Joi.boolean().default(false) }).default(), - csp: Joi.object({ - rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_OPTIONS.rules), - strict: Joi.boolean().default(DEFAULT_CSP_OPTIONS.strict), - warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_OPTIONS.warnLegacyBrowsers), - }).default(), + csp: HANDLED_IN_NEW_PLATFORM, cpu: Joi.object({ cgroup: Joi.object({ diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 803e5839690c3..b32b4a12a266e 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -26,7 +26,6 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { AppBootstrap } from './bootstrap'; import { mergeVariables } from './lib'; -import { createCspDirectives } from '../../../core/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; @@ -283,8 +282,7 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - const csp = createCspDirectives(config.get('csp.rules')); - response.header('content-security-policy', csp); + response.header('content-security-policy', config.get('csp.directives')); return response; } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 40879b6d11a94..1baa2c6368f72 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -11,7 +11,7 @@ import { initLogoutView } from './server/routes/views/logout'; import { initLoggedOutView } from './server/routes/views/logged_out'; import { AuditLogger } from '../../server/lib/audit_logger'; import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; -import { KibanaRequest, createCspDirectives } from '../../../../src/core/server'; +import { KibanaRequest } from '../../../../src/core/server'; export const security = (kibana) => new kibana.Plugin({ id: 'security', @@ -126,7 +126,7 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: createCspDirectives(config.get('csp.rules')), + cspRules: config.get('csp.directives'), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js deleted file mode 100644 index 9fd4a09ceb9da..0000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from 'boom'; -import Joi from 'joi'; -import { schema } from '@kbn/config-schema'; -import { canRedirectRequest, wrapError, OIDCAuthenticationFlow } from '../../../../../../../plugins/security/server'; -import { KibanaRequest, createCspDirectives } from '../../../../../../../../src/core/server'; - -export function initAuthenticateApi({ authc: { login, logout }, __legacyCompat: { config } }, server) { - function prepareCustomResourceResponse(response, contentType) { - return response - .header('cache-control', 'private, no-cache, no-store') - .header('content-security-policy', createCspDirectives(server.config().get('csp.rules'))) - .type(contentType); - } - - server.route({ - method: 'POST', - path: '/api/security/v1/login', - config: { - auth: false, - validate: { - payload: Joi.object({ - username: Joi.string().required(), - password: Joi.string().required() - }) - }, - response: { - emptyStatusCode: 204, - } - }, - async handler(request, h) { - const { username, password } = request.payload; - - try { - // We should prefer `token` over `basic` if possible. - const providerToLoginWith = config.authc.providers.includes('token') - ? 'token' - : 'basic'; - const authenticationResult = await login(KibanaRequest.from(request), { - provider: providerToLoginWith, - value: { username, password } - }); - - if (!authenticationResult.succeeded()) { - throw Boom.unauthorized(authenticationResult.error); - } - - return h.response(); - } catch(err) { - throw wrapError(err); - } - } - }); - - /** - * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow - * is used, so that we can extract authentication response from URL fragment and send it to - * the `/api/security/v1/oidc` route. - */ - server.route({ - method: 'GET', - path: '/api/security/v1/oidc/implicit', - config: { auth: false }, - async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` - - Kibana OpenID Connect Login - - `), - 'text/html' - ); - } - }); - - /** - * The route that accompanies `/api/security/v1/oidc/implicit` and renders a JavaScript snippet - * that extracts fragment part from the URL and send it to the `/api/security/v1/oidc` route. - * We need this separate endpoint because of default CSP policy that forbids inline scripts. - */ - server.route({ - method: 'GET', - path: '/api/security/v1/oidc/implicit.js', - config: { auth: false }, - async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` - window.location.replace( - '${server.config().get('server.basePath')}/api/security/v1/oidc?authenticationResponseURI=' + - encodeURIComponent(window.location.href) - ); - `), - 'text/javascript' - ); - } - }); - - server.route({ - // POST is only allowed for Third Party initiated authentication - // Consider splitting this route into two (GET and POST) when it's migrated to New Platform. - method: ['GET', 'POST'], - path: '/api/security/v1/oidc', - config: { - auth: false, - validate: { - query: Joi.object().keys({ - iss: Joi.string().uri({ scheme: 'https' }), - login_hint: Joi.string(), - target_link_uri: Joi.string().uri(), - code: Joi.string(), - error: Joi.string(), - error_description: Joi.string(), - error_uri: Joi.string().uri(), - state: Joi.string(), - authenticationResponseURI: Joi.string(), - }).unknown(), - } - }, - async handler(request, h) { - try { - const query = request.query || {}; - const payload = request.payload || {}; - - // An HTTP GET request with a query parameter named `authenticationResponseURI` that includes URL fragment OpenID - // Connect Provider sent during implicit authentication flow to the Kibana own proxy page that extracted that URL - // fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details - // at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth - let loginAttempt; - if (query.authenticationResponseURI) { - loginAttempt = { - flow: OIDCAuthenticationFlow.Implicit, - authenticationResponseURI: query.authenticationResponseURI, - }; - } else if (query.code || query.error) { - // An HTTP GET request with a query parameter named `code` (or `error`) as the response to a successful (or - // failed) authentication from an OpenID Connect Provider during authorization code authentication flow. - // See more details at https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. - loginAttempt = { - flow: OIDCAuthenticationFlow.AuthorizationCode, - // We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway. - authenticationResponseURI: request.url.path, - }; - } else if (query.iss || payload.iss) { - // An HTTP GET request with a query parameter named `iss` or an HTTP POST request with the same parameter in the - // payload as part of a 3rd party initiated authentication. See more details at - // https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin - loginAttempt = { - flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, - iss: query.iss || payload.iss, - loginHint: query.login_hint || payload.login_hint, - }; - } - - if (!loginAttempt) { - throw Boom.badRequest('Unrecognized login attempt.'); - } - - // We handle the fact that the user might get redirected to Kibana while already having an session - // Return an error notifying the user they are already logged in. - const authenticationResult = await login(KibanaRequest.from(request), { - provider: 'oidc', - value: loginAttempt - }); - if (authenticationResult.succeeded()) { - return Boom.forbidden( - 'Sorry, you already have an active Kibana session. ' + - 'If you want to start a new one, please logout from the existing session first.' - ); - } - - if (authenticationResult.redirected()) { - return h.redirect(authenticationResult.redirectURL); - } - - throw Boom.unauthorized(authenticationResult.error); - } catch (err) { - throw wrapError(err); - } - } - }); - - server.route({ - method: 'GET', - path: '/api/security/v1/logout', - config: { - auth: false - }, - async handler(request, h) { - if (!canRedirectRequest(KibanaRequest.from(request))) { - throw Boom.badRequest('Client should be able to process redirect response.'); - } - - try { - const deauthenticationResult = await logout( - // Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any - // set of query string parameters (e.g. SAML/OIDC logout request parameters). - KibanaRequest.from(request, { - query: schema.object({}, { allowUnknowns: true }), - }) - ); - if (deauthenticationResult.failed()) { - throw wrapError(deauthenticationResult.error); - } - - return h.redirect( - deauthenticationResult.redirectURL || `${server.config().get('server.basePath')}/` - ); - } catch (err) { - throw wrapError(err); - } - } - }); - - server.route({ - method: 'GET', - path: '/api/security/v1/me', - handler(request) { - return request.auth.credentials; - } - }); -} From 6d46ff3df80420acec2d4fd7cec5ff7b66422294 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Thu, 12 Dec 2019 16:09:34 -0600 Subject: [PATCH 04/13] Consolidate CSP configuration with HTTP config --- ...a-plugin-server.cspconfig._constructor_.md | 20 ++++ ... kibana-plugin-server.cspconfig.header.md} | 6 +- .../server/kibana-plugin-server.cspconfig.md | 29 ++++++ ...> kibana-plugin-server.cspconfig.rules.md} | 4 +- ... kibana-plugin-server.cspconfig.strict.md} | 4 +- ...in-server.cspconfig.warnlegacybrowsers.md} | 4 +- .../server/kibana-plugin-server.cspoptions.md | 23 ----- ...ibana-plugin-server.default_csp_options.md | 13 --- ...bana-plugin-server.httpservicesetup.csp.md | 13 +++ .../kibana-plugin-server.httpservicesetup.md | 1 + .../core/server/kibana-plugin-server.md | 3 +- ...kibana-plugin-server.sharedglobalconfig.md | 2 +- src/core/server/csp/config.ts | 17 ++-- src/core/server/csp/csp.test.ts | 47 --------- src/core/server/csp/csp.ts | 27 ------ src/core/server/csp/csp_config.test.ts | 97 +++++++++++++++++++ .../server/csp/{types.ts => csp_config.ts} | 41 +++++--- src/core/server/csp/index.ts | 7 +- src/core/server/http/http_config.test.ts | 3 + src/core/server/http/http_config.ts | 29 +++--- src/core/server/http/http_server.ts | 2 + src/core/server/http/http_service.mock.ts | 2 + src/core/server/http/http_service.ts | 18 ++-- src/core/server/http/http_tools.test.ts | 26 ++--- src/core/server/http/types.ts | 6 ++ src/core/server/index.ts | 2 +- .../legacy_object_to_config_adapter.test.ts | 16 +++ src/core/server/legacy/legacy_service.ts | 14 ++- src/core/server/mocks.ts | 4 +- src/core/server/plugins/plugin_context.ts | 8 +- src/core/server/plugins/types.ts | 3 - src/core/server/server.api.md | 20 ++-- src/core/server/types.ts | 2 +- .../csp_usage_collector/csp_collector.test.ts | 9 +- .../lib/csp_usage_collector/csp_collector.ts | 7 +- src/legacy/ui/ui_render/ui_render_mixin.js | 2 + 36 files changed, 320 insertions(+), 211 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md rename docs/development/core/server/{kibana-plugin-server.cspoptions.directives.md => kibana-plugin-server.cspconfig.header.md} (58%) create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.md rename docs/development/core/server/{kibana-plugin-server.cspoptions.rules.md => kibana-plugin-server.cspconfig.rules.md} (58%) rename docs/development/core/server/{kibana-plugin-server.cspoptions.strict.md => kibana-plugin-server.cspconfig.strict.md} (65%) rename docs/development/core/server/{kibana-plugin-server.cspoptions.warnlegacybrowsers.md => kibana-plugin-server.cspconfig.warnlegacybrowsers.md} (61%) delete mode 100644 docs/development/core/server/kibana-plugin-server.cspoptions.md delete mode 100644 docs/development/core/server/kibana-plugin-server.default_csp_options.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md delete mode 100644 src/core/server/csp/csp.test.ts delete mode 100644 src/core/server/csp/csp.ts create mode 100644 src/core/server/csp/csp_config.test.ts rename src/core/server/csp/{types.ts => csp_config.ts} (63%) diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md b/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md new file mode 100644 index 0000000000000..a7c268b60ab0d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [(constructor)](./kibana-plugin-server.cspconfig._constructor_.md) + +## CspConfig.(constructor) + +Returns the default CSP configuration when passed with no config + +Signature: + +```typescript +constructor(rawCspConfig?: Partial); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| rawCspConfig | Partial<CspConfigType> | | + diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.directives.md b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md similarity index 58% rename from docs/development/core/server/kibana-plugin-server.cspoptions.directives.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.header.md index 37ba72f09919c..034bab4e70782 100644 --- a/docs/development/core/server/kibana-plugin-server.cspoptions.directives.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [directives](./kibana-plugin-server.cspoptions.directives.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [header](./kibana-plugin-server.cspconfig.header.md) -## CspOptions.directives property +## CspConfig.header property The CSP rules in a formatted directives string for use in a `Content-Security-Policy` header. Signature: ```typescript -directives: string; +header: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md new file mode 100644 index 0000000000000..bb07d10a56295 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) + +## CspConfig class + +CSP configuration for use in Kibana. + +Signature: + +```typescript +export declare class CspConfig +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(rawCspConfig)](./kibana-plugin-server.cspconfig._constructor_.md) | | Returns the default CSP configuration when passed with no config | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [header](./kibana-plugin-server.cspconfig.header.md) | | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | +| [rules](./kibana-plugin-server.cspconfig.rules.md) | | string[] | The CSP rules used for Kibana. | +| [strict](./kibana-plugin-server.cspconfig.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.cspconfig.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.cspoptions.rules.md b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md similarity index 58% rename from docs/development/core/server/kibana-plugin-server.cspoptions.rules.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.rules.md index ae5a0012b99ef..e1c10e56db3e7 100644 --- a/docs/development/core/server/kibana-plugin-server.cspoptions.rules.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [rules](./kibana-plugin-server.cspoptions.rules.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rules](./kibana-plugin-server.cspconfig.rules.md) -## CspOptions.rules property +## CspConfig.rules property The CSP rules used for Kibana. diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.strict.md b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md similarity index 65% rename from docs/development/core/server/kibana-plugin-server.cspoptions.strict.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.strict.md index ca113045e1819..5334e53ba07ff 100644 --- a/docs/development/core/server/kibana-plugin-server.cspoptions.strict.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [strict](./kibana-plugin-server.cspoptions.strict.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [strict](./kibana-plugin-server.cspconfig.strict.md) -## CspOptions.strict property +## CspConfig.strict property Specify whether browsers that do not support CSP should be able to use Kibana. Use `true` to block and `false` to allow. diff --git a/docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md similarity index 61% rename from docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md rename to docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md index b149e63860974..67247a3697fea 100644 --- a/docs/development/core/server/kibana-plugin-server.cspoptions.warnlegacybrowsers.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspoptions.warnlegacybrowsers.md) +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) -## CspOptions.warnLegacyBrowsers property +## CspConfig.warnLegacyBrowsers property 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.cspoptions.md b/docs/development/core/server/kibana-plugin-server.cspoptions.md deleted file mode 100644 index ad4062eb0c338..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspoptions.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspOptions](./kibana-plugin-server.cspoptions.md) - -## CspOptions interface - -The CSP options used for Kibana. - -Signature: - -```typescript -export interface CspOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [directives](./kibana-plugin-server.cspoptions.directives.md) | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | -| [rules](./kibana-plugin-server.cspoptions.rules.md) | string[] | The CSP rules used for Kibana. | -| [strict](./kibana-plugin-server.cspoptions.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.cspoptions.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.default_csp_options.md b/docs/development/core/server/kibana-plugin-server.default_csp_options.md deleted file mode 100644 index b4e954ccf29ca..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.default_csp_options.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DEFAULT\_CSP\_OPTIONS](./kibana-plugin-server.default_csp_options.md) - -## DEFAULT\_CSP\_OPTIONS variable - -The default set of CSP options used for Kibana. - -Signature: - -```typescript -DEFAULT_CSP_OPTIONS: Readonly -``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md new file mode 100644 index 0000000000000..e481435fdbd24 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [csp](./kibana-plugin-server.httpservicesetup.csp.md) + +## HttpServiceSetup.csp property + +The CSP config used for Kibana. + +Signature: + +```typescript +csp: CspConfig; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 25eebf1c06d01..a20bee3839733 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -19,6 +19,7 @@ export interface HttpServiceSetup | [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | | [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | | [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | +| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | CspConfig | The CSP config used for Kibana. | | [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index c4d57b5414eb9..662225426620b 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -18,6 +18,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | | [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | @@ -50,7 +51,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ContextSetup](./kibana-plugin-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | -| [CspOptions](./kibana-plugin-server.cspoptions.md) | The CSP options used for Kibana. | | [CustomHttpResponseOptions](./kibana-plugin-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | @@ -142,7 +142,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | -| [DEFAULT\_CSP\_OPTIONS](./kibana-plugin-server.default_csp_options.md) | The default set of CSP options used for Kibana. | | [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | | [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | diff --git a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md index 9eb46eb220acc..418d406d4c890 100644 --- a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md +++ b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md @@ -4,6 +4,7 @@ ## SharedGlobalConfig type + Signature: ```typescript @@ -11,6 +12,5 @@ export declare type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; path: Pick; - csp: Pick; }>; ``` diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts index d9b8735ab0e1c..be66c9122e11d 100644 --- a/src/core/server/csp/config.ts +++ b/src/core/server/csp/config.ts @@ -21,22 +21,17 @@ import { TypeOf, schema } from '@kbn/config-schema'; export type CspConfigType = TypeOf; -const rules = [ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, -]; - export const config = { // TODO: Move this to server.csp using config deprecations // ? https://github.com/elastic/kibana/pull/52251 path: 'csp', schema: schema.object({ - rules: schema.arrayOf(schema.string(), { defaultValue: rules }), - directives: schema.string({ - // TODO: Consider making this bidirectional in the future - validate: value => (value ? 'Specify `csp.rules` to compute `csp.directives`' : undefined), - defaultValue: (context: { rules: string[] } = { rules }) => context.rules.join('; '), + rules: schema.arrayOf(schema.string(), { + defaultValue: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src blob: 'self'`, + `style-src 'unsafe-inline' 'self'`, + ], }), strict: schema.boolean({ defaultValue: true }), warnLegacyBrowsers: schema.boolean({ defaultValue: true }), diff --git a/src/core/server/csp/csp.test.ts b/src/core/server/csp/csp.test.ts deleted file mode 100644 index 2d399aeaf3cc1..0000000000000 --- a/src/core/server/csp/csp.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { DEFAULT_CSP_OPTIONS } from './csp'; - -// 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, -// we test the default rules exactly so any change to those rules gets flagged -// for manual review. In other words, this test is intentionally fragile to draw -// extra attention if defaults are modified in any way. -// -// A test failure here does not necessarily mean this change cannot be made, -// but any change here should undergo sufficient scrutiny by the Kibana -// security team. -// -// The tests use inline snapshots to make it as easy as possible to identify -// the nature of a change in defaults during a PR review. -test('default CSP rules', () => { - expect(DEFAULT_CSP_OPTIONS).toMatchInlineSnapshot(` - Object { - "directives": "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, - } - `); -}); diff --git a/src/core/server/csp/csp.ts b/src/core/server/csp/csp.ts deleted file mode 100644 index aa4b18095c12a..0000000000000 --- a/src/core/server/csp/csp.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 { config } from './config'; -import { CspOptions } from './types'; - -/** - * The default set of CSP options used for Kibana. - * @public - */ -export const DEFAULT_CSP_OPTIONS: Readonly = Object.freeze(config.schema.validate({})); diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts new file mode 100644 index 0000000000000..009a0e271d8b9 --- /dev/null +++ b/src/core/server/csp/csp_config.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { CspConfig } from '.'; + +// 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, +// we test the default rules exactly so any change to those rules gets flagged +// for manual review. In other words, this test is intentionally fragile to draw +// extra attention if defaults are modified in any way. +// +// A test failure here does not necessarily mean this change cannot be made, +// but any change here should undergo sufficient scrutiny by the Kibana +// security team. +// +// The tests use inline snapshots to make it as easy as possible to identify +// the nature of a change in defaults during a PR review. + +describe('CspConfig', () => { + test('defaults from config', () => { + expect(new CspConfig()).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('creates from partial config', () => { + expect(new CspConfig({ strict: false, warnLegacyBrowsers: false })).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": false, + "warnLegacyBrowsers": false, + } + `); + }); + + test('computes header from rules', () => { + const cspConfig = new CspConfig({ rules: ['alpha', 'beta', 'gamma'] }); + + expect(cspConfig).toMatchInlineSnapshot(` + CspConfig { + "header": "alpha; beta; gamma", + "rules": Array [ + "alpha", + "beta", + "gamma", + ], + "strict": true, + "warnLegacyBrowsers": true, + } + `); + + cspConfig.rules = ['delta', 'epsilon', 'zeta']; + + expect(cspConfig).toMatchInlineSnapshot(` + CspConfig { + "header": "delta; epsilon; zeta", + "rules": Array [ + "delta", + "epsilon", + "zeta", + ], + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); +}); diff --git a/src/core/server/csp/types.ts b/src/core/server/csp/csp_config.ts similarity index 63% rename from src/core/server/csp/types.ts rename to src/core/server/csp/csp_config.ts index 67617a4f89cd2..3d795239ff0ef 100644 --- a/src/core/server/csp/types.ts +++ b/src/core/server/csp/csp_config.ts @@ -17,31 +17,50 @@ * under the License. */ +import { CspConfigType, config } from './config'; + +const DEFAULT = Object.freeze(config.schema.validate({})); + /** - * The CSP options used for Kibana. + * CSP configuration for use in Kibana. * @public */ -export interface CspOptions { - /** - * The CSP rules in a formatted directives string for use - * in a `Content-Security-Policy` header. - */ - directives: string; - +export class CspConfig { /** * The CSP rules used for Kibana. */ - rules: string[]; + public rules: string[]; /** * Specify whether browsers that do not support CSP should be * able to use Kibana. Use `true` to block and `false` to allow. */ - strict: boolean; + public strict: boolean; /** * Specify whether users with legacy browsers should be warned * about their lack of Kibana security compliance. */ - warnLegacyBrowsers: boolean; + public warnLegacyBrowsers: boolean; + + /** + * The CSP rules in a formatted directives string for use + * in a `Content-Security-Policy` header. + */ + public header!: string; + + /** + * Returns the default CSP configuration when passed with no config + */ + constructor(rawCspConfig: Partial = {}) { + const source = { ...DEFAULT, ...rawCspConfig }; + + this.rules = source.rules; + this.strict = source.strict; + this.warnLegacyBrowsers = source.warnLegacyBrowsers; + Object.defineProperty(this, 'header', { + enumerable: true, + get: () => this.rules.join('; '), + }); + } } diff --git a/src/core/server/csp/index.ts b/src/core/server/csp/index.ts index 1c72bd1acfc53..f9943a9ced475 100644 --- a/src/core/server/csp/index.ts +++ b/src/core/server/csp/index.ts @@ -17,6 +17,7 @@ * under the License. */ -export * from './config'; -export * from './types'; -export * from './csp'; +import { CspConfig } from './csp_config'; +import { CspConfigType, config } from './config'; + +export { CspConfig, CspConfigType, config }; diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 1ee7e13d5e851..888313e1478cb 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -256,6 +256,7 @@ describe('with TLS', () => { clientAuthentication: 'none', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); @@ -273,6 +274,7 @@ describe('with TLS', () => { clientAuthentication: 'optional', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); @@ -290,6 +292,7 @@ describe('with TLS', () => { clientAuthentication: 'required', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index cb7726de4da5a..055381b8f1602 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -19,6 +19,7 @@ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; import { Env } from '../config'; +import { CspConfigType, CspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; @@ -132,23 +133,25 @@ export class HttpConfig { public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; + public csp: CspConfig; /** * @internal */ - constructor(rawConfig: HttpConfigType, env: Env) { - this.autoListen = rawConfig.autoListen; - this.host = rawConfig.host; - this.port = rawConfig.port; - this.cors = rawConfig.cors; - this.maxPayload = rawConfig.maxPayload; - this.basePath = rawConfig.basePath; - this.keepaliveTimeout = rawConfig.keepaliveTimeout; - this.socketTimeout = rawConfig.socketTimeout; - this.rewriteBasePath = rawConfig.rewriteBasePath; + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { + this.autoListen = rawHttpConfig.autoListen; + this.host = rawHttpConfig.host; + this.port = rawHttpConfig.port; + this.cors = rawHttpConfig.cors; + this.maxPayload = rawHttpConfig.maxPayload; + this.basePath = rawHttpConfig.basePath; + this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; + this.socketTimeout = rawHttpConfig.socketTimeout; + this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.publicDir = env.staticFilesDir; - this.ssl = new SslConfig(rawConfig.ssl || {}); - this.defaultRoute = rawConfig.defaultRoute; - this.compression = rawConfig.compression; + this.ssl = new SslConfig(rawHttpConfig.ssl || {}); + this.defaultRoute = rawHttpConfig.defaultRoute; + this.compression = rawHttpConfig.compression; + this.csp = new CspConfig(rawCspConfig); } } diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 244b3cca60f31..994a6cced8914 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -46,6 +46,7 @@ export interface HttpServerSetup { */ registerRouter: (router: IRouter) => void; basePath: HttpServiceSetup['basePath']; + csp: HttpServiceSetup['csp']; createCookieSessionStorageFactory: HttpServiceSetup['createCookieSessionStorageFactory']; registerAuth: HttpServiceSetup['registerAuth']; registerOnPreAuth: HttpServiceSetup['registerOnPreAuth']; @@ -109,6 +110,7 @@ export class HttpServer { this.createCookieSessionStorageFactory(cookieOptions, config.basePath), registerAuth: this.registerAuth.bind(this), basePath: basePathService, + csp: config.csp, auth: { get: this.authState.get, isAuthenticated: this.authState.isAuthenticated, diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 444aa04171dbd..1f3e07dfa2209 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -18,6 +18,7 @@ */ import { Server } from 'hapi'; +import { CspConfig } from '../csp'; import { mockRouter } from './router/router.mock'; import { InternalHttpServiceSetup } from './types'; import { HttpService } from './http_service'; @@ -55,6 +56,7 @@ const createSetupContractMock = () => { registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), + csp: new CspConfig(), 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 caebd768c70e5..fae411607ca15 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Observable, Subscription } from 'rxjs'; +import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { Server } from 'hapi'; @@ -28,6 +28,7 @@ import { Logger } from '../logging'; import { ContextSetup } from '../context'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; +import { CspConfigType } from '../csp'; import { Router } from './router'; import { HttpConfig, HttpConfigType } from './http_config'; @@ -62,10 +63,15 @@ export class HttpService implements CoreService('server') - .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); - + this.config$ = combineLatest( + coreContext.configService.atPath('server'), + coreContext.configService.atPath('csp') + ).pipe( + map( + ([rawHttpConfig, rawCspConfig]) => + new HttpConfig(rawHttpConfig, rawCspConfig, coreContext.env) + ) + ); this.httpServer = new HttpServer(coreContext.logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer( coreContext.logger.get('http', 'redirect', 'server') @@ -79,7 +85,7 @@ export class HttpService implements CoreService { certificate: 'some-certificate-path', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` - Object { - "ca": undefined, - "cert": "content-some-certificate-path", - "ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", - "honorCipherOrder": true, - "key": "content-some-key-path", - "passphrase": undefined, - "rejectUnauthorized": false, - "requestCert": false, - "secureOptions": 67108864, - } - `); + Object { + "ca": undefined, + "cert": "content-some-certificate-path", + "ciphers": "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", + "honorCipherOrder": true, + "key": "content-some-key-path", + "passphrase": undefined, + "rejectUnauthorized": false, + "requestCert": false, + "secureOptions": 67108864, + } + `); }); it('properly configures TLS with client authentication', () => { @@ -151,6 +152,7 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), + {} as any, Env.createDefault(getEnvOptions()) ); diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 94c1982a18c0a..109ec0791b8dd 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -17,6 +17,7 @@ * under the License. */ import { IContextProvider, IContextContainer } from '../context'; +import { CspConfig } from '../csp'; import { RequestHandler, IRouter } from './router'; import { HttpServerSetup } from './http_server'; import { SessionStorageCookieOptions } from './cookie_session_storage'; @@ -182,6 +183,11 @@ export interface HttpServiceSetup { */ basePath: IBasePath; + /** + * The CSP config used for Kibana. + */ + csp: CspConfig; + /** * Flag showing whether a server was configured to use TLS connection. */ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index efea5af7fbc33..53e373b8060b0 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,7 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; -export { CspOptions, DEFAULT_CSP_OPTIONS } from './csp'; +export { CspConfig } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts index 201f761701a35..db2bc117280ca 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts @@ -45,6 +45,22 @@ describe('#get', () => { expect(configAdapter.get('container')).toEqual({ value: 'some' }); }); + test('correctly handles csp config.', () => { + const configAdapter = new LegacyObjectToConfigAdapter({ + csp: { + rules: ['strict'], + }, + }); + + expect(configAdapter.get('csp')).toMatchInlineSnapshot(` + Object { + "rules": Array [ + "strict", + ], + } + `); + }); + test('correctly handles silent logging config.', () => { const configAdapter = new LegacyObjectToConfigAdapter({ logging: { silent: true }, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 4c2e57dc69b29..43a47acc989ed 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -25,6 +25,7 @@ import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { SavedObjectsLegacyUiExports } from '../types'; import { Config, ConfigDeprecationProvider } from '../config'; import { CoreContext } from '../core_context'; +import { CspConfigType } from '../csp'; import { DevConfig, DevConfigType } from '../dev'; import { BasePathProxyServer, HttpConfig, HttpConfigType } from '../http'; import { Logger } from '../logging'; @@ -116,9 +117,15 @@ export class LegacyService implements CoreService { this.devConfig$ = coreContext.configService .atPath('dev') .pipe(map(rawConfig => new DevConfig(rawConfig))); - this.httpConfig$ = coreContext.configService - .atPath('server') - .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); + this.httpConfig$ = combineLatest( + coreContext.configService.atPath('server'), + coreContext.configService.atPath('csp') + ).pipe( + map( + ([rawHttpConfig, rawCspConfig]) => + new HttpConfig(rawHttpConfig, rawCspConfig, coreContext.env) + ) + ); } public async discoverPlugins(): Promise { @@ -284,6 +291,7 @@ export class LegacyService implements CoreService { registerOnPostAuth: setupDeps.core.http.registerOnPostAuth, registerOnPreResponse: setupDeps.core.http.registerOnPreResponse, basePath: setupDeps.core.http.basePath, + csp: setupDeps.core.http.csp, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, savedObjects: { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 8aab2a829c594..c6312da8252da 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -19,7 +19,7 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { PluginInitializerContext, CoreSetup, CoreStart } from '.'; -import { DEFAULT_CSP_OPTIONS } from './csp'; +import { CspConfig } from './csp'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -49,7 +49,6 @@ export function pluginInitializerContextConfigMock(config: T) { startupTimeout: duration('30s'), }, path: { data: '/tmp' }, - csp: DEFAULT_CSP_OPTIONS, }; const mock: jest.Mocked['config']> = { @@ -94,6 +93,7 @@ function createCoreSetupMock() { registerOnPostAuth: httpService.registerOnPostAuth, registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, + csp: new CspConfig(), isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), registerRouteHandlerContext: jest.fn(), diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 20e23d5ab1f6c..26c65baf95535 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -28,7 +28,6 @@ import { PluginOpaqueId, SharedGlobalConfigKeys, } from './types'; -import { CspConfigType, config as cspConfig } from '../csp'; import { PathConfigType, config as pathConfig } from '../path'; import { KibanaConfigType, config as kibanaConfig } from '../kibana_config'; import { @@ -89,15 +88,13 @@ export function createPluginInitializerContext( globalConfig$: combineLatest( coreContext.configService.atPath(kibanaConfig.path), coreContext.configService.atPath(elasticsearchConfig.path), - coreContext.configService.atPath(pathConfig.path), - coreContext.configService.atPath(cspConfig.path) + coreContext.configService.atPath(pathConfig.path) ).pipe( - map(([kibana, elasticsearch, path, csp]) => + map(([kibana, elasticsearch, path]) => deepFreeze({ kibana: pick(kibana, SharedGlobalConfigKeys.kibana), elasticsearch: pick(elasticsearch, SharedGlobalConfigKeys.elasticsearch), path: pick(path, SharedGlobalConfigKeys.path), - csp: pick(csp, SharedGlobalConfigKeys.csp), }) ) ), @@ -164,6 +161,7 @@ export function createPluginSetupContext( registerOnPostAuth: deps.http.registerOnPostAuth, registerOnPreResponse: deps.http.registerOnPreResponse, basePath: deps.http.basePath, + csp: deps.http.csp, isTlsEnabled: deps.http.isTlsEnabled, }, savedObjects: { diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 36457cbef200c..e717871912f46 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -24,7 +24,6 @@ import { RecursiveReadonly } from 'kibana/public'; import { ConfigPath, EnvironmentMode, PackageInfo, ConfigDeprecationProvider } from '../config'; import { LoggerFactory } from '../logging'; import { KibanaConfigType } from '../kibana_config'; -import { CspConfigType } from '../csp'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; import { PathConfigType } from '../path'; import { CoreSetup, CoreStart } from '..'; @@ -213,7 +212,6 @@ export const SharedGlobalConfigKeys = { kibana: ['defaultAppId', 'index'] as const, elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout', 'startupTimeout'] as const, path: ['data'] as const, - csp: ['directives', 'rules'] as const, }; /** @@ -223,7 +221,6 @@ export type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; path: Pick; - csp: Pick; }>; /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 321a4064e829c..2d7258a9611e9 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -575,8 +575,10 @@ export interface CoreStart { } // @public -export interface CspOptions { - directives: string; +export class CspConfig { + // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts + constructor(rawCspConfig?: Partial); + header: string; rules: string[]; strict: boolean; warnLegacyBrowsers: boolean; @@ -590,9 +592,6 @@ export interface CustomHttpResponseOptions; - // @public (undocumented) export interface DeprecationAPIClientParams extends GenericParams { // (undocumented) @@ -724,6 +723,7 @@ export interface HttpServiceSetup { basePath: IBasePath; createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; createRouter: () => IRouter; + csp: CspConfig; isTlsEnabled: boolean; registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; @@ -1785,7 +1785,6 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ kibana: Pick; elasticsearch: Pick; path: Pick; - csp: Pick; }>; // @public @@ -1825,10 +1824,9 @@ export const validBodyOutput: readonly ["data", "stream"]; // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:224:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:225:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 640e87e73617f..c8a3f8fff1b6b 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -22,4 +22,4 @@ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; -export { CspOptions } from './csp'; +export { CspConfig } from './csp'; 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 7659325264e0c..48066eea14e78 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 @@ -19,7 +19,7 @@ import sinon from 'sinon'; import { Server } from 'hapi'; -import { DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; +import { CspConfig } from '../../../../../../core/server'; import { createCspCollector } from './csp_collector'; interface MockConfig { @@ -85,9 +85,10 @@ test('does not arbitrarily fetch other csp configurations (e.g. whitelist only)' function setupCollector() { const mockConfig = { get: sinon.stub() }; - mockConfig.get.withArgs('csp.rules').returns(DEFAULT_CSP_OPTIONS.rules); - mockConfig.get.withArgs('csp.strict').returns(true); - mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(true); + const defaultCspConfig = new CspConfig(); + mockConfig.get.withArgs('csp.rules').returns(defaultCspConfig.rules); + mockConfig.get.withArgs('csp.strict').returns(defaultCspConfig.strict); + mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(defaultCspConfig.warnLegacyBrowsers); const mockKbnServer = getMockKbnServer(mockConfig); 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 f80a49a54dbda..0af66a7145b64 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,7 @@ */ import { Server } from 'hapi'; -import { DEFAULT_CSP_OPTIONS } from '../../../../../../core/server'; +import { CspConfig } from '../../../../../../core/server'; import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { @@ -27,14 +27,15 @@ export function createCspCollector(server: Server) { isReady: () => true, async fetch() { const config = server.config(); + const { header } = new CspConfig(); return { strict: config.get('csp.strict'), warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), - // It's important that we do not send the value of csp.directives here as it + // 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: DEFAULT_CSP_OPTIONS.directives !== config.get('csp.directives'), + rulesChangedFromDefault: header !== config.get('csp.header'), }; }, }; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index b32b4a12a266e..c3a18b043c337 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -246,6 +246,8 @@ export function uiRenderMixin(kbnServer, server, config) { } })); + console.log(config.get('csp')); + const response = h.view('ui_app', { strictCsp: config.get('csp.strict'), uiPublicUrl: `${basePath}/ui`, From 0725c627b5450c97b489cc05a8579b90e1701239 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Thu, 12 Dec 2019 16:26:31 -0600 Subject: [PATCH 05/13] Fix outstanding config renames --- src/legacy/ui/ui_render/ui_render_mixin.js | 2 +- x-pack/legacy/plugins/security/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index c3a18b043c337..3ee225fbd052c 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -284,7 +284,7 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - response.header('content-security-policy', config.get('csp.directives')); + response.header('content-security-policy', config.get('csp.header')); return response; } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 1baa2c6368f72..ef6b207fbbe64 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -126,7 +126,7 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: config.get('csp.directives'), + cspRules: config.get('csp.header'), }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` From 96017492f088a52d0d79a30828067df34c7f5397 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Thu, 12 Dec 2019 16:46:14 -0600 Subject: [PATCH 06/13] Remove legacy CSP configuration calls, migrate to platform properties --- src/core/server/csp/csp_config.ts | 13 +++++-------- .../server/lib/csp_usage_collector/csp_collector.ts | 11 ++++++----- src/legacy/ui/ui_render/ui_render_mixin.js | 9 ++++----- x-pack/legacy/plugins/security/index.js | 2 +- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index 3d795239ff0ef..827193c859442 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -29,25 +29,25 @@ export class CspConfig { /** * The CSP rules used for Kibana. */ - public rules: string[]; + public readonly rules: string[]; /** * Specify whether browsers that do not support CSP should be * able to use Kibana. Use `true` to block and `false` to allow. */ - public strict: boolean; + public readonly strict: boolean; /** * Specify whether users with legacy browsers should be warned * about their lack of Kibana security compliance. */ - public warnLegacyBrowsers: boolean; + public readonly warnLegacyBrowsers: boolean; /** * The CSP rules in a formatted directives string for use * in a `Content-Security-Policy` header. */ - public header!: string; + public readonly header: string; /** * Returns the default CSP configuration when passed with no config @@ -58,9 +58,6 @@ export class CspConfig { this.rules = source.rules; this.strict = source.strict; this.warnLegacyBrowsers = source.warnLegacyBrowsers; - Object.defineProperty(this, 'header', { - enumerable: true, - get: () => this.rules.join('; '), - }); + this.header = source.rules.join('; '); } } 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 0af66a7145b64..dadee4d1a5457 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 @@ -26,16 +26,17 @@ export function createCspCollector(server: Server) { type: 'csp', isReady: () => true, async fetch() { - const config = server.config(); - const { header } = new CspConfig(); + const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp; + // This is used to get the default CSP header string. + const { header: defaultCspHeader } = new CspConfig(); return { - strict: config.get('csp.strict'), - warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), + strict, + warnLegacyBrowsers, // 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 !== config.get('csp.header'), + rulesChangedFromDefault: header !== defaultCspHeader, }; }, }; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 3ee225fbd052c..da67a6dc696b9 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -245,11 +245,10 @@ export function uiRenderMixin(kbnServer, server, config) { return { id, plugin, config: {} }; } })); - - console.log(config.get('csp')); + const { strict, warnLegacyBrowsers, header } = kbnServer.newPlatform.setup.core.http.csp; const response = h.view('ui_app', { - strictCsp: config.get('csp.strict'), + strictCsp: strict, uiPublicUrl: `${basePath}/ui`, bootstrapScriptUrl: `${basePath}/bundles/app/${app.getId()}/bootstrap.js`, i18n: (id, options) => i18n.translate(id, options), @@ -267,7 +266,7 @@ export function uiRenderMixin(kbnServer, server, config) { translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`, }, csp: { - warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'), + warnLegacyBrowsers, }, vars: await replaceInjectedVars( request, @@ -284,7 +283,7 @@ export function uiRenderMixin(kbnServer, server, config) { }, }); - response.header('content-security-policy', config.get('csp.header')); + response.header('content-security-policy', header); return response; } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index ef6b207fbbe64..2e265c8be158a 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -126,7 +126,7 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: config.get('csp.header'), + cspRules: server.newPlatform.setup.core.http.csp.header, }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` From eb224e6c8b1a9a5f6086b99631195c2bb99cf9c6 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Thu, 12 Dec 2019 17:37:30 -0600 Subject: [PATCH 07/13] Revise docs --- .../core/server/kibana-plugin-server.cspconfig.header.md | 2 +- .../core/server/kibana-plugin-server.cspconfig.rules.md | 2 +- .../core/server/kibana-plugin-server.cspconfig.strict.md | 2 +- .../kibana-plugin-server.cspconfig.warnlegacybrowsers.md | 2 +- src/core/server/server.api.md | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md index 034bab4e70782..4b188490d3bb0 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md @@ -9,5 +9,5 @@ The CSP rules in a formatted directives string for use in a `Content-Security-Po Signature: ```typescript -header: string; +readonly header: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md index e1c10e56db3e7..02190c943b602 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md @@ -9,5 +9,5 @@ The CSP rules used for Kibana. Signature: ```typescript -rules: string[]; +readonly rules: string[]; ``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md index 5334e53ba07ff..5592ccfe8386c 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md @@ -9,5 +9,5 @@ Specify whether browsers that do not support CSP should be able to use Kibana. U Signature: ```typescript -strict: boolean; +readonly strict: boolean; ``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md index 67247a3697fea..8b29f6258f4df 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md @@ -9,5 +9,5 @@ Specify whether users with legacy browsers should be warned about their lack of Signature: ```typescript -warnLegacyBrowsers: boolean; +readonly warnLegacyBrowsers: boolean; ``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 2d7258a9611e9..aebf09839114e 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -578,10 +578,10 @@ export interface CoreStart { export class CspConfig { // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts constructor(rawCspConfig?: Partial); - header: string; - rules: string[]; - strict: boolean; - warnLegacyBrowsers: boolean; + readonly header: string; + readonly rules: string[]; + readonly strict: boolean; + readonly warnLegacyBrowsers: boolean; } // @public From f6339cb533d5d16d02bfa412ac3c7b86695f8a62 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 01:39:54 -0600 Subject: [PATCH 08/13] Fix test from type change --- src/core/server/csp/csp_config.test.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 009a0e271d8b9..651171012dd96 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -78,20 +78,5 @@ describe('CspConfig', () => { "warnLegacyBrowsers": true, } `); - - cspConfig.rules = ['delta', 'epsilon', 'zeta']; - - expect(cspConfig).toMatchInlineSnapshot(` - CspConfig { - "header": "delta; epsilon; zeta", - "rules": Array [ - "delta", - "epsilon", - "zeta", - ], - "strict": true, - "warnLegacyBrowsers": true, - } - `); }); }); From fd90dfe5fa49dfe23fbc36b01556b43fd95726dc Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 05:31:11 -0600 Subject: [PATCH 09/13] Expose ICspConfig, consolidate and simplify CSP defaults access --- src/core/server/csp/csp_config.test.ts | 15 +++ src/core/server/csp/csp_config.ts | 12 +- src/core/server/csp/index.ts | 4 +- src/core/server/http/http_config.ts | 4 +- src/core/server/http/http_service.mock.ts | 2 +- src/core/server/http/http_service.test.ts | 2 + src/core/server/http/http_service.ts | 27 ++--- src/core/server/http/types.ts | 4 +- src/core/server/index.ts | 2 +- src/core/server/mocks.ts | 2 +- src/core/server/types.ts | 2 +- .../csp_usage_collector/csp_collector.test.ts | 114 +++++++++--------- .../lib/csp_usage_collector/csp_collector.ts | 4 +- 13 files changed, 103 insertions(+), 91 deletions(-) diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 651171012dd96..45fa8445791b0 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -33,6 +33,21 @@ 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(` CspConfig { diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index 827193c859442..b0851d014d438 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -19,13 +19,21 @@ import { CspConfigType, config } from './config'; -const DEFAULT = Object.freeze(config.schema.validate({})); +const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); /** * CSP configuration for use in Kibana. * @public */ +export type ICspConfig = Pick; + +/** + * CSP configuration for use in Kibana. + * @internal + */ export class CspConfig { + static readonly DEFAULT = new CspConfig(); + /** * The CSP rules used for Kibana. */ @@ -53,7 +61,7 @@ export class CspConfig { * Returns the default CSP configuration when passed with no config */ constructor(rawCspConfig: Partial = {}) { - const source = { ...DEFAULT, ...rawCspConfig }; + const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; this.rules = source.rules; this.strict = source.strict; diff --git a/src/core/server/csp/index.ts b/src/core/server/csp/index.ts index f9943a9ced475..a9e320ac5afa5 100644 --- a/src/core/server/csp/index.ts +++ b/src/core/server/csp/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { CspConfig } from './csp_config'; +import { CspConfig, ICspConfig } from './csp_config'; import { CspConfigType, config } from './config'; -export { CspConfig, CspConfigType, config }; +export { CspConfig, CspConfigType, config, ICspConfig }; diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 055381b8f1602..912459c83df6e 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -19,7 +19,7 @@ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; import { Env } from '../config'; -import { CspConfigType, CspConfig } from '../csp'; +import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; @@ -133,7 +133,7 @@ export class HttpConfig { public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; - public csp: CspConfig; + public csp: ICspConfig; /** * @internal diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 1f3e07dfa2209..1668b409050b7 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -56,7 +56,7 @@ const createSetupContractMock = () => { registerOnPreResponse: jest.fn(), createRouter: jest.fn().mockImplementation(() => mockRouter.create({})), basePath: createBasePathMock(), - csp: new CspConfig(), + csp: CspConfig.DEFAULT, auth: { get: jest.fn(), isAuthenticated: jest.fn(), diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index a2546709a318c..8b500caf217dc 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -28,6 +28,7 @@ import { ConfigService, Env } from '../config'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; +import { config as cspConfig } from '../csp'; const logger = loggingServiceMock.create(); const env = Env.createDefault(getEnvOptions()); @@ -45,6 +46,7 @@ const createConfigService = (value: Partial = {}) => { logger ); configService.setSchema(config.path, config.schema); + configService.setSchema(cspConfig.path, cspConfig.schema); return configService; }; const contextSetup = contextServiceMock.createSetupContract(); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index fae411607ca15..faeae0b559b6b 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -28,10 +28,10 @@ import { Logger } from '../logging'; import { ContextSetup } from '../context'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; -import { CspConfigType } from '../csp'; +import { CspConfigType, config as cspConfig } from '../csp'; import { Router } from './router'; -import { HttpConfig, HttpConfigType } from './http_config'; +import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config'; import { HttpServer } from './http_server'; import { HttpsRedirectServer } from './https_redirect_server'; @@ -61,21 +61,16 @@ export class HttpService implements CoreService('server'), - coreContext.configService.atPath('csp') - ).pipe( - map( - ([rawHttpConfig, rawCspConfig]) => - new HttpConfig(rawHttpConfig, rawCspConfig, coreContext.env) - ) - ); - this.httpServer = new HttpServer(coreContext.logger, 'Kibana'); - this.httpsRedirectServer = new HttpsRedirectServer( - coreContext.logger.get('http', 'redirect', 'server') - ); + configService.atPath(httpConfig.path), + configService.atPath(cspConfig.path) + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + this.httpServer = new HttpServer(logger, 'Kibana'); + this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } public async setup(deps: SetupDeps) { diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 109ec0791b8dd..92217515a22a1 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -17,7 +17,7 @@ * under the License. */ import { IContextProvider, IContextContainer } from '../context'; -import { CspConfig } from '../csp'; +import { ICspConfig } from '../csp'; import { RequestHandler, IRouter } from './router'; import { HttpServerSetup } from './http_server'; import { SessionStorageCookieOptions } from './cookie_session_storage'; @@ -186,7 +186,7 @@ export interface HttpServiceSetup { /** * The CSP config used for Kibana. */ - csp: CspConfig; + csp: ICspConfig; /** * Flag showing whether a server was configured to use TLS connection. diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 53e373b8060b0..835c5872d51a3 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,7 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; -export { CspConfig } from './csp'; +export { CspConfig, ICspConfig } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c6312da8252da..07b60e771d643 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -93,7 +93,7 @@ function createCoreSetupMock() { registerOnPostAuth: httpService.registerOnPostAuth, registerOnPreResponse: httpService.registerOnPreResponse, basePath: httpService.basePath, - csp: new CspConfig(), + csp: CspConfig.DEFAULT, isTlsEnabled: httpService.isTlsEnabled, createRouter: jest.fn(), registerRouteHandlerContext: jest.fn(), diff --git a/src/core/server/types.ts b/src/core/server/types.ts index c8a3f8fff1b6b..6e3e6bfe208a6 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -22,4 +22,4 @@ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; -export { CspConfig } from './csp'; +export { ICspConfig } from './csp'; 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 48066eea14e78..395cb60587832 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 @@ -17,80 +17,74 @@ * under the License. */ -import sinon from 'sinon'; -import { Server } from 'hapi'; -import { CspConfig } from '../../../../../../core/server'; +import { CspConfig, ICspConfig } from '../../../../../../core/server'; import { createCspCollector } from './csp_collector'; -interface MockConfig { - get: (x: string) => any; -} - -const getMockKbnServer = (mockConfig: MockConfig) => ({ - config: () => mockConfig, +const createMockKbnServer = () => ({ + newPlatform: { + setup: { + core: { + http: { + csp: new CspConfig(), + }, + }, + }, + }, }); -test('fetches whether strict mode is enabled', async () => { - const { collector, mockConfig } = setupCollector(); +describe('csp collector', () => { + let kbnServer: ReturnType; - expect((await collector.fetch()).strict).toEqual(true); + function updateCsp(config: Partial) { + kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config); + } - mockConfig.get.withArgs('csp.strict').returns(false); - expect((await collector.fetch()).strict).toEqual(false); -}); + beforeEach(() => { + kbnServer = createMockKbnServer(); + }); -test('fetches whether the legacy browser warning is enabled', async () => { - const { collector, mockConfig } = setupCollector(); + test('fetches whether strict mode is enabled', async () => { + const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true); + expect((await collector.fetch()).strict).toEqual(true); - mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(false); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false); -}); + updateCsp({ strict: false }); + expect((await collector.fetch()).strict).toEqual(false); + }); -test('fetches whether the csp rules have been changed or not', async () => { - const { collector, mockConfig } = setupCollector(); + test('fetches whether the legacy browser warning is enabled', async () => { + const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false); + expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true); - mockConfig.get.withArgs('csp.rules').returns(['not', 'default']); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true); -}); + updateCsp({ warnLegacyBrowsers: false }); + expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false); + }); -test('does not include raw csp.rules under any property names', async () => { - const { collector } = setupCollector(); - - // It's important that we do not send the value of csp.rules here as it - // can be customized with values that can be identifiable to given - // installs, such as URLs - // - // We use a snapshot here to ensure csp.rules isn't finding its way into the - // payload under some new and unexpected variable name (e.g. cspRules). - expect(await collector.fetch()).toMatchInlineSnapshot(` - Object { - "rulesChangedFromDefault": false, - "strict": true, - "warnLegacyBrowsers": true, - } - `); -}); + test('fetches whether the csp rules have been changed or not', async () => { + const collector = createCspCollector(kbnServer as any); -test('does not arbitrarily fetch other csp configurations (e.g. whitelist only)', async () => { - const { collector, mockConfig } = setupCollector(); + expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false); - mockConfig.get.withArgs('csp.foo').returns('bar'); + updateCsp({ rules: ['not', 'default'] }); + expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true); + }); - expect(await collector.fetch()).not.toHaveProperty('foo'); -}); - -function setupCollector() { - const mockConfig = { get: sinon.stub() }; - const defaultCspConfig = new CspConfig(); - mockConfig.get.withArgs('csp.rules').returns(defaultCspConfig.rules); - mockConfig.get.withArgs('csp.strict').returns(defaultCspConfig.strict); - mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(defaultCspConfig.warnLegacyBrowsers); + test('does not include raw csp rules under any property names', async () => { + const collector = createCspCollector(kbnServer as any); - const mockKbnServer = getMockKbnServer(mockConfig); - - return { mockConfig, collector: createCspCollector(mockKbnServer as Server) }; -} + // It's important that we do not send the value of csp.rules here as it + // can be customized with values that can be identifiable to given + // installs, such as URLs + // + // We use a snapshot here to ensure csp.rules isn't finding its way into the + // payload under some new and unexpected variable name (e.g. cspRules). + expect(await collector.fetch()).toMatchInlineSnapshot(` + Object { + "rulesChangedFromDefault": false, + "strict": true, + "warnLegacyBrowsers": true, + } + `); + }); +}); 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 dadee4d1a5457..6622ed4bef478 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 @@ -27,8 +27,6 @@ export function createCspCollector(server: Server) { isReady: () => true, async fetch() { const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp; - // This is used to get the default CSP header string. - const { header: defaultCspHeader } = new CspConfig(); return { strict, @@ -36,7 +34,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 !== defaultCspHeader, + rulesChangedFromDefault: header !== CspConfig.DEFAULT.header, }; }, }; From b81a6f82f3c7811543c774af1216e1b48d5437e1 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 05:40:15 -0600 Subject: [PATCH 10/13] Rebase and update docs --- ...a-plugin-server.cspconfig._constructor_.md | 20 ------------- .../kibana-plugin-server.cspconfig.header.md | 13 --------- .../server/kibana-plugin-server.cspconfig.md | 29 ------------------- .../kibana-plugin-server.cspconfig.rules.md | 13 --------- .../kibana-plugin-server.cspconfig.strict.md | 13 --------- ...gin-server.cspconfig.warnlegacybrowsers.md | 13 --------- ...bana-plugin-server.httpservicesetup.csp.md | 2 +- .../kibana-plugin-server.httpservicesetup.md | 2 +- .../server/kibana-plugin-server.icspconfig.md | 13 +++++++++ .../core/server/kibana-plugin-server.md | 2 +- src/core/server/server.api.md | 11 +++++-- 11 files changed, 25 insertions(+), 106 deletions(-) delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.header.md delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.md delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.rules.md delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.strict.md delete mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md create mode 100644 docs/development/core/server/kibana-plugin-server.icspconfig.md diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md b/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md deleted file mode 100644 index a7c268b60ab0d..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [(constructor)](./kibana-plugin-server.cspconfig._constructor_.md) - -## CspConfig.(constructor) - -Returns the default CSP configuration when passed with no config - -Signature: - -```typescript -constructor(rawCspConfig?: Partial); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| rawCspConfig | Partial<CspConfigType> | | - diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md deleted file mode 100644 index 4b188490d3bb0..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [header](./kibana-plugin-server.cspconfig.header.md) - -## CspConfig.header property - -The CSP rules in a formatted directives string for use in a `Content-Security-Policy` header. - -Signature: - -```typescript -readonly header: string; -``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md deleted file mode 100644 index bb07d10a56295..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) - -## CspConfig class - -CSP configuration for use in Kibana. - -Signature: - -```typescript -export declare class CspConfig -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(rawCspConfig)](./kibana-plugin-server.cspconfig._constructor_.md) | | Returns the default CSP configuration when passed with no config | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [header](./kibana-plugin-server.cspconfig.header.md) | | string | The CSP rules in a formatted directives string for use in a Content-Security-Policy header. | -| [rules](./kibana-plugin-server.cspconfig.rules.md) | | string[] | The CSP rules used for Kibana. | -| [strict](./kibana-plugin-server.cspconfig.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.cspconfig.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.cspconfig.rules.md b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md deleted file mode 100644 index 02190c943b602..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rules](./kibana-plugin-server.cspconfig.rules.md) - -## CspConfig.rules property - -The CSP rules used for Kibana. - -Signature: - -```typescript -readonly rules: string[]; -``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md deleted file mode 100644 index 5592ccfe8386c..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [strict](./kibana-plugin-server.cspconfig.strict.md) - -## CspConfig.strict property - -Specify whether browsers that do not support CSP should be able to use Kibana. Use `true` to block and `false` to allow. - -Signature: - -```typescript -readonly strict: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md deleted file mode 100644 index 8b29f6258f4df..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) - -## CspConfig.warnLegacyBrowsers property - -Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. - -Signature: - -```typescript -readonly warnLegacyBrowsers: boolean; -``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md index e481435fdbd24..7bf83305613ea 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.csp.md @@ -9,5 +9,5 @@ The CSP config used for Kibana. Signature: ```typescript -csp: CspConfig; +csp: ICspConfig; ``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index a20bee3839733..99d4caf40c0d3 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -19,7 +19,7 @@ export interface HttpServiceSetup | [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | | [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | | [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | -| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | CspConfig | The CSP config used for Kibana. | +| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | | [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.md b/docs/development/core/server/kibana-plugin-server.icspconfig.md new file mode 100644 index 0000000000000..c763babced4e2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) + +## ICspConfig type + +CSP configuration for use in Kibana. + +Signature: + +```typescript +export declare type ICspConfig = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 662225426620b..a4fef80b4e3e8 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -18,7 +18,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | | [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | -| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | @@ -169,6 +168,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | | [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index aebf09839114e..6a138be8fa317 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -574,10 +574,12 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; } -// @public +// @internal export class CspConfig { // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts constructor(rawCspConfig?: Partial); + // (undocumented) + static readonly DEFAULT: CspConfig; readonly header: string; readonly rules: string[]; readonly strict: boolean; @@ -723,7 +725,7 @@ export interface HttpServiceSetup { basePath: IBasePath; createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; createRouter: () => IRouter; - csp: CspConfig; + csp: ICspConfig; isTlsEnabled: boolean; registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; @@ -752,6 +754,11 @@ export interface IContextContainer> { // @public export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; +// Warning: (ae-incompatible-release-tags) The symbol "ICspConfig" is marked as @public, but its signature references "CspConfig" which is marked as @internal +// +// @public +export type ICspConfig = Pick; + // @public export interface IKibanaResponse { // (undocumented) From b3ebf53cd4708ed4401fb029776ef1606075f59a Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 12:05:53 -0600 Subject: [PATCH 11/13] Remove legacy API from route definition params, review nits --- .../kibana-plugin-server.cspconfig.default.md | 11 +++++++ .../kibana-plugin-server.cspconfig.header.md | 11 +++++++ .../server/kibana-plugin-server.cspconfig.md | 28 ++++++++++++++++ .../kibana-plugin-server.cspconfig.rules.md | 11 +++++++ .../kibana-plugin-server.cspconfig.strict.md | 11 +++++++ ...gin-server.cspconfig.warnlegacybrowsers.md | 11 +++++++ .../kibana-plugin-server.icspconfig.header.md | 13 ++++++++ .../server/kibana-plugin-server.icspconfig.md | 14 ++++++-- .../kibana-plugin-server.icspconfig.rules.md | 13 ++++++++ .../kibana-plugin-server.icspconfig.strict.md | 13 ++++++++ ...in-server.icspconfig.warnlegacybrowsers.md | 13 ++++++++ .../core/server/kibana-plugin-server.md | 3 +- src/core/server/csp/csp_config.ts | 32 +++++++++++-------- src/core/server/server.api.md | 19 ++++++++--- x-pack/legacy/plugins/security/index.js | 1 - x-pack/plugins/security/server/plugin.ts | 3 +- .../routes/authentication/basic.test.ts | 3 +- .../routes/authentication/common.test.ts | 3 +- .../routes/authentication/index.test.ts | 2 +- .../server/routes/authentication/index.ts | 4 +-- .../server/routes/authentication/oidc.ts | 12 ++----- .../server/routes/authentication/saml.test.ts | 3 +- .../server/routes/authentication/saml.ts | 12 ++----- .../security/server/routes/index.mock.ts | 2 +- .../plugins/security/server/routes/index.ts | 3 +- .../routes/users/change_password.test.ts | 3 +- 26 files changed, 198 insertions(+), 56 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.default.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.header.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.rules.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.strict.md create mode 100644 docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md create mode 100644 docs/development/core/server/kibana-plugin-server.icspconfig.header.md create mode 100644 docs/development/core/server/kibana-plugin-server.icspconfig.rules.md create mode 100644 docs/development/core/server/kibana-plugin-server.icspconfig.strict.md create mode 100644 docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.default.md b/docs/development/core/server/kibana-plugin-server.cspconfig.default.md new file mode 100644 index 0000000000000..56e6cf35cdd13 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.default.md @@ -0,0 +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) + +## CspConfig.DEFAULT property + +Signature: + +```typescript +static readonly DEFAULT: CspConfig; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.header.md b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md new file mode 100644 index 0000000000000..e3a3d5d712a42 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.header.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [header](./kibana-plugin-server.cspconfig.header.md) + +## CspConfig.header property + +Signature: + +```typescript +readonly header: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md new file mode 100644 index 0000000000000..e5276991be404 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) + +## CspConfig class + +CSP configuration for use in Kibana. + +Signature: + +```typescript +export declare class CspConfig implements ICspConfig +``` + +## Properties + +| 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[] | | +| [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | +| [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | + +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md new file mode 100644 index 0000000000000..c5270c2375dc1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.rules.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [rules](./kibana-plugin-server.cspconfig.rules.md) + +## CspConfig.rules property + +Signature: + +```typescript +readonly rules: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md new file mode 100644 index 0000000000000..3ac48edd374c9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.strict.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [strict](./kibana-plugin-server.cspconfig.strict.md) + +## CspConfig.strict property + +Signature: + +```typescript +readonly strict: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md new file mode 100644 index 0000000000000..59d661593d940 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.warnlegacybrowsers.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CspConfig](./kibana-plugin-server.cspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) + +## CspConfig.warnLegacyBrowsers property + +Signature: + +```typescript +readonly warnLegacyBrowsers: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.header.md b/docs/development/core/server/kibana-plugin-server.icspconfig.header.md new file mode 100644 index 0000000000000..d757863fdc12d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.header.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [header](./kibana-plugin-server.icspconfig.header.md) + +## ICspConfig.header property + +The CSP rules in a formatted directives string for use in a `Content-Security-Policy` header. + +Signature: + +```typescript +readonly header: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.md b/docs/development/core/server/kibana-plugin-server.icspconfig.md index c763babced4e2..fb8188386a376 100644 --- a/docs/development/core/server/kibana-plugin-server.icspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.md @@ -2,12 +2,22 @@ [Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) -## ICspConfig type +## ICspConfig interface CSP configuration for use in Kibana. Signature: ```typescript -export declare type ICspConfig = Pick; +export interface ICspConfig ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [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. | +| [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.rules.md b/docs/development/core/server/kibana-plugin-server.icspconfig.rules.md new file mode 100644 index 0000000000000..6216e6d817136 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.rules.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [rules](./kibana-plugin-server.icspconfig.rules.md) + +## ICspConfig.rules property + +The CSP rules used for Kibana. + +Signature: + +```typescript +readonly rules: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md b/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md new file mode 100644 index 0000000000000..4ab97ad9f665a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.strict.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [strict](./kibana-plugin-server.icspconfig.strict.md) + +## ICspConfig.strict property + +Specify whether browsers that do not support CSP should be able to use Kibana. Use `true` to block and `false` to allow. + +Signature: + +```typescript +readonly strict: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md b/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md new file mode 100644 index 0000000000000..aea35f0569448 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.icspconfig.warnlegacybrowsers.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ICspConfig](./kibana-plugin-server.icspconfig.md) > [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) + +## ICspConfig.warnLegacyBrowsers property + +Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. + +Signature: + +```typescript +readonly warnLegacyBrowsers: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index a4fef80b4e3e8..e97ecbcfaf739 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -18,6 +18,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [BasePath](./kibana-plugin-server.basepath.md) | Access or manipulate the Kibana base path | | [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | +| [CspConfig](./kibana-plugin-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchErrorHelpers](./kibana-plugin-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | @@ -64,6 +65,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | | [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | @@ -168,7 +170,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IBasePath](./kibana-plugin-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-server.basepath.md) | | [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index b0851d014d438..4c48cace46b2f 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -22,43 +22,49 @@ import { CspConfigType, config } from './config'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); /** - * CSP configuration for use in Kibana. + * Csp configuration for use in Kibana. * @public */ -export type ICspConfig = Pick; - -/** - * CSP configuration for use in Kibana. - * @internal - */ -export class CspConfig { - static readonly DEFAULT = new CspConfig(); - +export interface ICspConfig { /** * The CSP rules used for Kibana. */ - public readonly rules: string[]; + readonly rules: string[]; /** * Specify whether browsers that do not support CSP should be * able to use Kibana. Use `true` to block and `false` to allow. */ - public readonly strict: boolean; + readonly strict: boolean; /** * Specify whether users with legacy browsers should be warned * about their lack of Kibana security compliance. */ - public readonly warnLegacyBrowsers: boolean; + readonly warnLegacyBrowsers: boolean; /** * The CSP rules in a formatted directives string for use * in a `Content-Security-Policy` header. */ + readonly header: string; +} + +/** + * CSP configuration for use in Kibana. + * @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; /** * Returns the default CSP configuration when passed with no config + * @internal */ constructor(rawCspConfig: Partial = {}) { const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6a138be8fa317..0bb82b52a4a05 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -574,15 +574,21 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; } -// @internal -export class CspConfig { +// @public +export class CspConfig implements ICspConfig { // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts + // + // @internal constructor(rawCspConfig?: Partial); // (undocumented) static readonly DEFAULT: CspConfig; + // (undocumented) readonly header: string; + // (undocumented) readonly rules: string[]; + // (undocumented) readonly strict: boolean; + // (undocumented) readonly warnLegacyBrowsers: boolean; } @@ -754,10 +760,13 @@ export interface IContextContainer> { // @public export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; -// Warning: (ae-incompatible-release-tags) The symbol "ICspConfig" is marked as @public, but its signature references "CspConfig" which is marked as @internal -// // @public -export type ICspConfig = Pick; +export interface ICspConfig { + readonly header: string; + readonly rules: string[]; + readonly strict: boolean; + readonly warnLegacyBrowsers: boolean; +} // @public export interface IKibanaResponse { diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 2e265c8be158a..c6438db6dc6eb 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -126,7 +126,6 @@ export const security = (kibana) => new kibana.Plugin({ isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind( server.plugins.kibana.systemApi ), - cspRules: server.newPlatform.setup.core.http.csp.header, }); // Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator` diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 633b064da6d61..2f9cb148b7c78 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -43,7 +43,6 @@ export type FeaturesService = Pick; */ export interface LegacyAPI { isSystemAPIRequest: (request: KibanaRequest) => boolean; - cspRules: string; savedObjects: SavedObjectsLegacyService; auditLogger: { log: (eventType: string, message: string, data?: Record) => void; @@ -168,7 +167,7 @@ export class Plugin { config, authc, authz, - getLegacyAPI: this.getLegacyAPI, + csp: core.http.csp, }); const adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); diff --git a/x-pack/plugins/security/server/routes/authentication/basic.test.ts b/x-pack/plugins/security/server/routes/authentication/basic.test.ts index 8e24f99b1302d..be17b3e29f854 100644 --- a/x-pack/plugins/security/server/routes/authentication/basic.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/basic.test.ts @@ -15,7 +15,6 @@ import { import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { ConfigType } from '../../config'; -import { LegacyAPI } from '../../plugin'; import { defineBasicRoutes } from './basic'; import { @@ -50,7 +49,7 @@ describe('Basic authentication routes', () => { config: { authc: { providers: ['saml'] } } as ConfigType, authc, authz: authorizationMock.create(), - getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + csp: httpServiceMock.createSetupContract().csp, }); }); diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index f57fb1d5a7d66..5d5868d4cc593 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -15,7 +15,6 @@ import { import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, DeauthenticationResult } from '../../authentication'; import { ConfigType } from '../../config'; -import { LegacyAPI } from '../../plugin'; import { defineCommonRoutes } from './common'; import { @@ -50,7 +49,7 @@ describe('Common authentication routes', () => { config: { authc: { providers: ['saml'] } } as ConfigType, authc, authz: authorizationMock.create(), - getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + csp: httpServiceMock.createSetupContract().csp, }); }); diff --git a/x-pack/plugins/security/server/routes/authentication/index.test.ts b/x-pack/plugins/security/server/routes/authentication/index.test.ts index cad370b7837e1..5450dfafa5e49 100644 --- a/x-pack/plugins/security/server/routes/authentication/index.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/index.test.ts @@ -27,7 +27,7 @@ describe('Authentication routes', () => { config: { authc: { providers: ['basic'] } } as ConfigType, authc: authenticationMock.create(), authz: authorizationMock.create(), - getLegacyAPI: () => ({ cspRules: 'test-csp-rule' }), + csp: httpServiceMock.createSetupContract().csp, }); const samlRoutePathPredicate = ([{ path }]: [{ path: string }, any]) => diff --git a/x-pack/plugins/security/server/routes/authentication/index.ts b/x-pack/plugins/security/server/routes/authentication/index.ts index 21f015cc23b68..6035025564cbf 100644 --- a/x-pack/plugins/security/server/routes/authentication/index.ts +++ b/x-pack/plugins/security/server/routes/authentication/index.ts @@ -11,13 +11,13 @@ import { defineCommonRoutes } from './common'; import { defineOIDCRoutes } from './oidc'; import { RouteDefinitionParams } from '..'; -export function createCustomResourceResponse(body: string, contentType: string, cspRules: string) { +export function createCustomResourceResponse(body: string, contentType: string, cspHeader: string) { return { body, headers: { 'content-type': contentType, 'cache-control': 'private, no-cache, no-store', - 'content-security-policy': cspRules, + 'content-security-policy': cspHeader, }, statusCode: 200, }; diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts index 8483630763ae6..ee9c2b46ac878 100644 --- a/x-pack/plugins/security/server/routes/authentication/oidc.ts +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -17,13 +17,7 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for SAML authentication. */ -export function defineOIDCRoutes({ - router, - logger, - authc, - getLegacyAPI, - basePath, -}: RouteDefinitionParams) { +export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: RouteDefinitionParams) { // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. for (const path of ['/api/security/oidc/implicit', '/api/security/v1/oidc/implicit']) { /** @@ -54,7 +48,7 @@ export function defineOIDCRoutes({ `, 'text/html', - getLegacyAPI().cspRules + csp.header ) ); } @@ -82,7 +76,7 @@ export function defineOIDCRoutes({ ); `, 'text/javascript', - getLegacyAPI().cspRules + csp.header ) ); } diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index c8735f9f87f4a..b6447273c2559 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -9,7 +9,6 @@ import { Authentication, AuthenticationResult, SAMLLoginStep } from '../../authe import { defineSAMLRoutes } from './saml'; import { ConfigType } from '../../config'; import { IRouter, RequestHandler, RouteConfig } from '../../../../../../src/core/server'; -import { LegacyAPI } from '../../plugin'; import { elasticsearchServiceMock, @@ -36,7 +35,7 @@ describe('SAML authentication routes', () => { config: { authc: { providers: ['saml'] } } as ConfigType, authc, authz: authorizationMock.create(), - getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + csp: httpServiceMock.createSetupContract().csp, }); }); diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index f724d0e7708be..06acf5283fe97 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -12,13 +12,7 @@ import { RouteDefinitionParams } from '..'; /** * Defines routes required for SAML authentication. */ -export function defineSAMLRoutes({ - router, - logger, - authc, - getLegacyAPI, - basePath, -}: RouteDefinitionParams) { +export function defineSAMLRoutes({ router, logger, authc, csp, basePath }: RouteDefinitionParams) { router.get( { path: '/api/security/saml/capture-url-fragment', @@ -36,7 +30,7 @@ export function defineSAMLRoutes({ `, 'text/html', - getLegacyAPI().cspRules + csp.header ) ); } @@ -57,7 +51,7 @@ export function defineSAMLRoutes({ ); `, 'text/javascript', - getLegacyAPI().cspRules + csp.header ) ); } diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 2d3a3154e6499..8a32e6b00bdf4 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -17,11 +17,11 @@ export const routeDefinitionParamsMock = { create: () => ({ router: httpServiceMock.createRouter(), basePath: httpServiceMock.createBasePath(), + csp: httpServiceMock.createSetupContract().csp, logger: loggingServiceMock.create().get(), clusterClient: elasticsearchServiceMock.createClusterClient(), config: { ...ConfigSchema.validate({}), encryptionKey: 'some-enc-key' }, authc: authenticationMock.create(), authz: authorizationMock.create(), - getLegacyAPI: jest.fn(), }), }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 756eaa76e2c2e..ade840e7ca495 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -8,7 +8,6 @@ import { CoreSetup, IClusterClient, IRouter, Logger } from '../../../../../src/c import { Authentication } from '../authentication'; import { Authorization } from '../authorization'; import { ConfigType } from '../config'; -import { LegacyAPI } from '../plugin'; import { defineAuthenticationRoutes } from './authentication'; import { defineAuthorizationRoutes } from './authorization'; @@ -22,12 +21,12 @@ import { defineUsersRoutes } from './users'; export interface RouteDefinitionParams { router: IRouter; basePath: CoreSetup['http']['basePath']; + csp: CoreSetup['http']['csp']; logger: Logger; clusterClient: IClusterClient; config: ConfigType; authc: Authentication; authz: Authorization; - getLegacyAPI: () => Pick; } export function defineRoutes(params: RouteDefinitionParams) { diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index 9f88d28bc115f..80a25e03ede62 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -17,7 +17,6 @@ import { import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { ConfigType } from '../../config'; -import { LegacyAPI } from '../../plugin'; import { defineChangeUserPasswordRoutes } from './change_password'; import { @@ -77,7 +76,7 @@ describe('Change password', () => { config: { authc: { providers: ['saml'] } } as ConfigType, authc, authz: authorizationMock.create(), - getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + csp: httpServiceMock.createSetupContract().csp, }); const [changePasswordRouteConfig, changePasswordRouteHandler] = router.post.mock.calls[0]; From 451da17dc553ff0740017f090200ac10963e1526 Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 12:22:39 -0600 Subject: [PATCH 12/13] Clean up config path usages for consistency --- src/core/server/csp/config.ts | 3 +++ src/core/server/index.ts | 2 +- src/core/server/legacy/legacy_service.ts | 33 +++++++++++------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts index be66c9122e11d..41a319748a1c9 100644 --- a/src/core/server/csp/config.ts +++ b/src/core/server/csp/config.ts @@ -19,6 +19,9 @@ import { TypeOf, schema } from '@kbn/config-schema'; +/** + * @internal + */ export type CspConfigType = TypeOf; export const config = { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 835c5872d51a3..a2a4c6069b916 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,7 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; -export { CspConfig, ICspConfig } from './csp'; +export { CspConfig, ICspConfig, CspConfigType } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 43a47acc989ed..662cc0bdf2f3a 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -25,9 +25,9 @@ import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { SavedObjectsLegacyUiExports } from '../types'; import { Config, ConfigDeprecationProvider } from '../config'; import { CoreContext } from '../core_context'; -import { CspConfigType } from '../csp'; -import { DevConfig, DevConfigType } from '../dev'; -import { BasePathProxyServer, HttpConfig, HttpConfigType } from '../http'; +import { CspConfigType, config as cspConfig } from '../csp'; +import { DevConfig, DevConfigType, config as devConfig } from '../dev'; +import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http'; import { Logger } from '../logging'; import { PluginsServiceSetup, PluginsServiceStart } from '../plugins'; import { findLegacyPluginSpecs } from './plugins'; @@ -113,19 +113,16 @@ export class LegacyService implements CoreService { private settings: Record | undefined; constructor(private readonly coreContext: CoreContext) { - this.log = coreContext.logger.get('legacy-service'); - this.devConfig$ = coreContext.configService - .atPath('dev') + const { logger, configService, env } = coreContext; + + this.log = logger.get('legacy-service'); + this.devConfig$ = configService + .atPath(devConfig.path) .pipe(map(rawConfig => new DevConfig(rawConfig))); this.httpConfig$ = combineLatest( - coreContext.configService.atPath('server'), - coreContext.configService.atPath('csp') - ).pipe( - map( - ([rawHttpConfig, rawCspConfig]) => - new HttpConfig(rawHttpConfig, rawCspConfig, coreContext.env) - ) - ); + configService.atPath(httpConfig.path), + configService.atPath(cspConfig.path) + ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); } public async discoverPlugins(): Promise { @@ -247,8 +244,8 @@ export class LegacyService implements CoreService { ? combineLatest(this.devConfig$, this.httpConfig$).pipe( first(), map( - ([devConfig, httpConfig]) => - new BasePathProxyServer(this.coreContext.logger.get('server'), httpConfig, devConfig) + ([dev, http]) => + new BasePathProxyServer(this.coreContext.logger.get('server'), http, dev) ) ) : EMPTY; @@ -347,9 +344,9 @@ export class LegacyService implements CoreService { require('../../../cli/repl').startRepl(kbnServer); } - const httpConfig = await this.httpConfig$.pipe(first()).toPromise(); + const { autoListen } = await this.httpConfig$.pipe(first()).toPromise(); - if (httpConfig.autoListen) { + if (autoListen) { try { await kbnServer.listen(); } catch (err) { From fd01be74896eb5f1e8e09d5a6147e80e4807cf5c Mon Sep 17 00:00:00 2001 From: Eli Perelman Date: Fri, 13 Dec 2019 13:32:33 -0600 Subject: [PATCH 13/13] Regenerate docs --- src/core/server/csp/csp_config.ts | 6 +++--- src/core/server/index.ts | 2 +- src/core/server/server.api.md | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index 4c48cace46b2f..bb57702a4a241 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -17,12 +17,12 @@ * under the License. */ -import { CspConfigType, config } from './config'; +import { config } from './config'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); /** - * Csp configuration for use in Kibana. + * CSP configuration for use in Kibana. * @public */ export interface ICspConfig { @@ -66,7 +66,7 @@ export class CspConfig implements ICspConfig { * Returns the default CSP configuration when passed with no config * @internal */ - constructor(rawCspConfig: Partial = {}) { + constructor(rawCspConfig: Partial> = {}) { const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; this.rules = source.rules; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index a2a4c6069b916..835c5872d51a3 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -68,7 +68,7 @@ export { HandlerParameters, } from './context'; export { CoreId } from './core_context'; -export { CspConfig, ICspConfig, CspConfigType } from './csp'; +export { CspConfig, ICspConfig } from './csp'; export { ClusterClient, IClusterClient, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 0bb82b52a4a05..381ed9da2b62a 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -576,10 +576,8 @@ export interface CoreStart { // @public export class CspConfig implements ICspConfig { - // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts - // // @internal - constructor(rawCspConfig?: Partial); + constructor(rawCspConfig?: Partial>); // (undocumented) static readonly DEFAULT: CspConfig; // (undocumented)