mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.5`: - [Add CSP header to all requests, including api requests (#144902)](https://github.com/elastic/kibana/pull/144902) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Thomas Watson","email":"watson@elastic.co"},"sourceCommit":{"committedDate":"2022-11-16T20:45:10Z","message":"Add CSP header to all requests, including api requests (#144902)\n\nPreviously `/api/*` requests didn't include a `Content-Security-Policy`\r\nheader, now they do.\r\n\r\nCloses #143871","sha":"5550ab6cb10fbfddf437a74900103bb33dd1afa4","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport:all-open","v8.6.0","v8.7.0"],"number":144902,"url":"https://github.com/elastic/kibana/pull/144902","mergeCommit":{"message":"Add CSP header to all requests, including api requests (#144902)\n\nPreviously `/api/*` requests didn't include a `Content-Security-Policy`\r\nheader, now they do.\r\n\r\nCloses #143871","sha":"5550ab6cb10fbfddf437a74900103bb33dd1afa4"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/145449","number":145449,"state":"MERGED","mergeCommit":{"sha":"a9f7ba61f128e61beb936d2caff143e93e3321ea","message":"[8.6] Add CSP header to all requests, including api requests (#144902) (#145449)\n\n# Backport\n\nThis will backport the following commits from `main` to `8.6`:\n- [Add CSP header to all requests, including api requests\n(#144902)](https://github.com/elastic/kibana/pull/144902)\n\n<!--- Backport version: 8.9.7 -->\n\n### Questions ?\nPlease refer to the [Backport tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT [{\"author\":{\"name\":\"Thomas\nWatson\",\"email\":\"watson@elastic.co\"},\"sourceCommit\":{\"committedDate\":\"2022-11-16T20:45:10Z\",\"message\":\"Add\nCSP header to all requests, including api requests\n(#144902)\\n\\nPreviously `/api/*` requests didn't include a\n`Content-Security-Policy`\\r\\nheader, now they do.\\r\\n\\r\\nCloses\n#143871\",\"sha\":\"5550ab6cb10fbfddf437a74900103bb33dd1afa4\",\"branchLabelMapping\":{\"^v8.7.0$\":\"main\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:enhancement\",\"backport:all-open\",\"v8.7.0\"],\"number\":144902,\"url\":\"https://github.com/elastic/kibana/pull/144902\",\"mergeCommit\":{\"message\":\"Add\nCSP header to all requests, including api requests\n(#144902)\\n\\nPreviously `/api/*` requests didn't include a\n`Content-Security-Policy`\\r\\nheader, now they do.\\r\\n\\r\\nCloses\n#143871\",\"sha\":\"5550ab6cb10fbfddf437a74900103bb33dd1afa4\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[],\"targetPullRequestStates\":[{\"branch\":\"main\",\"label\":\"v8.7.0\",\"labelRegex\":\"^v8.7.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/144902\",\"number\":144902,\"mergeCommit\":{\"message\":\"Add\nCSP header to all requests, including api requests\n(#144902)\\n\\nPreviously `/api/*` requests didn't include a\n`Content-Security-Policy`\\r\\nheader, now they do.\\r\\n\\r\\nCloses\n#143871\",\"sha\":\"5550ab6cb10fbfddf437a74900103bb33dd1afa4\"}}]}]\nBACKPORT-->\n\nCo-authored-by: Thomas Watson <watson@elastic.co>"}},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/144902","number":144902,"mergeCommit":{"message":"Add CSP header to all requests, including api requests (#144902)\n\nPreviously `/api/*` requests didn't include a `Content-Security-Policy`\r\nheader, now they do.\r\n\r\nCloses #143871","sha":"5550ab6cb10fbfddf437a74900103bb33dd1afa4"}}]}] BACKPORT-->
This commit is contained in:
parent
ed806a896e
commit
723a55ec12
7 changed files with 61 additions and 72 deletions
|
@ -102,6 +102,8 @@ export class HttpService
|
|||
},
|
||||
});
|
||||
|
||||
registerCoreHandlers(prebootSetup, config, this.env);
|
||||
|
||||
if (this.shouldListen(config)) {
|
||||
this.log.debug('starting preboot server');
|
||||
await this.prebootServer.start();
|
||||
|
|
|
@ -248,19 +248,28 @@ describe('customHeaders pre-response handler', () => {
|
|||
toolkit = createToolkit();
|
||||
});
|
||||
|
||||
it('adds the kbn-name header to the response', () => {
|
||||
const config = createConfig({ name: 'my-server-name' });
|
||||
it('adds the kbn-name and Content-Security-Policy headers to the response', () => {
|
||||
const config = createConfig({
|
||||
name: 'my-server-name',
|
||||
csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' },
|
||||
});
|
||||
const handler = createCustomHeadersPreResponseHandler(config as HttpConfig);
|
||||
|
||||
handler({} as any, {} as any, toolkit);
|
||||
|
||||
expect(toolkit.next).toHaveBeenCalledTimes(1);
|
||||
expect(toolkit.next).toHaveBeenCalledWith({ headers: { 'kbn-name': 'my-server-name' } });
|
||||
expect(toolkit.next).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
'Content-Security-Policy': 'foo',
|
||||
'kbn-name': 'my-server-name',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('adds the security headers and custom headers defined in the configuration', () => {
|
||||
const config = createConfig({
|
||||
name: 'my-server-name',
|
||||
csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' },
|
||||
securityResponseHeaders: {
|
||||
headerA: 'value-A',
|
||||
headerB: 'value-B', // will be overridden by the custom response header below
|
||||
|
@ -276,6 +285,7 @@ describe('customHeaders pre-response handler', () => {
|
|||
expect(toolkit.next).toHaveBeenCalledTimes(1);
|
||||
expect(toolkit.next).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
'Content-Security-Policy': 'foo',
|
||||
'kbn-name': 'my-server-name',
|
||||
headerA: 'value-A',
|
||||
headerB: 'x',
|
||||
|
@ -283,11 +293,13 @@ describe('customHeaders pre-response handler', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('preserve the kbn-name value from server.name if defined in custom headders ', () => {
|
||||
it('do not allow overwrite of the kbn-name and Content-Security-Policy headers if defined in custom headders ', () => {
|
||||
const config = createConfig({
|
||||
name: 'my-server-name',
|
||||
csp: { strict: true, warnLegacyBrowsers: true, disableEmbedding: true, header: 'foo' },
|
||||
customResponseHeaders: {
|
||||
'kbn-name': 'custom-name',
|
||||
'Content-Security-Policy': 'custom-csp',
|
||||
headerA: 'value-A',
|
||||
headerB: 'value-B',
|
||||
},
|
||||
|
@ -300,6 +312,7 @@ describe('customHeaders pre-response handler', () => {
|
|||
expect(toolkit.next).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
'kbn-name': 'my-server-name',
|
||||
'Content-Security-Policy': 'foo',
|
||||
headerA: 'value-A',
|
||||
headerB: 'value-B',
|
||||
},
|
||||
|
|
|
@ -61,12 +61,18 @@ export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPost
|
|||
};
|
||||
|
||||
export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => {
|
||||
const { name: serverName, securityResponseHeaders, customResponseHeaders } = config;
|
||||
const {
|
||||
name: serverName,
|
||||
securityResponseHeaders,
|
||||
customResponseHeaders,
|
||||
csp: { header: cspHeader },
|
||||
} = config;
|
||||
|
||||
return (request, response, toolkit) => {
|
||||
const additionalHeaders = {
|
||||
...securityResponseHeaders,
|
||||
...customResponseHeaders,
|
||||
'Content-Security-Policy': cspHeader,
|
||||
[KIBANA_NAME_HEADER]: serverName,
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ const createConfigService = () => {
|
|||
configService.atPath.mockImplementation((path) => {
|
||||
if (path === 'server') {
|
||||
return new BehaviorSubject({
|
||||
name: 'kibana',
|
||||
hosts: ['localhost'],
|
||||
maxPayload: new ByteSizeValue(1024),
|
||||
autoListen: true,
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('HttpResources service', () => {
|
|||
describe(`${name} register`, () => {
|
||||
const routeConfig: RouteConfig<any, any, any, 'get'> = { path: '/', validate: false };
|
||||
let register: HttpResources['register'];
|
||||
|
||||
beforeEach(async () => {
|
||||
register = await initializer();
|
||||
});
|
||||
|
@ -79,32 +80,8 @@ describe('HttpResources service', () => {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('can attach headers, except the CSP header', async () => {
|
||||
register(routeConfig, async (ctx, req, res) => {
|
||||
return res.renderCoreApp({
|
||||
headers: {
|
||||
'content-security-policy': "script-src 'unsafe-eval'",
|
||||
'x-kibana': '42',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const [[, routeHandler]] = router.get.mock.calls;
|
||||
|
||||
const responseFactory = httpResourcesMock.createResponseFactory();
|
||||
await routeHandler(context, kibanaRequest, responseFactory);
|
||||
|
||||
expect(responseFactory.ok).toHaveBeenCalledWith({
|
||||
body: '<body />',
|
||||
headers: {
|
||||
'x-kibana': '42',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderAnonymousCoreApp', () => {
|
||||
it('formats successful response', async () => {
|
||||
register(routeConfig, async (ctx, req, res) => {
|
||||
|
@ -125,32 +102,8 @@ describe('HttpResources service', () => {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('can attach headers, except the CSP header', async () => {
|
||||
register(routeConfig, async (ctx, req, res) => {
|
||||
return res.renderAnonymousCoreApp({
|
||||
headers: {
|
||||
'content-security-policy': "script-src 'unsafe-eval'",
|
||||
'x-kibana': '42',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const [[, routeHandler]] = router.get.mock.calls;
|
||||
|
||||
const responseFactory = httpResourcesMock.createResponseFactory();
|
||||
await routeHandler(context, kibanaRequest, responseFactory);
|
||||
|
||||
expect(responseFactory.ok).toHaveBeenCalledWith({
|
||||
body: '<body />',
|
||||
headers: {
|
||||
'x-kibana': '42',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderHtml', () => {
|
||||
it('formats successful response', async () => {
|
||||
const htmlBody = '<html><body /></html>';
|
||||
|
@ -165,20 +118,17 @@ describe('HttpResources service', () => {
|
|||
body: htmlBody,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can attach headers, except the CSP & "content-type" headers', async () => {
|
||||
it('can attach headers, except the "content-type" header', async () => {
|
||||
const htmlBody = '<html><body /></html>';
|
||||
register(routeConfig, async (ctx, req, res) => {
|
||||
return res.renderHtml({
|
||||
body: htmlBody,
|
||||
headers: {
|
||||
'content-type': 'text/html5',
|
||||
'content-security-policy': "script-src 'unsafe-eval'",
|
||||
'x-kibana': '42',
|
||||
},
|
||||
});
|
||||
|
@ -194,12 +144,11 @@ describe('HttpResources service', () => {
|
|||
headers: {
|
||||
'content-type': 'text/html',
|
||||
'x-kibana': '42',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderJs', () => {
|
||||
it('formats successful response', async () => {
|
||||
const jsBody = 'alert(1);';
|
||||
|
@ -214,20 +163,17 @@ describe('HttpResources service', () => {
|
|||
body: jsBody,
|
||||
headers: {
|
||||
'content-type': 'text/javascript',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can attach headers, except the CSP & "content-type" headers', async () => {
|
||||
it('can attach headers, except the "content-type" header', async () => {
|
||||
const jsBody = 'alert(1);';
|
||||
register(routeConfig, async (ctx, req, res) => {
|
||||
return res.renderJs({
|
||||
body: jsBody,
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
'content-security-policy': "script-src 'unsafe-eval'",
|
||||
'x-kibana': '42',
|
||||
},
|
||||
});
|
||||
|
@ -243,8 +189,6 @@ describe('HttpResources service', () => {
|
|||
headers: {
|
||||
'content-type': 'text/javascript',
|
||||
'x-kibana': '42',
|
||||
'content-security-policy':
|
||||
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -90,7 +90,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
request: KibanaRequest,
|
||||
response: KibanaResponseFactory
|
||||
): HttpResourcesServiceToolkit {
|
||||
const cspHeader = deps.http.csp.header;
|
||||
return {
|
||||
async renderCoreApp(options: HttpResourcesRenderOptions = {}) {
|
||||
const apmConfig = getApmConfig(request.url.pathname);
|
||||
|
@ -105,7 +104,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
|
||||
return response.ok({
|
||||
body,
|
||||
headers: { ...options.headers, 'content-security-policy': cspHeader },
|
||||
headers: options.headers,
|
||||
});
|
||||
},
|
||||
async renderAnonymousCoreApp(options: HttpResourcesRenderOptions = {}) {
|
||||
|
@ -121,7 +120,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
|
||||
return response.ok({
|
||||
body,
|
||||
headers: { ...options.headers, 'content-security-policy': cspHeader },
|
||||
headers: options.headers,
|
||||
});
|
||||
},
|
||||
renderHtml(options: HttpResourcesResponseOptions) {
|
||||
|
@ -130,7 +129,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
headers: {
|
||||
...options.headers,
|
||||
'content-type': 'text/html',
|
||||
'content-security-policy': cspHeader,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -140,7 +138,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
|
|||
headers: {
|
||||
...options.headers,
|
||||
'content-type': 'text/javascript',
|
||||
'content-security-policy': cspHeader,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1473,6 +1473,32 @@ describe('OnPreResponse', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('runs with default preResponse handlers', () => {
|
||||
it('does not allow overwriting of the "kbn-name" and "Content-Security-Policy" headers', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup(setupDeps);
|
||||
const router = createRouter('/');
|
||||
|
||||
router.get({ path: '/', validate: false }, (context, req, res) =>
|
||||
res.ok({
|
||||
headers: {
|
||||
foo: 'bar',
|
||||
'kbn-name': 'hijacked!',
|
||||
'Content-Security-Policy': 'hijacked!',
|
||||
},
|
||||
})
|
||||
);
|
||||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener).get('/').expect(200);
|
||||
|
||||
expect(response.header.foo).toBe('bar');
|
||||
expect(response.header['kbn-name']).toBe('kibana');
|
||||
expect(response.header['content-security-policy']).toBe(
|
||||
`script-src 'self' 'unsafe-eval'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('run interceptors in the right order', () => {
|
||||
it('with Auth registered', async () => {
|
||||
const {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue