Skip to content

Commit

Permalink
Derive CSP options from config
Browse files Browse the repository at this point in the history
  • Loading branch information
eliperelman committed Dec 12, 2019
1 parent 84df08e commit fe8af81
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 103 deletions.
4 changes: 1 addition & 3 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,19 @@
/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
/src/legacy/server/saved_objects/ @elastic/kibana-platform
/src/legacy/server/status/ @elastic/kibana-platform

# Security
/src/core/server/csp/ @elastic/kibana-security
/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform
/x-pack/legacy/plugins/security/ @elastic/kibana-security
/x-pack/legacy/plugins/spaces/ @elastic/kibana-security
/x-pack/plugins/spaces/ @elastic/kibana-security
/x-pack/legacy/plugins/encrypted_saved_objects/ @elastic/kibana-security
/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security
/src/legacy/server/csp/ @elastic/kibana-security
/x-pack/plugins/security/ @elastic/kibana-security
/x-pack/test/api_integration/apis/security/ @elastic/kibana-security

Expand Down
44 changes: 44 additions & 0 deletions src/core/server/csp/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { TypeOf, schema } from '@kbn/config-schema';

export type CspConfigType = TypeOf<typeof config.schema>;

const rules = [
`script-src 'unsafe-eval' 'self'`,
`worker-src blob: 'self'`,
`style-src 'unsafe-inline' 'self'`,
];

export const config = {
// TODO: Move this to server.csp using config deprecations
// ? https://github.com/elastic/kibana/pull/52251
path: 'csp',
schema: schema.object({
rules: schema.arrayOf(schema.string(), { defaultValue: rules }),
directives: schema.string({
// TODO: Consider making this bidirectional in the future
validate: value => (value ? 'Specify `csp.rules` to compute `csp.directives`' : undefined),
defaultValue: (context: { rules: string[] } = { rules }) => context.rules.join('; '),
}),
strict: schema.boolean({ defaultValue: true }),
warnLegacyBrowsers: schema.boolean({ defaultValue: true }),
}),
};
14 changes: 1 addition & 13 deletions src/core/server/csp/csp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { createCspDirectives, DEFAULT_CSP_OPTIONS } from './csp';
import { DEFAULT_CSP_OPTIONS } from './csp';

