Move CSP options to new platform (#52698)

* Move CSP options to new platform

* Expose SharedGlobalConfig from root

* Derive CSP options from config

* Consolidate CSP configuration with HTTP config

* Fix outstanding config renames

* Remove legacy CSP configuration calls, migrate to platform properties

* Revise docs

* Fix test from type change

* Expose ICspConfig, consolidate and simplify CSP defaults access

* Rebase and update docs

* Remove legacy API from route definition params, review nits

* Clean up config path usages for consistency

* Regenerate docs
This commit is contained in:
Eli Perelman 2019-12-13 15:57:17 -06:00 committed by GitHub
parent 1ea9d791bc
commit 614bde927e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 610 additions and 255 deletions

3
.github/CODEOWNERS vendored
View file

@ -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,12 +87,12 @@
/src/legacy/server/status/ @elastic/kibana-platform
# 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

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [DEFAULT](./kibana-plugin-server.cspconfig.default.md)
## CspConfig.DEFAULT property
<b>Signature:</b>
```typescript
static readonly DEFAULT: CspConfig;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [header](./kibana-plugin-server.cspconfig.header.md)
## CspConfig.header property
<b>Signature:</b>
```typescript
readonly header: string;
```

View file

@ -0,0 +1,28 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md)
## CspConfig class
CSP configuration for use in Kibana.
<b>Signature:</b>
```typescript
export declare class CspConfig implements ICspConfig
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [DEFAULT](./kibana-plugin-server.cspconfig.default.md) | <code>static</code> | <code>CspConfig</code> | |
| [header](./kibana-plugin-server.cspconfig.header.md) | | <code>string</code> | |
| [rules](./kibana-plugin-server.cspconfig.rules.md) | | <code>string[]</code> | |
| [strict](./kibana-plugin-server.cspconfig.strict.md) | | <code>boolean</code> | |
| [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | <code>boolean</code> | |
## 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.

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [rules](./kibana-plugin-server.cspconfig.rules.md)
## CspConfig.rules property
<b>Signature:</b>
```typescript
readonly rules: string[];
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [strict](./kibana-plugin-server.cspconfig.strict.md)
## CspConfig.strict property
<b>Signature:</b>
```typescript
readonly strict: boolean;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md)
## CspConfig.warnLegacyBrowsers property
<b>Signature:</b>
```typescript
readonly warnLegacyBrowsers: boolean;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) &gt; [csp](./kibana-plugin-server.httpservicesetup.csp.md)
## HttpServiceSetup.csp property
The CSP config used for Kibana.
<b>Signature:</b>
```typescript
csp: ICspConfig;
```

View file

@ -19,6 +19,7 @@ export interface HttpServiceSetup
| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | <code>IBasePath</code> | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md)<!-- -->. |
| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <code>&lt;T&gt;(cookieOptions: SessionStorageCookieOptions&lt;T&gt;) =&gt; Promise&lt;SessionStorageFactory&lt;T&gt;&gt;</code> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) |
| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | <code>() =&gt; IRouter</code> | Provides ability to declare a handler function for a particular path and HTTP request method. |
| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | <code>ICspConfig</code> | The CSP config used for Kibana. |
| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | <code>boolean</code> | Flag showing whether a server was configured to use TLS connection. |
| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | <code>(handler: AuthenticationHandler) =&gt; void</code> | To define custom authentication and/or authorization mechanism for incoming requests. |
| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | <code>(handler: OnPostAuthHandler) =&gt; void</code> | To define custom logic to perform for incoming requests. |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ICspConfig](./kibana-plugin-server.icspconfig.md) &gt; [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.
<b>Signature:</b>
```typescript
readonly header: string;
```

View file

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ICspConfig](./kibana-plugin-server.icspconfig.md)
## ICspConfig interface
CSP configuration for use in Kibana.
<b>Signature:</b>
```typescript
export interface ICspConfig
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [header](./kibana-plugin-server.icspconfig.header.md) | <code>string</code> | The CSP rules in a formatted directives string for use in a <code>Content-Security-Policy</code> header. |
| [rules](./kibana-plugin-server.icspconfig.rules.md) | <code>string[]</code> | The CSP rules used for Kibana. |
| [strict](./kibana-plugin-server.icspconfig.strict.md) | <code>boolean</code> | Specify whether browsers that do not support CSP should be able to use Kibana. Use <code>true</code> to block and <code>false</code> to allow. |
| [warnLegacyBrowsers](./kibana-plugin-server.icspconfig.warnlegacybrowsers.md) | <code>boolean</code> | Specify whether users with legacy browsers should be warned about their lack of Kibana security compliance. |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ICspConfig](./kibana-plugin-server.icspconfig.md) &gt; [rules](./kibana-plugin-server.icspconfig.rules.md)
## ICspConfig.rules property
The CSP rules used for Kibana.
<b>Signature:</b>
```typescript
readonly rules: string[];
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ICspConfig](./kibana-plugin-server.icspconfig.md) &gt; [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.
<b>Signature:</b>
```typescript
readonly strict: boolean;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [ICspConfig](./kibana-plugin-server.icspconfig.md) &gt; [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.
<b>Signature:</b>
```typescript
readonly warnLegacyBrowsers: boolean;
```

View file

@ -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 <code>asScoped(...)</code>).<!-- -->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 <code>body.error.header[WWW-Authenticate]</code> |
| [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 <code>hapi</code> 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) | |

