Allow additive csp configuration (#102059) (#103405)

* add additive csp configuration

* add unit tests for new class

* fix types

* adapt test utils

* fix tests

* more unit tests on config

* generated doc

* review comments

* update ascii doc

* update ascii doc links

* automatically add single quotes for keywords

* add missing csp directives

* add more tests

* add additional settings to asciidoc

* add null-check

* revert test config props

* fix usage collection usage

* some review comments

* last review comments

* add kibana-docker variables

* try to fix doc reference

* try to fix doc reference again

* fix tests
# Conflicts:
#	src/core/server/csp/config.ts
#	src/core/server/csp/csp_config.test.ts
This commit is contained in:
Pierre Gayvallet 2021-06-25 22:54:07 +02:00 committed by GitHub
parent d2721e5134
commit b02ddafaf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1245 additions and 52 deletions

View file

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

View file

@ -20,6 +20,7 @@ The constructor for this class is marked as internal. Third-party code should no
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) | | <code></code> | |
| [DEFAULT](./kibana-plugin-core-server.cspconfig.default.md) | <code>static</code> | <code>CspConfig</code> | |
| [disableEmbedding](./kibana-plugin-core-server.cspconfig.disableembedding.md) | | <code>boolean</code> | |
| [header](./kibana-plugin-core-server.cspconfig.header.md) | | <code>string</code> | |

View file

@ -36,11 +36,57 @@ Set to `false` to disable Console. *Default: `true`*
<<ops-cGroupOverrides-cpuAcctPath, `ops.cGroupOverrides.cpuAcctPath`>>.
| `csp.rules:`
| A https://w3c.github.io/webappsec-csp/[content-security-policy] template
| deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."]
A https://w3c.github.io/webappsec-csp/[Content Security Policy] template
that disables certain unnecessary and potentially insecure capabilities in
the browser. It is strongly recommended that you keep the default CSP rules
that ship with {kib}.
| `csp.script_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src[Content Security Policy `script-src` directive].
| `csp.worker_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src[Content Security Policy `worker-src` directive].
| `csp.style_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src[Content Security Policy `style-src` directive].
| `csp.connect_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src[Content Security Policy `connect-src` directive].
| `csp.default_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src[Content Security Policy `default-src` directive].
| `csp.font_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src[Content Security Policy `font-src` directive].
| `csp.frame_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src[Content Security Policy `frame-src` directive].
| `csp.img_src:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src[Content Security Policy `img-src` directive].
| `csp.frame_ancestors:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors[Content Security Policy `frame-ancestors` directive].
|===
[NOTE]
============
The `frame-ancestors` directive can also be configured by using
<<server-securityResponseHeaders-disableEmbedding, `server.securityResponseHeaders.disableEmbedding`>>. In that case, that takes precedence and any values in `csp.frame_ancestors`
are ignored.
============
[cols="2*<"]
|===
| `csp.report_uri:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri[Content Security Policy `report-uri` directive].
| `csp.report_to:`
| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to[Content Security Policy `report-to` directive].
|[[csp-strict]] `csp.strict:`
| Blocks {kib} access to any browser that
does not enforce even rudimentary CSP rules. In practice, this disables
@ -538,8 +584,7 @@ a|`server.securityResponseHeaders:`
is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or `null`.
To disable, set to `null`. *Default:* `null`
[[server-securityResponseHeaders-disableEmbedding]]
a|`server.securityResponseHeaders:`
|[[server-securityResponseHeaders-disableEmbedding]]`server.securityResponseHeaders:`
`disableEmbedding:`
| Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy[`Content-Security-Policy`] and
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options[`X-Frame-Options`] headers are configured to disable embedding

View file