// CSP rules aren't strictly additive, so any change can potentially expand or
// restrict the policy in a way we consider a breaking change. For that reason,
Expand Down Expand Up @@ -45,15 +45,3 @@ test('default CSP rules', () => {
}
`);
});

test('createCspDirectives() converts an array of rules into a CSP header string', () => {
const directives = createCspDirectives([
`string-src 'self'`,
'worker-src blob:',
'img-src data: blob:',
]);

expect(directives).toMatchInlineSnapshot(
`"string-src 'self'; worker-src blob:; img-src data: blob:"`
);
});
65 changes: 3 additions & 62 deletions src/core/server/csp/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,70 +17,11 @@
* under the License.
*/

import { TypeOf, schema } from '@kbn/config-schema';

/**
* The CSP options used for Kibana.
* @public
*/
export interface CspOptions {
/**
* The CSP rules in a formatted directives string for use
* in a `Content-Security-Policy` header.
*/
directives: string;

/**
* The CSP rules used for Kibana.
*/
rules: string[];

/**
* Specify whether browsers that do not support CSP should be
* able to use Kibana. Use `true` to block and `false` to allow.
*/
strict: boolean;

/**
* Specify whether users with legacy browsers should be warned
* about their lack of Kibana security compliance.
*/
warnLegacyBrowsers: boolean;
}

export type CspConfigType = TypeOf<typeof config.schema>;

const DEFAULT_RULES = [
`script-src 'unsafe-eval' 'self'`,
`worker-src blob: 'self'`,
`style-src 'unsafe-inline' 'self'`,
];
const defaults = {
directives: createCspDirectives(DEFAULT_RULES),
rules: DEFAULT_RULES,
strict: true,
warnLegacyBrowsers: true,
};
export const config = {
path: 'csp',
schema: schema.object({
rules: schema.arrayOf(schema.string(), { defaultValue: defaults.rules }),
strict: schema.boolean({ defaultValue: defaults.strict }),
warnLegacyBrowsers: schema.boolean({ defaultValue: defaults.warnLegacyBrowsers }),
}),
};
import { config } from './config';
import { CspOptions } from './types';

/**
* The default set of CSP options used for Kibana.
* @public
*/
export const DEFAULT_CSP_OPTIONS: Readonly<CspOptions> = Object.freeze(defaults);

/**
* Converts an array of rules into a formatted directives string for use
* in a `Content-Security-Policy` header.
* @internal
*/
export function createCspDirectives(rules: string[]) {
return rules.join('; ');
}
export const DEFAULT_CSP_OPTIONS: Readonly<CspOptions> = Object.freeze(config.schema.validate({}));
2 changes: 2 additions & 0 deletions src/core/server/csp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@
* under the License.
*/

export * from './config';
export * from './types';
export * from './csp';
47 changes: 47 additions & 0 deletions src/core/server/csp/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
* The CSP options used for Kibana.
* @public
*/
export interface CspOptions {
/**
* The CSP rules in a formatted directives string for use
* in a `Content-Security-Policy` header.
*/
directives: string;

/**
* The CSP rules used for Kibana.
*/
rules: string[];

/**
* Specify whether browsers that do not support CSP should be
* able to use Kibana. Use `true` to block and `false` to allow.
*/
strict: boolean;

/**
* Specify whether users with legacy browsers should be warned
* about their lack of Kibana security compliance.
*/
warnLegacyBrowsers: boolean;
}
2 changes: 1 addition & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export {
HandlerParameters,
} from './context';
export { CoreId } from './core_context';
export { CspOptions, DEFAULT_CSP_OPTIONS, createCspDirectives } from './csp';
export { CspOptions, DEFAULT_CSP_OPTIONS } from './csp';
export {
ClusterClient,
IClusterClient,
Expand Down
3 changes: 0 additions & 3 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,6 @@ export interface CoreStart {
savedObjects: SavedObjectsServiceStart;
}

// @internal
export function createCspDirectives(rules: string[]): string;

// @public
export interface CspOptions {
directives: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import { Server } from 'hapi';
import { createCspDirectives, DEFAULT_CSP_OPTIONS } from '../../../../../../core/server';
import { DEFAULT_CSP_OPTIONS } from '../../../../../../core/server';
import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';

export function createCspCollector(server: Server) {
Expand All @@ -28,16 +28,13 @@ export function createCspCollector(server: Server) {
async fetch() {
const config = server.config();

// It's important that we do not send the value of csp.rules here as it
// can be customized with values that can be identifiable to given
// installs, such as URLs
const defaultRulesString = createCspDirectives([...DEFAULT_CSP_OPTIONS.rules]);
const actualRulesString = createCspDirectives(config.get('csp.rules'));

return {
strict: config.get('csp.strict'),
warnLegacyBrowsers: config.get('csp.warnLegacyBrowsers'),
rulesChangedFromDefault: defaultRulesString !== actualRulesString,
// It's important that we do not send the value of csp.directives here as it
// can be customized with values that can be identifiable to given
// installs, such as URLs
rulesChangedFromDefault: DEFAULT_CSP_OPTIONS.directives !== config.get('csp.directives'),
};
},
};
Expand Down
7 changes: 1 addition & 6 deletions src/legacy/server/config/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import Joi from 'joi';
import os from 'os';
import { join } from 'path';
import { DEFAULT_CSP_OPTIONS } from '../../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getDataPath } from '../../../core/server/path'; // Still used by optimize config schema

Expand Down Expand Up @@ -48,11 +47,7 @@ export default () => Joi.object({
exclusive: Joi.boolean().default(false)
}).default(),

csp: Joi.object({
rules: Joi.array().items(Joi.string()).default(DEFAULT_CSP_OPTIONS.rules),
strict: Joi.boolean().default(DEFAULT_CSP_OPTIONS.strict),
warnLegacyBrowsers: Joi.boolean().default(DEFAULT_CSP_OPTIONS.warnLegacyBrowsers),
}).default(),
csp: HANDLED_IN_NEW_PLATFORM,

cpu: Joi.object({
cgroup: Joi.object({
Expand Down
4 changes: 1 addition & 3 deletions src/legacy/ui/ui_render/ui_render_mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { AppBootstrap } from './bootstrap';
import { mergeVariables } from './lib';
import { createCspDirectives } from '../../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { fromRoot } from '../../../core/server/utils';

Expand Down Expand Up @@ -283,8 +282,7 @@ export function uiRenderMixin(kbnServer, server, config) {
},
});

const csp = createCspDirectives(config.get('csp.rules'));
response.header('content-security-policy', csp);
response.header('content-security-policy', config.get('csp.directives'));

return response;
}
Expand Down
4 changes: 2 additions & 2 deletions x-pack/legacy/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { initLogoutView } from './server/routes/views/logout';
import { initLoggedOutView } from './server/routes/views/logged_out';
import { AuditLogger } from '../../server/lib/audit_logger';
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
import { KibanaRequest, createCspDirectives } from '../../../../src/core/server';
import { KibanaRequest } from '../../../../src/core/server';

export const security = (kibana) => new kibana.Plugin({
id: 'security',
Expand Down Expand Up @@ -126,7 +126,7 @@ export const security = (kibana) => new kibana.Plugin({
isSystemAPIRequest: server.plugins.kibana.systemApi.isSystemApiRequest.bind(
server.plugins.kibana.systemApi
),
cspRules: createCspDirectives(config.get('csp.rules')),
cspRules: config.get('csp.directives'),
});

// Legacy xPack Info endpoint returns whatever we return in a callback for `registerLicenseCheckResultsGenerator`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import Boom from 'boom';
import Joi from 'joi';
import { schema } from '@kbn/config-schema';
import { canRedirectRequest, wrapError, OIDCAuthenticationFlow } from '../../../../../../../plugins/security/server';
import { KibanaRequest, createCspDirectives } from '../../../../../../../../src/core/server';
import { KibanaRequest } from '../../../../../../../../src/core/server';

export function initAuthenticateApi({ authc: { login, logout }, __legacyCompat: { config } }, server) {
function prepareCustomResourceResponse(response, contentType) {
return response
.header('cache-control', 'private, no-cache, no-store')
.header('content-security-policy', createCspDirectives(server.config().get('csp.rules')))
.header('content-security-policy', server.config().get('csp.directives'))
.type(contentType);
}

Expand Down

0 comments on commit fe8af81

Please sign in to comment.