Skip to content

Commit faf0c28

Browse files
Enforce object_src in our CSP (#241029)
## Summary This transitions our `object_src 'report-sample' 'none'` CSP directive from the `Content-Security-Policy-Report-Only` header to the `Content-Security-Policy` header, thereby enforcing the directive. - We also have to allow administrators to configure `object_src` to suit their needs. Therefore, this PR introduces a new `csp.object_src` configuration property, which allows admins to specify a different `object_src` than the one we ship with. - This deprecates the `csp.report_only.object_src` configuration setting, as we are no longer reporting `object_src` directives in a "report only" fashion. - This adds a config deprecation for `csp.disableUnsafeEval`, which was documented as deprecated as of `8.7.0`. Resolves #241024 ## Release Notes Enforces the `object_src 'none'` directive in Kibana's Content Security Policy. Introduces a new `csp.object_src` configuration option to control its behavior. --------- Co-authored-by: florent-leborgne <[email protected]>
1 parent cf5ff5b commit faf0c28

File tree

12 files changed

+185
-33
lines changed

12 files changed

+185
-33
lines changed

docs/reference/cloud/elastic-cloud-kibana-settings.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ This setting is not available in versions 8.0.0 through 8.2.0. As such, this set
419419
`csp.img_src`
420420
: Add sources for the [Content Security Policy `img-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src).
421421

422+
`csp.object_src` {applies_to}`stack: ga 9.3`
423+
: Add sources for the [Content Security Policy `object-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src).
424+
422425
`csp.report_uri`
423426
: Add sources for the [Content Security Policy `report-uri` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri).
424427

docs/reference/configuration-reference/general-settings.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ If a setting is applicable to {{ech}} environments, its name is followed by this
4545
`csp.img_src`
4646
: Add sources for the [Content Security Policy `img-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src).
4747

48+
`csp.object_src` {applies_to}`stack: ga 9.3`
49+
: Add sources for the [Content Security Policy `object-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src).
50+
4851
`csp.frame_ancestors`
4952
: Add sources for the [Content Security Policy `frame-ancestors` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors).
5053

@@ -59,6 +62,11 @@ If a setting is applicable to {{ech}} environments, its name is followed by this
5962
`csp.report_only.object_src`
6063
: Add sources for the [Content Security Policy `object-src` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/object-src) in reporting mode.
6164

65+
:::{note}
66+
:applies_to: stack: deprecated 9.3
67+
This setting is deprecated in favor of `csp.object_src`.
68+
:::
69+
6270
`csp.report_uri`
6371
: Add sources for the [Content Security Policy `report-uri` directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri).
6472

src/core/packages/http/server-internal/src/csp/config.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,40 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import { cloneDeep } from 'lodash';
11+
import { applyDeprecations, configDeprecationFactory } from '@kbn/config';
1012
import { cspConfig } from './config';
1113

14+
const deprecationContext = {
15+
branch: 'main',
16+
version: '9.0.0',
17+
docLinks: {} as any,
18+
};
19+
20+
const applyConfigDeprecations = (settings: Record<string, any> = {}) => {
21+
const deprecations = cspConfig.deprecations!(configDeprecationFactory);
22+
const deprecationMessages: string[] = [];
23+
const configPaths: string[] = [];
24+
const { config: migrated } = applyDeprecations(
25+
settings,
26+
deprecations.map((deprecation) => ({
27+
deprecation,
28+
path: cspConfig.path,
29+
context: deprecationContext,
30+
})),
31+
() =>
32+
({ message, configPath }) => {
33+
deprecationMessages.push(message);
34+
configPaths.push(configPath);
35+
}
36+
);
37+
return {
38+
configPaths,
39+
messages: deprecationMessages,
40+
migrated,
41+
};
42+
};
43+
1244
describe('config.validate()', () => {
1345
it(`does not allow "disableEmbedding" to be set to true`, () => {
1446
// This is intentionally not editable in the raw CSP config.
@@ -320,6 +352,36 @@ describe('config.validate()', () => {
320352
});
321353
});
322354

355+
describe(`"object_src"`, () => {
356+
it('throws if using an `nonce-*` value', () => {
357+
expect(() =>
358+
cspConfig.schema.validate({
359+
object_src: [`hello`, `nonce-foo`],
360+
})
361+
).toThrowErrorMatchingInlineSnapshot(
362+
`"[object_src]: using \\"nonce-*\\" is considered insecure and is not allowed"`
363+
);
364+
});
365+
366+
it("throws if using `none` or `'none'`", () => {
367+
expect(() =>
368+
cspConfig.schema.validate({
369+
object_src: [`hello`, `none`],
370+
})
371+
).toThrowErrorMatchingInlineSnapshot(
372+
`"[object_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
373+
);
374+
375+
expect(() =>
376+
cspConfig.schema.validate({
377+
object_src: [`hello`, `'none'`],
378+
})
379+
).toThrowErrorMatchingInlineSnapshot(
380+
`"[object_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"`
381+
);
382+
});
383+
});
384+
323385
describe(`"frame_ancestors"`, () => {
324386
it('throws if using an `nonce-*` value', () => {
325387
expect(() =>
@@ -350,3 +412,40 @@ describe('config.validate()', () => {
350412
});
351413
});
352414
});
415+
416+
describe('config deprecations', () => {
417+
it('does not report deprecations for default configuration', () => {
418+
const defaultConfig = { csp: {} };
419+
const { messages, migrated } = applyConfigDeprecations(cloneDeep(defaultConfig));
420+
expect(migrated).toEqual(defaultConfig);
421+
expect(messages).toHaveLength(0);
422+
});
423+
424+
it('warns when configuring csp.disableUnsafeEval', () => {
425+
const userConfig = {
426+
csp: {
427+
disableUnsafeEval: false,
428+
},
429+
};
430+
const { messages, configPaths, migrated } = applyConfigDeprecations(cloneDeep(userConfig));
431+
expect(migrated).toEqual(userConfig);
432+
expect(configPaths).toEqual(['csp.disableUnsafeEval']);
433+
expect(messages).toEqual([
434+
'`csp.disableUnsafeEval` has been replaced by the `csp.script_src` setting.',
435+
]);
436+
});
437+
438+
it('warns when configuring the unused csp.report_only.object_src', () => {
439+
const userConfig = {
440+
csp: {
441+
report_only: {
442+
object_src: "'self'",
443+
},
444+
},
445+
};
446+
const { messages, configPaths, migrated } = applyConfigDeprecations(cloneDeep(userConfig));
447+
expect(migrated).toEqual({}); // the setting should be removed from config
448+
expect(configPaths).toEqual(['csp.report_only.object_src']);
449+
expect(messages).toEqual(['You no longer need to configure "csp.report_only.object_src".']);
450+
});
451+
});

src/core/packages/http/server-internal/src/csp/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ const configSchema = schema.object(
7474
defaultValue: [],
7575
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
7676
}),
77+
object_src: schema.arrayOf(schema.string(), {
78+
defaultValue: [],
79+
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
80+
}),
7781
frame_ancestors: schema.arrayOf(schema.string(), {
7882
defaultValue: [],
7983
validate: getDirectiveValidator({ allowNone: false, allowNonce: false }),
@@ -142,4 +146,17 @@ export const cspConfig: ServiceConfigDescriptor<CspConfigType> = {
142146
// ? https://github.com/elastic/kibana/pull/52251
143147
path: 'csp',
144148
schema: configSchema,
149+
deprecations: ({ unusedFromRoot, deprecateFromRoot }) => [
150+
unusedFromRoot('csp.report_only.object_src', { level: 'warning' }),
151+
deprecateFromRoot('csp.disableUnsafeEval', '10.0.0', {
152+
level: 'warning',
153+
message: '`csp.disableUnsafeEval` has been replaced by the `csp.script_src` setting.',
154+
correctiveActions: {
155+
manualSteps: [
156+
'Remove the `csp.disableUnsafeEval` setting from your kibana configuration file.',
157+
"Use `csp.script_src: ['unsafe-eval']` instead if you wish to enable `unsafe-eval`.",
158+
],
159+
},
160+
}),
161+
],
145162
};

src/core/packages/http/server-internal/src/csp/csp_config.test.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ describe('CspConfig', () => {
3636
CspConfig {
3737
"disableEmbedding": false,
3838
"disableUnsafeEval": true,
39-
"header": "script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'",
40-
"reportOnlyHeader": "form-action 'report-sample' 'self'; object-src 'report-sample' 'none'",
39+
"header": "script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'",
40+
"reportOnlyHeader": "form-action 'report-sample' 'self'",
4141
"strict": true,
4242
"warnLegacyBrowsers": true,
4343
}
@@ -68,7 +68,7 @@ describe('CspConfig', () => {
6868
worker_src: ['foo', 'bar'],
6969
});
7070
expect(config.header).toEqual(
71-
`script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob: foo bar; style-src 'report-sample' 'self' 'unsafe-inline'`
71+
`script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob: foo bar; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
7272
);
7373
});
7474

@@ -79,7 +79,7 @@ describe('CspConfig', () => {
7979
});
8080

8181
expect(config.header).toEqual(
82-
`script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline' foo bar`
82+
`script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline' foo bar; object-src 'report-sample' 'none'`
8383
);
8484
});
8585

@@ -90,7 +90,7 @@ describe('CspConfig', () => {
9090
});
9191

9292
expect(config.header).toEqual(
93-
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'`
93+
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
9494
);
9595
});
9696

@@ -100,9 +100,10 @@ describe('CspConfig', () => {
100100
script_src: ['script', 'foo'],
101101
worker_src: ['worker', 'bar'],
102102
style_src: ['style', 'dolly'],
103+
object_src: ['object', 'obj'],
103104
});
104105
expect(config.header).toEqual(
105-
`script-src 'report-sample' 'self' script foo; worker-src 'report-sample' 'self' blob: worker bar; style-src 'report-sample' 'self' 'unsafe-inline' style dolly`
106+
`script-src 'report-sample' 'self' script foo; worker-src 'report-sample' 'self' blob: worker bar; style-src 'report-sample' 'self' 'unsafe-inline' style dolly; object-src 'report-sample' object obj`
106107
);
107108
});
108109