@ -9,11 +9,469 @@
import { config } from './config';
describe('config.validate()', () => {
test(`does not allow "disableEmbedding" to be set to true`, () => {
it(`does not allow "disableEmbedding" to be set to true`, () => {
// This is intentionally not editable in the raw CSP config.
// Users should set `server.securityResponseHeaders.disableEmbedding` to control this config property.
expect(() => config.schema.validate({ disableEmbedding: true })).toThrowError(
'[disableEmbedding]: expected value to equal [false]'
);
});
describe(`"script_src"`, () => {
it(`throws if containing 'unsafe-inline' when 'strict' is true`, () => {
expect(() =>
config.schema.validate({
strict: true,
warnLegacyBrowsers: false,
script_src: [`'self'`, `unsafe-inline`],
})
).toThrowErrorMatchingInlineSnapshot(
`"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"`
);
expect(() =>
config.schema.validate({
strict: true,
warnLegacyBrowsers: false,
script_src: [`'self'`, `'unsafe-inline'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"`
);
});
it(`throws if containing 'unsafe-inline' when 'warnLegacyBrowsers' is true`, () => {
expect(() =>
config.schema.validate({
strict: false,
warnLegacyBrowsers: true,
script_src: [`'self'`, `unsafe-inline`],
})
).toThrowErrorMatchingInlineSnapshot(
`"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"`
);
expect(() =>
config.schema.validate({
strict: false,
warnLegacyBrowsers: true,
script_src: [`'self'`, `'unsafe-inline'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"`
);
});
it(`does not throw if containing 'unsafe-inline' when 'strict' and 'warnLegacyBrowsers' are false`, () => {
expect(() =>
config.schema.validate({
strict: false,
warnLegacyBrowsers: false,
script_src: [`'self'`, `unsafe-inline`],
})
).not.toThrow();
expect(() =>
config.schema.validate({
strict: false,
warnLegacyBrowsers: false,
script_src: [`'self'`, `'unsafe-inline'`],
})
).not.toThrow();
});
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
script_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
script_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[script_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
script_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
script_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"worker_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
worker_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
worker_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[worker_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
worker_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
worker_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"style_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
style_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
style_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[style_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
style_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
style_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"connect_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
connect_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
connect_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[connect_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
connect_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
connect_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"default_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
default_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
default_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[default_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
default_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
default_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"font_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
font_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
font_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[font_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
font_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
font_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"frame_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
frame_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
frame_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
frame_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
frame_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"img_src"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
img_src: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
img_src: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[img_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
img_src: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
img_src: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
describe(`"frame_ancestors"`, () => {
it(`throws if 'rules' is also specified`, () => {
expect(() =>
config.schema.validate({
rules: [
`script-src 'unsafe-eval' 'self'`,
`worker-src 'unsafe-eval' 'self'`,
`style-src 'unsafe-eval' 'self'`,
],
frame_ancestors: [`'self'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""`
);
});
it('throws if using an `nonce-*` value', () => {
expect(() =>
config.schema.validate({
frame_ancestors: [`hello`, `nonce-foo`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_ancestors]: using \\"nonce-*\\" is considered insecure and is not allowed"`
);
});
it("throws if using `none` or `'none'`", () => {
expect(() =>
config.schema.validate({
frame_ancestors: [`hello`, `none`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
expect(() =>
config.schema.validate({
frame_ancestors: [`hello`, `'none'`],
})
).toThrowErrorMatchingInlineSnapshot(
`"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
);
});
});
});

View file

@ -7,28 +7,150 @@
*/
import { TypeOf, schema } from '@kbn/config-schema';
import { ServiceConfigDescriptor } from '../internal_types';
/**
* @internal
*/
export type CspConfigType = TypeOf<typeof config.schema>;
interface DirectiveValidationOptions {
allowNone: boolean;
allowNonce: boolean;
}
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'`,
],
const getDirectiveValidator = (options: DirectiveValidationOptions) => {
const validateValue = getDirectiveValueValidator(options);
return (values: string[]) => {
for (const value of values) {
const error = validateValue(value);
if (error) {
return error;
}
}
};
};
const getDirectiveValueValidator = ({ allowNone, allowNonce }: DirectiveValidationOptions) => {
return (value: string) => {
if (!allowNonce && value.startsWith('nonce-')) {
return `using "nonce-*" is considered insecure and is not allowed`;
}
if (!allowNone && (value === `none` || value === `'none'`)) {
return `using "none" would conflict with Kibana's default csp configuration and is not allowed`;
}
};
};
const configSchema = schema.object(
{
rules: schema.maybe(schema.arrayOf(schema.string())),
script_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
worker_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
style_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
connect_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
default_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
font_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
frame_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
img_src: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
frame_ancestors: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
}),
report_uri: schema.arrayOf(schema.string(), {
defaultValue: [],
validate: getDirectiveValidator({ allowNone: true, allowNonce: false }),
}),
report_to: schema.arrayOf(schema.string(), {
defaultValue: [],
}),
strict: schema.boolean({ defaultValue: false }),
warnLegacyBrowsers: schema.boolean({ defaultValue: true }),
disableEmbedding: schema.oneOf([schema.literal<boolean>(false)], { defaultValue: false }),
}),
},
{
validate: (cspConfig) => {
if (cspConfig.rules && hasDirectiveSpecified(cspConfig)) {
return `"csp.rules" cannot be used when specifying per-directive additions such as "script_src", "worker_src" or "style_src"`;
}
const hasUnsafeInlineScriptSrc =
cspConfig.script_src.includes(`unsafe-inline`) ||
cspConfig.script_src.includes(`'unsafe-inline'`);
if (cspConfig.strict && hasUnsafeInlineScriptSrc) {
return 'cannot use `unsafe-inline` for `script_src` when `csp.strict` is true';
}
if (cspConfig.warnLegacyBrowsers && hasUnsafeInlineScriptSrc) {
return 'cannot use `unsafe-inline` for `script_src` when `csp.warnLegacyBrowsers` is true';
}
},
}
);
const hasDirectiveSpecified = (rawConfig: CspConfigType): boolean => {
return Boolean(
rawConfig.script_src.length ||
rawConfig.worker_src.length ||
rawConfig.style_src.length ||
rawConfig.connect_src.length ||
rawConfig.default_src.length ||
rawConfig.font_src.length ||
rawConfig.frame_src.length ||
rawConfig.img_src.length ||
rawConfig.frame_ancestors.length ||
rawConfig.report_uri.length ||
rawConfig.report_to.length
);
};
export const FRAME_ANCESTORS_RULE = `frame-ancestors 'self'`; // only used by CspConfig when embedding is disabled
/**
* @internal
*/
export type CspConfigType = TypeOf<typeof configSchema>;
export const config: ServiceConfigDescriptor<CspConfigType> = {
// TODO: Move this to server.csp using config deprecations
// ? https://github.com/elastic/kibana/pull/52251
path: 'csp',
schema: configSchema,
deprecations: () => [
(rawConfig, fromPath, addDeprecation) => {
const cspConfig = rawConfig[fromPath];
if (cspConfig?.rules) {
addDeprecation({
message:
'`csp.rules` is deprecated in favor of directive specific configuration. Please use `csp.connect_src`, ' +
'`csp.default_src`, `csp.font_src`, `csp.frame_ancestors`, `csp.frame_src`, `csp.img_src`, ' +
'`csp.report_uri`, `csp.report_to`, `csp.script_src`, `csp.style_src`, and `csp.worker_src` instead.',
correctiveActions: {
manualSteps: [
`Remove "csp.rules" from the Kibana config file."`,
`Add directive specific configurations to the config file using "csp.connect_src", "csp.default_src", "csp.font_src", ` +
`"csp.frame_ancestors", "csp.frame_src", "csp.img_src", "csp.report_uri", "csp.report_to", "csp.script_src", ` +
`"csp.style_src", and "csp.worker_src".`,
],
},
});
}
},
],
};

View file

@ -7,7 +7,7 @@
*/
import { CspConfig } from './csp_config';
import { FRAME_ANCESTORS_RULE } from './config';
import { config as cspConfig, CspConfigType } from './config';
// 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,
@ -23,6 +23,12 @@ import { FRAME_ANCESTORS_RULE } from './config';
// the nature of a change in defaults during a PR review.
describe('CspConfig', () => {
let defaultConfig: CspConfigType;
beforeEach(() => {
defaultConfig = cspConfig.schema.validate({});
});
test('DEFAULT', () => {
expect(CspConfig.DEFAULT).toMatchInlineSnapshot(`
CspConfig {
@ -40,50 +46,129 @@ describe('CspConfig', () => {
});
test('defaults from config', () => {
expect(new CspConfig()).toEqual(CspConfig.DEFAULT);
expect(new CspConfig(defaultConfig)).toEqual(CspConfig.DEFAULT);
});
describe('partial config', () => {
test('allows "rules" to be set and changes header', () => {
const rules = ['foo', 'bar'];
const config = new CspConfig({ rules });
const rules = [`foo 'self'`, `bar 'self'`];
const config = new CspConfig({ ...defaultConfig, rules });
expect(config.rules).toEqual(rules);
expect(config.header).toMatchInlineSnapshot(`"foo; bar"`);
expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`);
});
test('allows "strict" to be set', () => {
const config = new CspConfig({ strict: true });
const config = new CspConfig({ ...defaultConfig, strict: true });
expect(config.strict).toEqual(true);
expect(config.strict).not.toEqual(CspConfig.DEFAULT.strict);
});
test('allows "warnLegacyBrowsers" to be set', () => {
const warnLegacyBrowsers = false;
const config = new CspConfig({ warnLegacyBrowsers });
const config = new CspConfig({ ...defaultConfig, warnLegacyBrowsers });
expect(config.warnLegacyBrowsers).toEqual(warnLegacyBrowsers);
expect(config.warnLegacyBrowsers).not.toEqual(CspConfig.DEFAULT.warnLegacyBrowsers);
});
test('allows "worker_src" to be set and changes header', () => {
const config = new CspConfig({
...defaultConfig,
rules: [],
worker_src: ['foo', 'bar'],
});
expect(config.rules).toEqual([`worker-src foo bar`]);
expect(config.header).toEqual(`worker-src foo bar`);
});
test('allows "style_src" to be set and changes header', () => {
const config = new CspConfig({
...defaultConfig,
rules: [],
style_src: ['foo', 'bar'],
});
expect(config.rules).toEqual([`style-src foo bar`]);
expect(config.header).toEqual(`style-src foo bar`);
});
test('allows "script_src" to be set and changes header', () => {
const config = new CspConfig({
...defaultConfig,
rules: [],
script_src: ['foo', 'bar'],
});
expect(config.rules).toEqual([`script-src foo bar`]);
expect(config.header).toEqual(`script-src foo bar`);
});
test('allows all directives to be set and changes header', () => {
const config = new CspConfig({
...defaultConfig,
rules: [],
script_src: ['script', 'foo'],
worker_src: ['worker', 'bar'],
style_src: ['style', 'dolly'],
});
expect(config.rules).toEqual([
`script-src script foo`,
`worker-src worker bar`,
`style-src style dolly`,
]);
expect(config.header).toEqual(
`script-src script foo; worker-src worker bar; style-src style dolly`
);
});
test('applies defaults when `rules` is undefined', () => {
const config = new CspConfig({
...defaultConfig,
rules: undefined,
script_src: ['script'],
worker_src: ['worker'],
style_src: ['style'],
});
expect(config.rules).toEqual([
`script-src 'unsafe-eval' 'self' script`,
`worker-src blob: 'self' worker`,
`style-src 'unsafe-inline' 'self' style`,
]);
expect(config.header).toEqual(
`script-src 'unsafe-eval' 'self' script; worker-src blob: 'self' worker; style-src 'unsafe-inline' 'self' style`
);
});
describe('allows "disableEmbedding" to be set', () => {
const disableEmbedding = true;
test('and changes rules/header if custom rules are not defined', () => {
const config = new CspConfig({ disableEmbedding });
const config = new CspConfig({ ...defaultConfig, disableEmbedding });
expect(config.disableEmbedding).toEqual(disableEmbedding);
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
expect(config.rules).toEqual(expect.arrayContaining([FRAME_ANCESTORS_RULE]));
expect(config.rules).toEqual(expect.arrayContaining([`frame-ancestors 'self'`]));
expect(config.header).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"`
);
});
test('and does not change rules/header if custom rules are defined', () => {
const rules = ['foo', 'bar'];
const config = new CspConfig({ disableEmbedding, rules });
const rules = [`foo 'self'`, `bar 'self'`];
const config = new CspConfig({ ...defaultConfig, disableEmbedding, rules });
expect(config.disableEmbedding).toEqual(disableEmbedding);
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
expect(config.rules).toEqual(rules);
expect(config.header).toMatchInlineSnapshot(`"foo; bar"`);
expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`);
});
test('and overrides `frame-ancestors` if set', () => {
const config = new CspConfig({
...defaultConfig,
disableEmbedding: true,
frame_ancestors: ['foo.com'],
});
expect(config.disableEmbedding).toEqual(disableEmbedding);
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
expect(config.header).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"`
);
});
});
});

View file

@ -6,7 +6,8 @@
* Side Public License, v 1.
*/
import { config, FRAME_ANCESTORS_RULE } from './config';
import { config, CspConfigType } from './config';
import { CspDirectives } from './csp_directives';
const DEFAULT_CONFIG = Object.freeze(config.schema.validate({}));
@ -50,8 +51,9 @@ export interface ICspConfig {
* @public
*/
export class CspConfig implements ICspConfig {
static readonly DEFAULT = new CspConfig();
static readonly DEFAULT = new CspConfig(DEFAULT_CONFIG);
readonly #directives: CspDirectives;
public readonly rules: string[];
public readonly strict: boolean;
public readonly warnLegacyBrowsers: boolean;
@ -62,16 +64,18 @@ export class CspConfig implements ICspConfig {
* 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.disableEmbedding = source.disableEmbedding;
if (!rawCspConfig.rules?.length && source.disableEmbedding) {
this.rules.push(FRAME_ANCESTORS_RULE);
constructor(rawCspConfig: CspConfigType) {
this.#directives = CspDirectives.fromConfig(rawCspConfig);
if (!rawCspConfig.rules?.length && rawCspConfig.disableEmbedding) {
this.#directives.clearDirectiveValues('frame-ancestors');
this.#directives.addDirectiveValue('frame-ancestors', `'self'`);
}
this.header = this.rules.join('; ');
this.rules = this.#directives.getRules();
this.header = this.#directives.getCspHeader();
this.strict = rawCspConfig.strict;
this.warnLegacyBrowsers = rawCspConfig.warnLegacyBrowsers;
this.disableEmbedding = rawCspConfig.disableEmbedding;
}
}

View file

@ -0,0 +1,266 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CspDirectives } from './csp_directives';
import { config as cspConfig } from './config';
describe('CspDirectives', () => {
describe('#addDirectiveValue', () => {
it('properly updates the rules', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', 'foo');
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"style-src foo",
]
`);
directives.addDirectiveValue('style-src', 'bar');
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"style-src foo bar",
]
`);
});
it('properly updates the header', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', 'foo');
expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo"`);
directives.addDirectiveValue('style-src', 'bar');
expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`);
});
it('handles distinct directives', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', 'foo');
directives.addDirectiveValue('style-src', 'bar');
directives.addDirectiveValue('worker-src', 'dolly');
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"style-src foo bar; worker-src dolly"`
);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"style-src foo bar",
"worker-src dolly",
]
`);
});
it('removes duplicates', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', 'foo');
directives.addDirectiveValue('style-src', 'foo');
directives.addDirectiveValue('style-src', 'bar');
expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"style-src foo bar",
]
`);
});
it('automatically adds single quotes for keywords', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', 'none');
directives.addDirectiveValue('style-src', 'self');
directives.addDirectiveValue('style-src', 'strict-dynamic');
directives.addDirectiveValue('style-src', 'report-sample');
directives.addDirectiveValue('style-src', 'unsafe-inline');
directives.addDirectiveValue('style-src', 'unsafe-eval');
directives.addDirectiveValue('style-src', 'unsafe-hashes');
directives.addDirectiveValue('style-src', 'unsafe-allow-redirects');
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"`
);
});
it('does not add single quotes for keywords when already present', () => {
const directives = new CspDirectives();
directives.addDirectiveValue('style-src', `'none'`);
directives.addDirectiveValue('style-src', `'self'`);
directives.addDirectiveValue('style-src', `'strict-dynamic'`);
directives.addDirectiveValue('style-src', `'report-sample'`);
directives.addDirectiveValue('style-src', `'unsafe-inline'`);
directives.addDirectiveValue('style-src', `'unsafe-eval'`);
directives.addDirectiveValue('style-src', `'unsafe-hashes'`);
directives.addDirectiveValue('style-src', `'unsafe-allow-redirects'`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"`
);
});
});
describe('#fromConfig', () => {
it('returns the correct rules for the default config', () => {
const config = cspConfig.schema.validate({});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
]
`);
});
it('returns the correct header for the default config', () => {
const config = cspConfig.schema.validate({});
const directives = CspDirectives.fromConfig(config);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"`
);
});
it('handles config with rules', () => {
const config = cspConfig.schema.validate({
rules: [`script-src 'self' http://foo.com`, `worker-src 'self'`],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'self' http://foo.com",
"worker-src 'self'",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'self' http://foo.com; worker-src 'self'"`
);
});
it('adds single quotes for keyword for rules', () => {
const config = cspConfig.schema.validate({
rules: [`script-src self http://foo.com`, `worker-src self`],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'self' http://foo.com",
"worker-src 'self'",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'self' http://foo.com; worker-src 'self'"`
);
});
it('handles multiple whitespaces when parsing rules', () => {
const config = cspConfig.schema.validate({
rules: [` script-src 'self' http://foo.com `, ` worker-src 'self' `],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'self' http://foo.com",
"worker-src 'self'",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'self' http://foo.com; worker-src 'self'"`
);
});
it('supports unregistered directives', () => {
const config = cspConfig.schema.validate({
rules: [`script-src 'self' http://foo.com`, `img-src 'self'`, 'foo bar'],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'self' http://foo.com",
"img-src 'self'",
"foo bar",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'self' http://foo.com; img-src 'self'; foo bar"`
);
});
it('adds default value for config with directives', () => {
const config = cspConfig.schema.validate({
script_src: [`baz`],
worker_src: [`foo`],
style_src: [`bar`, `dolly`],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'unsafe-eval' 'self' baz",
"worker-src blob: 'self' foo",
"style-src 'unsafe-inline' 'self' bar dolly",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self' baz; worker-src blob: 'self' foo; style-src 'unsafe-inline' 'self' bar dolly"`
);
});
it('adds additional values for some directives without defaults', () => {
const config = cspConfig.schema.validate({
connect_src: [`connect-src`],
default_src: [`default-src`],
font_src: [`font-src`],
frame_src: [`frame-src`],
img_src: [`img-src`],
frame_ancestors: [`frame-ancestors`],
report_uri: [`report-uri`],
report_to: [`report-to`],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'unsafe-eval' 'self'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
"connect-src 'self' connect-src",
"default-src 'self' default-src",
"font-src 'self' font-src",
"frame-src 'self' frame-src",
"img-src 'self' img-src",
"frame-ancestors 'self' frame-ancestors",
"report-uri report-uri",
"report-to report-to",
]
`);
});
it('adds single quotes for keywords in added directives', () => {
const config = cspConfig.schema.validate({
script_src: [`unsafe-hashes`],
});
const directives = CspDirectives.fromConfig(config);
expect(directives.getRules()).toMatchInlineSnapshot(`
Array [
"script-src 'unsafe-eval' 'self' 'unsafe-hashes'",
"worker-src blob: 'self'",
"style-src 'unsafe-inline' 'self'",
]
`);
expect(directives.getCspHeader()).toMatchInlineSnapshot(
`"script-src 'unsafe-eval' 'self' 'unsafe-hashes'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"`
);
});
});
});

View file

@ -0,0 +1,159 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CspConfigType } from './config';
export type CspDirectiveName =
| 'script-src'
| 'worker-src'
| 'style-src'
| 'frame-ancestors'
| 'connect-src'
| 'default-src'
| 'font-src'
| 'frame-src'
| 'img-src'
| 'report-uri'
| 'report-to';
/**
* The default rules that are always applied
*/
export const defaultRules: Partial<Record<CspDirectiveName, string[]>> = {
'script-src': [`'unsafe-eval'`, `'self'`],
'worker-src': [`blob:`, `'self'`],
'style-src': [`'unsafe-inline'`, `'self'`],
};
/**
* Per-directive rules that will be added when the configuration contains at least one value
* Main purpose is to add `self` value to some directives when the configuration specifies other values
*/
export const additionalRules: Partial<Record<CspDirectiveName, string[]>> = {
'connect-src': [`'self'`],
'default-src': [`'self'`],
'font-src': [`'self'`],
'img-src': [`'self'`],
'frame-ancestors': [`'self'`],
'frame-src': [`'self'`],
};
export class CspDirectives {
private readonly directives = new Map<CspDirectiveName, Set<string>>();
addDirectiveValue(directiveName: CspDirectiveName, directiveValue: string) {
if (!this.directives.has(directiveName)) {
this.directives.set(directiveName, new Set());
}
this.directives.get(directiveName)!.add(normalizeDirectiveValue(directiveValue));
}
clearDirectiveValues(directiveName: CspDirectiveName) {
this.directives.delete(directiveName);
}
getCspHeader() {
return this.getRules().join('; ');
}
getRules() {
return [...this.directives.entries()].map(([name, values]) => {
return [name, ...values].join(' ');
});
}
static fromConfig(config: CspConfigType): CspDirectives {
const cspDirectives = new CspDirectives();
// adding `csp.rules` or `default` rules
const initialRules = config.rules ? parseRules(config.rules) : { ...defaultRules };
Object.entries(initialRules).forEach(([key, values]) => {
values?.forEach((value) => {
cspDirectives.addDirectiveValue(key as CspDirectiveName, value);
});
});
// adding per-directive configuration
const additiveConfig = parseConfigDirectives(config);
[...additiveConfig.entries()].forEach(([directiveName, directiveValues]) => {
const additionalValues = additionalRules[directiveName] ?? [];
[...additionalValues, ...directiveValues].forEach((value) => {
cspDirectives.addDirectiveValue(directiveName, value);
});
});
return cspDirectives;
}
}
const parseRules = (rules: string[]): Partial<Record<CspDirectiveName, string[]>> => {
const directives: Partial<Record<CspDirectiveName, string[]>> = {};
rules.forEach((rule) => {
const [name, ...values] = rule.replace(/\s+/g, ' ').trim().split(' ');
directives[name as CspDirectiveName] = values;
});
return directives;
};
const parseConfigDirectives = (cspConfig: CspConfigType): Map<CspDirectiveName, string[]> => {
const map = new Map<CspDirectiveName, string[]>();
if (cspConfig.script_src?.length) {
map.set('script-src', cspConfig.script_src);
}
if (cspConfig.worker_src?.length) {
map.set('worker-src', cspConfig.worker_src);
}
if (cspConfig.style_src?.length) {
map.set('style-src', cspConfig.style_src);
}
if (cspConfig.connect_src?.length) {
map.set('connect-src', cspConfig.connect_src);
}
if (cspConfig.default_src?.length) {
map.set('default-src', cspConfig.default_src);
}
if (cspConfig.font_src?.length) {
map.set('font-src', cspConfig.font_src);
}
if (cspConfig.frame_src?.length) {
map.set('frame-src', cspConfig.frame_src);
}
if (cspConfig.img_src?.length) {
map.set('img-src', cspConfig.img_src);
}
if (cspConfig.frame_ancestors?.length) {
map.set('frame-ancestors', cspConfig.frame_ancestors);
}
if (cspConfig.report_uri?.length) {
map.set('report-uri', cspConfig.report_uri);
}
if (cspConfig.report_to?.length) {
map.set('report-to', cspConfig.report_to);
}
return map;
};
const keywordTokens = [
'none',
'self',
'strict-dynamic',
'report-sample',
'unsafe-inline',
'unsafe-eval',
'unsafe-hashes',
'unsafe-allow-redirects',
];
function normalizeDirectiveValue(value: string) {
if (keywordTokens.includes(value)) {
return `'${value}'`;
}
return value;
}

View file

@ -69,7 +69,11 @@ configService.atPath.mockImplementation((path) => {
} as any);
}
if (path === 'csp') {
return new BehaviorSubject({} as any);
return new BehaviorSubject({
strict: false,
disableEmbedding: false,
warnLegacyBrowsers: true,
});
}
throw new Error(`Unexpected config path: ${path}`);
});

View file

@ -8,7 +8,7 @@
import uuid from 'uuid';
import { config, HttpConfig } from './http_config';
import { CspConfig } from '../csp';
import { config as cspConfig } from '../csp';
import { ExternalUrlConfig } from '../external_url';
const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost'];
@ -465,7 +465,8 @@ describe('HttpConfig', () => {
},
},
});
const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT, ExternalUrlConfig.DEFAULT);
const rawCspConfig = cspConfig.schema.validate({});
const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT);
expect(httpConfig.customResponseHeaders).toEqual({
string: 'string',

View file

@ -79,7 +79,11 @@ describe('core lifecycle handlers', () => {
} as any);
}
if (path === 'csp') {
return new BehaviorSubject({} as any);
return new BehaviorSubject({
strict: false,
disableEmbedding: false,
warnLegacyBrowsers: true,
});
}
throw new Error(`Unexpected config path: ${path}`);
});

View file

@ -56,7 +56,11 @@ configService.atPath.mockImplementation((path) => {
} as any);
}
if (path === 'csp') {
return new BehaviorSubject({} as any);
return new BehaviorSubject({
strict: false,
disableEmbedding: false,
warnLegacyBrowsers: true,
});
}
throw new Error(`Unexpected config path: ${path}`);
});

View file

@ -777,8 +777,12 @@ export interface CountResponse {
// @public
export class CspConfig implements ICspConfig {
// (undocumented)
#private;
// Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts
//
// @internal
constructor(rawCspConfig?: Partial<Omit<ICspConfig, 'header'>>);
constructor(rawCspConfig: CspConfigType);
// (undocumented)
static readonly DEFAULT: CspConfig;
// (undocumented)

View file

@ -31,6 +31,17 @@ kibana_vars=(
csp.rules
csp.strict
csp.warnLegacyBrowsers
csp.script_src
csp.worker_src
csp.style_src
csp.connect_src
csp.default_src
csp.font_src
csp.frame_src
csp.img_src
csp.frame_ancestors
csp.report_uri
csp.report_to
data.autocomplete.valueSuggestions.terminateAfter
data.autocomplete.valueSuggestions.timeout
elasticsearch.customHeaders

View file

@ -22,7 +22,21 @@ describe('csp collector', () => {
const mockedFetchContext = createCollectorFetchContextMock();
function updateCsp(config: Partial<ICspConfig>) {
httpMock.csp = new CspConfig(config);
httpMock.csp = new CspConfig({
...CspConfig.DEFAULT,
style_src: [],
worker_src: [],
script_src: [],
connect_src: [],
default_src: [],
font_src: [],
frame_src: [],
img_src: [],
frame_ancestors: [],
report_uri: [],
report_to: [],
...config,
});
}
beforeEach(() => {