View file

@ -0,0 +1,42 @@
/*
* 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';
/**
* @internal
*/
export type CspConfigType = TypeOf<typeof config.schema>;
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: [
`script-src 'unsafe-eval' 'self'`,
`worker-src blob: 'self'`,
`style-src 'unsafe-inline' 'self'`,
],
}),
strict: schema.boolean({ defaultValue: true }),
warnLegacyBrowsers: schema.boolean({ defaultValue: true }),
}),
};

View file

@ -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('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 {
"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,
}
`);
});
});

View file

@ -0,0 +1,77 @@
/*
* 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';
const DEFAULT_CONFIG = Object.freeze(config.schema.validate({}));
/**
* CSP configuration for use in Kibana.
* @public
*/
export interface ICspConfig {
/**
* The CSP rules used for Kibana.
*/
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.
*/
readonly strict: boolean;
/**
* Specify whether users with legacy browsers should be warned
* about their lack of Kibana security compliance.
*/
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<Omit<ICspConfig, 'header'>> = {}) {
const source = { ...DEFAULT_CONFIG, ...rawCspConfig };
this.rules = source.rules;
this.strict = source.strict;
this.warnLegacyBrowsers = source.warnLegacyBrowsers;
this.header = source.rules.join('; ');
}
}

View file

@ -17,16 +17,7 @@
* under the License.
*/
export const DEFAULT_CSP_RULES = Object.freeze([
`script-src 'unsafe-eval' 'self'`,
`worker-src blob: 'self'`,
`style-src 'unsafe-inline' 'self'`,
]);
import { CspConfig, ICspConfig } from './csp_config';
import { CspConfigType, config } from './config';
export const DEFAULT_CSP_STRICT = true;
export const DEFAULT_CSP_WARN_LEGACY_BROWSERS = true;
export function createCSPRuleString(rules: string[]) {
return rules.join('; ');
}
export { CspConfig, CspConfigType, config, ICspConfig };

View file

@ -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())
);

View file

@ -19,6 +19,7 @@
import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema';
import { Env } from '../config';
import { CspConfigType, CspConfig, ICspConfig } 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: ICspConfig;
/**
* @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);
}
}

View file

@ -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,

View file

@ -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: CspConfig.DEFAULT,
auth: {
get: jest.fn(),
isAuthenticated: jest.fn(),

View file

@ -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<HttpConfigType> = {}) => {
logger
);
configService.setSchema(config.path, config.schema);
configService.setSchema(cspConfig.path, cspConfig.schema);
return configService;
};
const contextSetup = contextServiceMock.createSetupContract();

View file

@ -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,9 +28,10 @@ import { Logger } from '../logging';
import { ContextSetup } from '../context';
import { CoreContext } from '../core_context';
import { PluginOpaqueId } from '../plugins';
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';
@ -60,16 +61,16 @@ export class HttpService implements CoreService<InternalHttpServiceSetup, HttpSe
private requestHandlerContext?: RequestHandlerContextContainer;
constructor(private readonly coreContext: CoreContext) {
this.logger = coreContext.logger;
this.log = coreContext.logger.get('http');
this.config$ = coreContext.configService
.atPath<HttpConfigType>('server')
.pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env)));
const { logger, configService, env } = coreContext;
this.httpServer = new HttpServer(coreContext.logger, 'Kibana');
this.httpsRedirectServer = new HttpsRedirectServer(
coreContext.logger.get('http', 'redirect', 'server')
);
this.logger = logger;
this.log = logger.get('http');
this.config$ = combineLatest(
configService.atPath<HttpConfigType>(httpConfig.path),
configService.atPath<CspConfigType>(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) {
@ -79,7 +80,7 @@ export class HttpService implements CoreService<InternalHttpServiceSetup, HttpSe
// If the server is already running we can't make any config changes
// to it, so we warn and don't allow the config to pass through.
this.log.warn(
'Received new HTTP config after server was started. ' + 'Config will **not** be applied.'
'Received new HTTP config after server was started. Config will **not** be applied.'
);
}
});

View file

@ -122,22 +122,23 @@ describe('getServerOptions', () => {
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())
);

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { IContextProvider, IContextContainer } from '../context';
import { ICspConfig } 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: ICspConfig;
/**
* Flag showing whether a server was configured to use TLS connection.
*/

View file

@ -68,6 +68,7 @@ export {
HandlerParameters,
} from './context';
export { CoreId } from './core_context';
export { CspConfig, ICspConfig } from './csp';
export {
ClusterClient,
IClusterClient,

View file

@ -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 },

View file

@ -25,8 +25,9 @@ import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { SavedObjectsLegacyUiExports } from '../types';
import { Config, ConfigDeprecationProvider } from '../config';
import { CoreContext } from '../core_context';
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';
@ -112,13 +113,16 @@ export class LegacyService implements CoreService {
private settings: Record<string, any> | undefined;
constructor(private readonly coreContext: CoreContext) {
this.log = coreContext.logger.get('legacy-service');
this.devConfig$ = coreContext.configService
.atPath<DevConfigType>('dev')
const { logger, configService, env } = coreContext;
this.log = logger.get('legacy-service');
this.devConfig$ = configService
.atPath<DevConfigType>(devConfig.path)
.pipe(map(rawConfig => new DevConfig(rawConfig)));
this.httpConfig$ = coreContext.configService
.atPath<HttpConfigType>('server')
.pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env)));
this.httpConfig$ = combineLatest(
configService.atPath<HttpConfigType>(httpConfig.path),
configService.atPath<CspConfigType>(cspConfig.path)
).pipe(map(([http, csp]) => new HttpConfig(http, csp, env)));
}
public async discoverPlugins(): Promise<LegacyServiceDiscoverPlugins> {
@ -240,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;
@ -284,6 +288,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: {
@ -339,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) {

View file

@ -19,6 +19,7 @@
import { of } from 'rxjs';
import { duration } from 'moment';
import { PluginInitializerContext, CoreSetup, CoreStart } from '.';
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';
@ -92,6 +93,7 @@ function createCoreSetupMock() {
registerOnPostAuth: httpService.registerOnPostAuth,
registerOnPreResponse: httpService.registerOnPreResponse,
basePath: httpService.basePath,
csp: CspConfig.DEFAULT,
isTlsEnabled: httpService.isTlsEnabled,
createRouter: jest.fn(),
registerRouteHandlerContext: jest.fn(),

View file

@ -161,6 +161,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
registerOnPostAuth: deps.http.registerOnPostAuth,
registerOnPreResponse: deps.http.registerOnPreResponse,
basePath: deps.http.basePath,
csp: deps.http.csp,
isTlsEnabled: deps.http.isTlsEnabled,
},
savedObjects: {

View file

@ -574,6 +574,22 @@ export interface CoreStart {
savedObjects: SavedObjectsServiceStart;
}
// @public
export class CspConfig implements ICspConfig {
// @internal
constructor(rawCspConfig?: Partial<Omit<ICspConfig, 'header'>>);
// (undocumented)
static readonly DEFAULT: CspConfig;
// (undocumented)
readonly header: string;
// (undocumented)
readonly rules: string[];
// (undocumented)
readonly strict: boolean;
// (undocumented)
readonly warnLegacyBrowsers: boolean;
}
// @public
export interface CustomHttpResponseOptions<T extends HttpResponsePayload | ResponseError> {
body?: T;
@ -713,6 +729,7 @@ export interface HttpServiceSetup {
basePath: IBasePath;
createCookieSessionStorageFactory: <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>>;
createRouter: () => IRouter;
csp: ICspConfig;
isTlsEnabled: boolean;
registerAuth: (handler: AuthenticationHandler) => void;
registerOnPostAuth: (handler: OnPostAuthHandler) => void;
@ -741,6 +758,14 @@ export interface IContextContainer<THandler extends HandlerFunction<any>> {
// @public
export type IContextProvider<THandler extends HandlerFunction<any>, TContextName extends keyof HandlerContextType<THandler>> = (context: Partial<HandlerContextType<THandler>>, ...rest: HandlerParameters<THandler>) => Promise<HandlerContextType<THandler>[TContextName]> | HandlerContextType<THandler>[TContextName];
// @public
export interface ICspConfig {
readonly header: string;
readonly rules: string[];
readonly strict: boolean;
readonly warnLegacyBrowsers: boolean;
}
// @public
export interface IKibanaResponse<T extends HttpResponsePayload | ResponseError = any> {
// (undocumented)

View file

@ -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<unknown>]> = [
[pathConfig.path, pathConfig.schema],
[cspConfig.path, cspConfig.schema],
[elasticsearchConfig.path, elasticsearchConfig.schema],
[loggingConfig.path, loggingConfig.schema],
[httpConfig.path, httpConfig.schema],

View file

@ -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 { ICspConfig } from './csp';

View file

@ -17,79 +17,74 @@
* under the License.
*/
import sinon from 'sinon';
import { Server } from 'hapi';
import { DEFAULT_CSP_RULES } from '../../../../../server/csp';
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<typeof createMockKbnServer>;
expect((await collector.fetch()).strict).toEqual(true);
function updateCsp(config: Partial<ICspConfig>) {
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 strict mode is enabled', async () => {
const collector = createCspCollector(kbnServer as any);
expect((await collector.fetch()).strict).toEqual(true);
updateCsp({ strict: false });
expect((await collector.fetch()).strict).toEqual(false);
});
test('fetches whether the legacy browser warning is enabled', async () => {
const collector = createCspCollector(kbnServer as any);
expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true);
updateCsp({ warnLegacyBrowsers: false });
expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false);
});
test('fetches whether the csp rules have been changed or not', async () => {
const collector = createCspCollector(kbnServer as any);
expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false);
updateCsp({ rules: ['not', 'default'] });
expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true);
});
test('does not include raw csp rules under any property names', async () => {
const collector = createCspCollector(kbnServer as any);
// 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 legacy browser warning is enabled', async () => {
const { collector, mockConfig } = setupCollector();
expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true);
mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(false);
expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false);
});
test('fetches whether the csp rules have been changed or not', async () => {
const { collector, mockConfig } = setupCollector();
expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false);
mockConfig.get.withArgs('csp.rules').returns(['not', 'default']);
expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true);
});
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('does not arbitrarily fetch other csp configurations (e.g. whitelist only)', async () => {
const { collector, mockConfig } = setupCollector();
mockConfig.get.withArgs('csp.foo').returns('bar');
expect(await collector.fetch()).not.toHaveProperty('foo');
});
function setupCollector() {
const mockConfig = { get: sinon.stub() };
mockConfig.get.withArgs('csp.rules').returns(DEFAULT_CSP_RULES);
mockConfig.get.withArgs('csp.strict').returns(true);
mockConfig.get.withArgs('csp.warnLegacyBrowsers').returns(true);
const mockKbnServer = getMockKbnServer(mockConfig);
return { mockConfig, collector: createCspCollector(mockKbnServer as Server) };
}

View file

@ -18,7 +18,7 @@
*/
import { Server } from 'hapi';
import { createCSPRuleString, DEFAULT_CSP_RULES } from '../../../../../server/csp';
import { CspConfig } from '../../../../../../core/server';
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
export function createCspCollector(server: Server) {
@ -26,18 +26,15 @@ export function createCspCollector(server: Server) {
type: 'csp',
isReady: () => true,
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 = createCSPRuleString([...DEFAULT_CSP_RULES]);
const actualRulesString = createCSPRuleString(config.get('csp.rules'));
const { strict, warnLegacyBrowsers, header } = server.newPlatform.setup.core.http.csp;
return {
strict: config.get('csp.strict'),
warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'),
rulesChangedFromDefault: defaultRulesString !== actualRulesString,
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 !== CspConfig.DEFAULT.header,
};
},
};

View file

@ -22,11 +22,6 @@ import os from 'os';
import { join } from 'path';
// 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({
@ -52,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_RULES),
strict: Joi.boolean().default(DEFAULT_CSP_STRICT),
warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_WARN_LEGACY_BROWSERS),
}).default(),
csp: HANDLED_IN_NEW_PLATFORM,
cpu: Joi.object({
cgroup: Joi.object({

View file

@ -1,61 +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 {
createCSPRuleString,
DEFAULT_CSP_RULES,
DEFAULT_CSP_STRICT,
DEFAULT_CSP_WARN_LEGACY_BROWSERS,
} 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 otherwords, 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_RULES).toMatchInlineSnapshot(`
Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
]
`);
});
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:']);
expect(csp).toMatchInlineSnapshot(`"string-src 'self'; worker-src blob:; img-src data: blob:"`);
});

View file

@ -28,7 +28,6 @@ import { AppBootstrap } from './bootstrap';
import { mergeVariables } from './lib';
// 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) {
@ -246,9 +245,10 @@ export function uiRenderMixin(kbnServer, server, config) {
return { id, plugin, config: {} };
}
}));
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),
@ -266,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,
@ -283,8 +283,7 @@ export function uiRenderMixin(kbnServer, server, config) {
},
});
const csp = createCSPRuleString(config.get('csp.rules'));
response.header('content-security-policy', csp);
response.header('content-security-policy', header);
return response;
}

View file

@ -12,7 +12,6 @@ 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';
export const security = (kibana) => new kibana.Plugin({
id: 'security',
@ -126,7 +125,6 @@ export const security = (kibana) => new kibana.Plugin({
isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind(
server.plugins.kibana.systemApi
),
cspRules: createCSPRuleString(config.get('csp.rules')),
});
// Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator`

