mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
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:
parent
1ea9d791bc
commit
614bde927e
52 changed files with 610 additions and 255 deletions
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
static readonly DEFAULT: CspConfig;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly header: string;
|
||||
```
|
|
@ -0,0 +1,28 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [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.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly rules: string[];
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly strict: boolean;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly warnLegacyBrowsers: boolean;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
csp: ICspConfig;
|
||||
```
|
|
@ -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><T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>></code> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) |
|
||||
| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | <code>() => 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) => void</code> | To define custom authentication and/or authorization mechanism for incoming requests. |
|
||||
| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | <code>(handler: OnPostAuthHandler) => void</code> | To define custom logic to perform for incoming requests. |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly header: string;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [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. |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly rules: string[];
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly strict: boolean;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly warnLegacyBrowsers: boolean;
|
||||
```
|
|
@ -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) | |
|
||||
|
|
42
src/core/server/csp/config.ts
Normal file
42
src/core/server/csp/config.ts
Normal 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 }),
|
||||
}),
|
||||
};
|
97
src/core/server/csp/csp_config.test.ts
Normal file
97
src/core/server/csp/csp_config.test.ts
Normal 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,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
77
src/core/server/csp/csp_config.ts
Normal file
77
src/core/server/csp/csp_config.ts
Normal 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('; ');
|
||||
}
|
||||
}
|
|
@ -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 };
|
|
@ -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())
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -68,6 +68,7 @@ export {
|
|||
HandlerParameters,
|
||||
} from './context';
|
||||
export { CoreId } from './core_context';
|
||||
export { CspConfig, ICspConfig } from './csp';
|
||||
export {
|
||||
ClusterClient,
|
||||
IClusterClient,
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) };
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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:"`);
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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]) =>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue