Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CSP header to all requests, including api requests #144902

Merged
merged 1 commit into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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();
});
Expand All @@ -81,32 +82,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 = createHttpResourcesResponseFactory();
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) => {
Expand All @@ -127,32 +104,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 = createHttpResourcesResponseFactory();
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>';
Expand All @@ -167,20 +120,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',
},
});
Expand All @@ -196,12 +146,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);';
Expand All @@ -216,20 +165,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',
},
});
Expand All @@ -245,12 +191,11 @@ 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'",
},
});
});
});

describe('renderCss', () => {
it('formats successful response', async () => {
const cssBody = `body {border: 1px solid red;}`;
Expand All @@ -265,20 +210,17 @@ describe('HttpResources service', () => {
body: cssBody,
headers: {
'content-type': 'text/css',
'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 cssBody = `body {border: 1px solid red;}`;
register(routeConfig, async (ctx, req, res) => {
return res.renderCss({
body: cssBody,
headers: {
'content-type': 'text/css5',
'content-security-policy': "script-src 'unsafe-eval'",
'x-kibana': '42',
},
});
Expand All @@ -294,8 +236,6 @@ describe('HttpResources service', () => {
headers: {
'content-type': 'text/css',
'x-kibana': '42',
'content-security-policy':
"script-src 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'",
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,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);
Expand All @@ -116,7 +115,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 = {}) {
Expand All @@ -132,7 +131,7 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe

return response.ok({
body,
headers: { ...options.headers, 'content-security-policy': cspHeader },
headers: options.headers,
});
},
renderHtml(options: HttpResourcesResponseOptions) {
Expand All @@ -141,7 +140,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
headers: {
...options.headers,
'content-type': 'text/html',
'content-security-policy': cspHeader,
},
});
},
Expand All @@ -151,7 +149,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
headers: {
...options.headers,
'content-type': 'text/javascript',
'content-security-policy': cspHeader,
},
});
},
Expand All @@ -161,7 +158,6 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
headers: {
...options.headers,
'content-type': 'text/css',
'content-security-policy': cspHeader,
},
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -276,18 +285,21 @@ 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',
},
});
});

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',
},
Expand All @@ -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',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
watson marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions src/core/server/integration_tests/http/lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down