Skip to content

Commit

Permalink
[csp] allow blob styles when running from source (#54991)
Browse files Browse the repository at this point in the history
* [csp] allow blob styles when running from source

* update kbn/pm dist

* add kibanaServer service to saml_api_integration suite

* use common naming

* attempt to use env.packageInfo instead of IS_KIBANA_DISTRIBUTABLE const

* remove mock, clone rules before modifying

* pass env where necessary

* update core api docs

* make env optional in HttpConfig

* add tests for CspConfig changes, base header on this.rules

* fix test snapshot

* make env optional in HttpConfig too

* remove CspConfig.DEFAULT and make env a required constructor arg

* update csp_usage_collector tests

* update core api docs

* fix test name

* rename headerChangedFromDefault back to rulesChangedFromDefault

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
Spencer and elasticmachine authored Jan 22, 2020
1 parent 003d6e9 commit 375f0b4
Show file tree
Hide file tree
Showing 25 changed files with 211 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CoreSetup](./kibana-plugin-server.coresetup.md) &gt; [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md)

## CoreSetup.getStartServices() method

Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`<!-- -->. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle.

<b>Signature:</b>

```typescript
getStartServices(): Promise<[CoreStart, TPluginsStart]>;
```
<b>Returns:</b>

`Promise<[CoreStart, TPluginsStart]>`

<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CoreSetup](./kibana-plugin-server.coresetup.md) &gt; [getStartServices](./kibana-plugin-server.coresetup.getstartservices.md)

## CoreSetup.getStartServices() method

Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`<!-- -->. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle.

<b>Signature:</b>

```typescript
getStartServices(): Promise<[CoreStart, TPluginsStart]>;
```
<b>Returns:</b>

`Promise<[CoreStart, TPluginsStart]>`

64 changes: 32 additions & 32 deletions docs/development/core/server/kibana-plugin-server.coresetup.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CoreSetup](./kibana-plugin-server.coresetup.md)

## CoreSetup interface

Context passed to the plugins `setup` method.

<b>Signature:</b>

```typescript
export interface CoreSetup<TPluginsStart extends object = object>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | <code>CapabilitiesSetup</code> | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
| [context](./kibana-plugin-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-server.contextsetup.md) |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
| [uuid](./kibana-plugin-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) |

## Methods

| Method | Description |
| --- | --- |
| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed <code>start</code>. This should only be used inside handlers registered during <code>setup</code> that will only be executed after <code>start</code> lifecycle. |

<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CoreSetup](./kibana-plugin-server.coresetup.md)

## CoreSetup interface

Context passed to the plugins `setup` method.

<b>Signature:</b>

```typescript
export interface CoreSetup<TPluginsStart extends object = object>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [capabilities](./kibana-plugin-server.coresetup.capabilities.md) | <code>CapabilitiesSetup</code> | [CapabilitiesSetup](./kibana-plugin-server.capabilitiessetup.md) |
| [context](./kibana-plugin-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-server.contextsetup.md) |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
| [uuid](./kibana-plugin-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) |

## Methods

| Method | Description |
| --- | --- |
| [getStartServices()](./kibana-plugin-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed <code>start</code>. This should only be used inside handlers registered during <code>setup</code> that will only be executed after <code>start</code> lifecycle. |

Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ The constructor for this class is marked as internal. Third-party code should no
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [DEFAULT](./kibana-plugin-server.cspconfig.default.md) | <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> | |
| [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md) | | <code>boolean</code> | |
| [strict](./kibana-plugin-server.cspconfig.strict.md) | | <code>boolean</code> | |
| [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | <code>boolean</code> | |
Original file line number Diff line number Diff line change
@@ -1,11 +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)
[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CspConfig](./kibana-plugin-server.cspconfig.md) &gt; [rulesChangedFromDefault](./kibana-plugin-server.cspconfig.ruleschangedfromdefault.md)

## CspConfig.DEFAULT property
## CspConfig.rulesChangedFromDefault property

<b>Signature:</b>

```typescript
static readonly DEFAULT: CspConfig;
readonly rulesChangedFromDefault: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ICspConfig
| --- | --- | --- |
| [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. |
| [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md) | <code>boolean</code> | Flag indicating that the configuraion changes the csp rules from the defaults |
| [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. |

Original file line number Diff line number Diff line change
@@ -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; [rulesChangedFromDefault](./kibana-plugin-server.icspconfig.ruleschangedfromdefault.md)

## ICspConfig.rulesChangedFromDefault property

Flag indicating that the configuraion changes the csp rules from the defaults

<b>Signature:</b>

```typescript
readonly rulesChangedFromDefault: boolean;
```
6 changes: 6 additions & 0 deletions packages/kbn-dev-utils/src/kbn_client/kbn_client_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface Status {
interface ApiResponseStatus {
name: string;
uuid: string;
running_from_source?: true;
version: {
number: string;
build_hash: string;
Expand All @@ -58,6 +59,11 @@ export class KbnClientStatus {
});
}

public async isDistributable() {
const status = await this.get();
return !status.running_from_source;
}

/**
* Get the overall/merged state
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/kbn-pm/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43639,6 +43639,10 @@ class KbnClientStatus {
path: 'api/status',
});
}
async isDistributable() {
const status = await this.get();
return !status.running_from_source;
}
/**
* Get the overall/merged state
*/
Expand Down
29 changes: 29 additions & 0 deletions src/core/server/config/env.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Env } from './env';

export function createMockEnv(options: { dist?: boolean } = {}): Env {
return {
// required by CspConfig
packageInfo: {
dist: options.dist ?? true,
},
} as any;
}
47 changes: 29 additions & 18 deletions src/core/server/csp/csp_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { CspConfig } from '.';
import { createMockEnv } from '../config/env.mock';

// CSP rules aren't strictly additive, so any change can potentially expand or
// restrict the policy in a way we consider a breaking change. For that reason,
Expand All @@ -33,53 +34,44 @@ import { CspConfig } from '.';
// the nature of a change in defaults during a PR review.

describe('CspConfig', () => {
test('DEFAULT', () => {
expect(CspConfig.DEFAULT).toMatchInlineSnapshot(`
CspConfig {
"header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
"rules": Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
],
"strict": true,
"warnLegacyBrowsers": true,
}
`);
});

test('defaults from config', () => {
expect(new CspConfig()).toMatchInlineSnapshot(`
const cspConfig = new CspConfig(createMockEnv());