@@ -114,7 +115,7 @@ describe('CspConfig', () => {
114115
style_src: ['style'],
115116
});
116117
expect(config.header).toEqual(
117-
`script-src 'report-sample' 'self' script; worker-src 'report-sample' 'self' blob: worker; style-src 'report-sample' 'self' 'unsafe-inline' style`
118+
`script-src 'report-sample' 'self' script; worker-src 'report-sample' 'self' blob: worker; style-src 'report-sample' 'self' 'unsafe-inline' style; object-src 'report-sample' 'none'`
118119
);
119120
});
120121

@@ -127,7 +128,7 @@ describe('CspConfig', () => {
127128
});
128129

129130
expect(config.header).toEqual(
130-
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'`
131+
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
131132
);
132133
});
133134

@@ -139,7 +140,7 @@ describe('CspConfig', () => {
139140
});
140141

141142
expect(config.header).toEqual(
142-
`script-src 'report-sample' 'self' 'unsafe-eval' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'`
143+
`script-src 'report-sample' 'self' 'unsafe-eval' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
143144
);
144145
});
145146

@@ -150,7 +151,7 @@ describe('CspConfig', () => {
150151
});
151152

152153
expect(config.header).toEqual(
153-
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'`
154+
`script-src 'report-sample' 'self' foo bar; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
154155
);
155156
});
156157
});
@@ -163,7 +164,7 @@ describe('CspConfig', () => {
163164
expect(config.disableEmbedding).toEqual(disableEmbedding);
164165
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
165166
expect(config.header).toMatchInlineSnapshot(
166-
`"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; frame-ancestors 'self'"`
167+
`"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'; frame-ancestors 'self'"`
167168
);
168169
});
169170

@@ -176,7 +177,7 @@ describe('CspConfig', () => {
176177
expect(config.disableEmbedding).toEqual(disableEmbedding);
177178
expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding);
178179
expect(config.header).toMatchInlineSnapshot(
179-
`"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; frame-ancestors 'self'"`
180+
`"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'; frame-ancestors 'self'"`
180181
);
181182
});
182183
});
@@ -186,7 +187,7 @@ describe('CspConfig', () => {
186187
test(`adds, for example, CDN host name to directives along with 'self'`, () => {
187188
const config = new CspConfig(defaultConfig, { default_src: ['foo.bar'] });
188189
expect(config.header).toEqual(
189-
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; default-src 'self' foo.bar"
190+
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'; default-src 'self' foo.bar"
190191
);
191192
});
192193

@@ -195,7 +196,7 @@ describe('CspConfig', () => {
195196
/* empty */
196197
});
197198
expect(config.header).toEqual(
198-
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'"
199+
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'"
199200
);
200201
});
201202
test('Passing an empty array in additional config does not affect existing config', () => {
@@ -204,7 +205,7 @@ describe('CspConfig', () => {
204205
worker_src: ['foo.bar'],
205206
});
206207
expect(config.header).toEqual(
207-
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob: foo.bar; style-src 'report-sample' 'self' 'unsafe-inline'"
208+
"script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob: foo.bar; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'"
208209
);
209210
});
210211
});

0 commit comments

Comments
 (0)