Filter the response headers in the proxy backend

This commit is contained in:
Dominik Henneke
2020-11-27 12:59:57 +01:00
parent 7aa0830abb
commit 6a6c7c14ea
4 changed files with 108 additions and 4 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-proxy-backend': patch
---
Filter the headers that are sent from the proxied-targed back to the frontend to not forwarded unwanted authentication or
monitoring contexts from other origins (like `Set-Cookie` with e.g. a google analytics context). The implementation reuses
the `allowedHeaders` configuration that now controls both directions `frontend->target` and `target->frontend`.
+5 -1
View File
@@ -78,7 +78,8 @@ There are also additional settings:
- `allowedMethods`: Limit the forwarded HTTP methods. For example
`allowedMethods: ['GET']` enforces read-only access.
- `allowedHeaders`: A list of headers that should be forwarded to the target.
- `allowedHeaders`: A list of headers that should be forwarded to and received
from the target.
By default, the proxy will only forward safe HTTP request headers to the target.
Those are based on the headers that are considered safe for CORS and includes
@@ -88,3 +89,6 @@ set by the proxy. If the proxy should forward other headers like
example `allowedHeaders: ['Authorization']`. This should help to not
accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to
third-parties.
The same logic applies to headers that are sent from the target back to the
frontend.
@@ -198,4 +198,74 @@ describe('buildMiddleware', () => {
'X-Auth-Request-User',
);
});
it('responds default headers', async () => {
buildMiddleware('/api/', logger, 'test', {
target: 'http://mocked',
});
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
const config = mockCreateProxyMiddleware.mock
.calls[0][1] as ProxyMiddlewareConfig;
const testClientResponse = {
headers: {
'cache-control': 'value',
'content-language': 'value',
'content-length': 'value',
'content-type': 'value',
expires: 'value',
'last-modified': 'value',
pragma: 'value',
'set-cookie': ['value'],
},
} as Partial<http.IncomingMessage>;
expect(config).toBeDefined();
expect(config.onProxyReq).toBeDefined();
config.onProxyRes!(
testClientResponse as http.IncomingMessage,
{} as http.IncomingMessage,
{} as http.ServerResponse,
);
expect(Object.keys(testClientResponse.headers!)).toEqual([
'cache-control',
'content-language',
'content-length',
'content-type',
'expires',
'last-modified',
'pragma',
]);
});
it('responds configured headers', async () => {
buildMiddleware('/api/', logger, 'test', {
target: 'http://mocked',
allowedHeaders: ['set-cookie'],
});
expect(createProxyMiddleware).toHaveBeenCalledTimes(1);
const config = mockCreateProxyMiddleware.mock
.calls[0][1] as ProxyMiddlewareConfig;
const testClientResponse = {
headers: {
'set-cookie': [],
'x-auth-request-user': 'asd',
},
} as Partial<http.IncomingMessage>;
config.onProxyRes!(
testClientResponse as http.IncomingMessage,
{} as http.IncomingMessage,
{} as http.ServerResponse,
);
expect(Object.keys(testClientResponse.headers!)).toEqual(['set-cookie']);
});
});
+26 -3
View File
@@ -90,8 +90,8 @@ export function buildMiddleware(
return fullConfig?.allowedMethods?.includes(req.method!) ?? true;
};
// Only forward the allowed HTTP headers to not forward unwanted secret headers
const headerAllowList = new Set<string>(
// Only return the allowed HTTP headers to not forward unwanted secret headers
const requestHeaderAllowList = new Set<string>(
[
// allow all safe headers
...safeForwardHeaders,
@@ -104,16 +104,39 @@ export function buildMiddleware(
].map(h => h.toLocaleLowerCase()),
);
// only forward the allowed headers in client->backend
fullConfig.onProxyReq = (proxyReq: http.ClientRequest) => {
const headerNames = proxyReq.getHeaderNames();
headerNames.forEach(h => {
if (!headerAllowList.has(h.toLocaleLowerCase())) {
if (!requestHeaderAllowList.has(h.toLocaleLowerCase())) {
proxyReq.removeHeader(h);
}
});
};
// Only forward the allowed HTTP headers to not forward unwanted secret headers
const responseHeaderAllowList = new Set<string>(
[
// allow all safe headers
...safeForwardHeaders,
// allow all configured headers
...(fullConfig.allowedHeaders || []),
].map(h => h.toLocaleLowerCase()),
);
// only forward the allowed headers in backend->client
fullConfig.onProxyRes = (proxyRes: http.IncomingMessage) => {
const headerNames = Object.keys(proxyRes.headers);
headerNames.forEach(h => {
if (!responseHeaderAllowList.has(h.toLocaleLowerCase())) {
delete proxyRes.headers[h];
}
});
};
return createProxyMiddleware(filter, fullConfig);
}