View file

@ -43,7 +43,6 @@ export type FeaturesService = Pick<FeaturesSetupContract, 'getFeatures'>;
*/
export interface LegacyAPI {
isSystemAPIRequest: (request: KibanaRequest) => boolean;
cspRules: string;
savedObjects: SavedObjectsLegacyService<KibanaRequest | LegacyRequest>;
auditLogger: {
log: (eventType: string, message: string, data?: Record<string, unknown>) => void;
@ -164,7 +163,7 @@ export class Plugin {
config,
authc,
authz,
getLegacyAPI: this.getLegacyAPI,
csp: core.http.csp,
});
const adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise();

View file

@ -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,
});
});

View file

@ -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,
});
});

View file

@ -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]) =>

View file

@ -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,
};

View file

@ -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({
<script src="${serverBasePath}/internal/security/oidc/implicit.js"></script>
`,
'text/html',
getLegacyAPI().cspRules
csp.header
)
);
}
@ -82,7 +76,7 @@ export function defineOIDCRoutes({
);
`,
'text/javascript',
getLegacyAPI().cspRules
csp.header
)
);
}

View file

@ -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,
});
});

View file

@ -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({
<script src="${basePath.serverBasePath}/api/security/saml/capture-url-fragment.js"></script>
`,
'text/html',
getLegacyAPI().cspRules
csp.header
)
);
}
@ -57,7 +51,7 @@ export function defineSAMLRoutes({
);
`,
'text/javascript',
getLegacyAPI().cspRules
csp.header
)
);
}

View file

@ -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(),
}),
};

View file

@ -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<LegacyAPI, 'cspRules'>;
}
export function defineRoutes(params: RouteDefinitionParams) {

View file

@ -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];