expect(cspConfig).toMatchInlineSnapshot(`
CspConfig {
"header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
"rules": Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
],
"rulesChangedFromDefault": false,
"strict": true,
"warnLegacyBrowsers": true,
}
`);
});

test('creates from partial config', () => {
expect(new CspConfig({ strict: false, warnLegacyBrowsers: false })).toMatchInlineSnapshot(`
const cspConfig = new CspConfig(createMockEnv(), { strict: false, warnLegacyBrowsers: false });

expect(cspConfig).toMatchInlineSnapshot(`
CspConfig {
"header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
"rules": Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
],
"rulesChangedFromDefault": false,
"strict": false,
"warnLegacyBrowsers": false,
}
`);
});

test('computes header from rules', () => {
const cspConfig = new CspConfig({ rules: ['alpha', 'beta', 'gamma'] });
const cspConfig = new CspConfig(createMockEnv(), { rules: ['alpha', 'beta', 'gamma'] });

expect(cspConfig).toMatchInlineSnapshot(`
CspConfig {
Expand All @@ -89,6 +81,25 @@ describe('CspConfig', () => {
"beta",
"gamma",
],
"rulesChangedFromDefault": true,
"strict": true,
"warnLegacyBrowsers": true,
}
`);
});

test(`includes blob: style-src if env indicates we're running from source`, () => {
const cspConfig = new CspConfig(createMockEnv({ dist: false }));

expect(cspConfig).toMatchInlineSnapshot(`
CspConfig {
"header": "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src blob: 'unsafe-inline' 'self'",
"rules": Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src blob: 'unsafe-inline' 'self'",
],
"rulesChangedFromDefault": false,
"strict": true,
"warnLegacyBrowsers": true,
}
Expand Down
31 changes: 26 additions & 5 deletions src/core/server/csp/csp_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { config } from './config';
import { Env } from '../config';

const DEFAULT_CONFIG = Object.freeze(config.schema.validate({}));

Expand Down Expand Up @@ -48,30 +49,50 @@ export interface ICspConfig {
* in a `Content-Security-Policy` header.
*/
readonly header: string;

/**
* Flag indicating that the configuraion changes the csp
* rules from the defaults
*/
readonly rulesChangedFromDefault: boolean;
}

/**
* 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;
public readonly rulesChangedFromDefault: boolean;

/**
* Returns the default CSP configuration when passed with no config
* @internal
*/
constructor(rawCspConfig: Partial<Omit<ICspConfig, 'header'>> = {}) {
constructor(env: Env, rawCspConfig?: Partial<Omit<ICspConfig, 'header'>>) {
const source = { ...DEFAULT_CONFIG, ...rawCspConfig };

this.rules = source.rules;
this.rules = source.rules.map(rule => {
// if we receive an env, and it indicates that this isn't a distributable, add `blob:` to the style csp rules
if (env && !env.packageInfo.dist && rule.startsWith('style-src ')) {
return rule.replace(/^style-src /, 'style-src blob: ');
}

return rule;
});
this.strict = source.strict;
this.warnLegacyBrowsers = source.warnLegacyBrowsers;
this.header = source.rules.join('; ');
this.header = this.rules.join('; ');

// only check to see if the csp values are customized when `rawCspConfig` was received.
if (!rawCspConfig) {
this.rulesChangedFromDefault = false;
} else {
const defaultCsp = new CspConfig(env);
this.rulesChangedFromDefault = defaultCsp.header !== this.header;
}
}
}
5 changes: 3 additions & 2 deletions src/core/server/http/http_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { hostname } from 'os';

import { CspConfigType, CspConfig, ICspConfig } from '../csp';
import { SslConfig, sslSchema } from './ssl_config';
import { Env } from '../config';

const validBasePathRegex = /(^$|^\/.*[^\/]$)/;
const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
Expand Down Expand Up @@ -148,7 +149,7 @@ export class HttpConfig {
/**
* @internal
*/
constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType) {
constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) {
this.autoListen = rawHttpConfig.autoListen;
this.host = rawHttpConfig.host;
this.port = rawHttpConfig.port;
Expand All @@ -162,7 +163,7 @@ export class HttpConfig {
this.rewriteBasePath = rawHttpConfig.rewriteBasePath;
this.ssl = new SslConfig(rawHttpConfig.ssl || {});
this.compression = rawHttpConfig.compression;
this.csp = new CspConfig(rawCspConfig);
this.csp = new CspConfig(env, rawCspConfig);
this.xsrf = rawHttpConfig.xsrf;
}
}
Loading

0 comments on commit 375f0b4

Please sign in to comment.