mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[console] support HEAD requests (#10611)
* [console] support HEAD requests Hapi handles HEAD requests automatically (by ignoring the body of a GET response) which prevents the console proxy from handling them correctly. To fix this the console proxy now only accepts requests with the POST method and requires the path and method in the query string. * [console] proxy 'Warning' header from es * [console] fix lint errors
This commit is contained in:
parent
556bfab85d
commit
e48a1a9740
9 changed files with 597 additions and 83 deletions
|
@ -1,11 +1,14 @@
|
|||
import Joi from 'joi';
|
||||
import Boom from 'boom';
|
||||
import apiServer from './api_server/server';
|
||||
import { existsSync } from 'fs';
|
||||
import { resolve, join, sep } from 'path';
|
||||
import { has } from 'lodash';
|
||||
import { ProxyConfigCollection } from './server/proxy_config_collection';
|
||||
import { getElasticsearchProxyConfig } from './server/elasticsearch_proxy_config';
|
||||
|
||||
import {
|
||||
ProxyConfigCollection,
|
||||
getElasticsearchProxyConfig,
|
||||
createProxyRoute
|
||||
} from './server';
|
||||
|
||||
export default function (kibana) {
|
||||
const modules = resolve(__dirname, 'public/webpackShims/');
|
||||
|
@ -66,91 +69,35 @@ export default function (kibana) {
|
|||
},
|
||||
|
||||
init: function (server, options) {
|
||||
const filters = options.proxyFilter.map(str => new RegExp(str));
|
||||
|
||||
if (options.ssl && options.ssl.verify) {
|
||||
throw new Error('sense.ssl.verify is no longer supported.');
|
||||
}
|
||||
|
||||
const config = server.config();
|
||||
const { filterHeaders } = server.plugins.elasticsearch;
|
||||
const proxyConfigCollection = new ProxyConfigCollection(options.proxyConfig);
|
||||
const proxyRouteConfig = {
|
||||
validate: {
|
||||
query: Joi.object().keys({
|
||||
uri: Joi.string()
|
||||
}).unknown(true),
|
||||
},
|
||||
const proxyPathFilters = options.proxyFilter.map(str => new RegExp(str));
|
||||
|
||||
pre: [
|
||||
function filterUri(req, reply) {
|
||||
const { uri } = req.query;
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: config.get('elasticsearch.url'),
|
||||
pathFilters: proxyPathFilters,
|
||||
getConfigForReq(req, uri) {
|
||||
const whitelist = config.get('elasticsearch.requestHeadersWhitelist');
|
||||
const headers = filterHeaders(req.headers, whitelist);
|
||||
|
||||
if (!filters.some(re => re.test(uri))) {
|
||||
const err = Boom.forbidden();
|
||||
err.output.payload = `Error connecting to '${uri}':\n\nUnable to send requests to that url.`;
|
||||
err.output.headers['content-type'] = 'text/plain';
|
||||
reply(err);
|
||||
} else {
|
||||
reply();
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
handler(req, reply) {
|
||||
let baseUri = server.config().get('elasticsearch.url');
|
||||
let { uri:path } = req.query;
|
||||
|
||||
baseUri = baseUri.replace(/\/+$/, '');
|
||||
path = path.replace(/^\/+/, '');
|
||||
const uri = baseUri + '/' + path;
|
||||
|
||||
const requestHeadersWhitelist = server.config().get('elasticsearch.requestHeadersWhitelist');
|
||||
const filterHeaders = server.plugins.elasticsearch.filterHeaders;
|
||||
|
||||
let additionalConfig;
|
||||
if (server.config().get('console.proxyConfig')) {
|
||||
additionalConfig = proxyConfigCollection.configForUri(uri);
|
||||
} else {
|
||||
additionalConfig = getElasticsearchProxyConfig(server);
|
||||
if (config.has('console.proxyConfig')) {
|
||||
return {
|
||||
...proxyConfigCollection.configForUri(uri),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
reply.proxy({
|
||||
mapUri: function (request, done) {
|
||||
done(null, uri, filterHeaders(request.headers, requestHeadersWhitelist));
|
||||
},
|
||||
xforward: true,
|
||||
onResponse(err, res, request, reply) {
|
||||
if (err != null) {
|
||||
reply(`Error connecting to '${uri}':\n\n${err.message}`).type('text/plain').statusCode = 502;
|
||||
} else {
|
||||
reply(null, res);
|
||||
}
|
||||
},
|
||||
|
||||
...additionalConfig
|
||||
});
|
||||
return {
|
||||
...getElasticsearchProxyConfig(server),
|
||||
headers,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
server.route({
|
||||
path: '/api/console/proxy',
|
||||
method: '*',
|
||||
config: {
|
||||
...proxyRouteConfig,
|
||||
|
||||
payload: {
|
||||
output: 'stream',
|
||||
parse: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/console/proxy',
|
||||
method: 'GET',
|
||||
config: {
|
||||
...proxyRouteConfig
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
server.route({
|
||||
path: '/api/console/api_server',
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
let $ = require('jquery');
|
||||
import { stringify as formatQueryString } from 'querystring'
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
let esVersion = [];
|
||||
|
||||
|
@ -34,13 +36,13 @@ module.exports.send = function (method, path, data) {
|
|||
}
|
||||
|
||||
var options = {
|
||||
url: '../api/console/proxy?uri=' + encodeURIComponent(path),
|
||||
data: method == "GET" ? null : data,
|
||||
url: '../api/console/proxy?' + formatQueryString({ path, method }),
|
||||
data,
|
||||
contentType,
|
||||
cache: false,
|
||||
crossDomain: true,
|
||||
type: method,
|
||||
dataType: "text", // disable automatic guessing
|
||||
type: 'POST',
|
||||
dataType: 'text', // disable automatic guessing
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import sinon from 'sinon';
|
||||
import Wreck from 'wreck';
|
||||
import expect from 'expect.js';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { createProxyRoute } from '../../';
|
||||
|
||||
import { createWreckResponseStub } from './stubs';
|
||||
|
||||
describe('Console Proxy Route', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const teardowns = [];
|
||||
let request;
|
||||
|
||||
beforeEach(() => {
|
||||
teardowns.push(() => sandbox.restore());
|
||||
request = async (method, path, response) => {
|
||||
sandbox.stub(Wreck, 'request', createWreckResponseStub(response));
|
||||
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
||||
teardowns.push(() => server.stop());
|
||||
|
||||
const params = [];
|
||||
if (path != null) params.push(`path=${path}`);
|
||||
if (method != null) params.push(`method=${method}`);
|
||||
return await server.inject({
|
||||
method: 'POST',
|
||||
url: `/api/console/proxy${params.length ? `?${params.join('&')}` : ''}`,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(teardowns.splice(0).map(fn => fn()));
|
||||
});
|
||||
|
||||
describe('response body', () => {
|
||||
context('GET request', () => {
|
||||
it('returns the exact body', async () => {
|
||||
const { payload } = await request('GET', '/', 'foobar');
|
||||
expect(payload).to.be('foobar');
|
||||
});
|
||||
});
|
||||
context('POST request', () => {
|
||||
it('returns the exact body', async () => {
|
||||
const { payload } = await request('POST', '/', 'foobar');
|
||||
expect(payload).to.be('foobar');
|
||||
});
|
||||
});
|
||||
context('PUT request', () => {
|
||||
it('returns the exact body', async () => {
|
||||
const { payload } = await request('PUT', '/', 'foobar');
|
||||
expect(payload).to.be('foobar');
|
||||
});
|
||||
});
|
||||
context('DELETE request', () => {
|
||||
it('returns the exact body', async () => {
|
||||
const { payload } = await request('DELETE', '/', 'foobar');
|
||||
expect(payload).to.be('foobar');
|
||||
});
|
||||
});
|
||||
context('HEAD request', () => {
|
||||
it('returns the status code and text', async () => {
|
||||
const { payload } = await request('HEAD', '/');
|
||||
expect(payload).to.be('200 - OK');
|
||||
});
|
||||
context('mixed casing', () => {
|
||||
it('returns the status code and text', async () => {
|
||||
const { payload } = await request('HeAd', '/');
|
||||
expect(payload).to.be('200 - OK');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
import { request } from 'http';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Wreck from 'wreck';
|
||||
import expect from 'expect.js';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { createProxyRoute } from '../../';
|
||||
|
||||
import { createWreckResponseStub } from './stubs';
|
||||
|
||||
describe('Console Proxy Route', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const teardowns = [];
|
||||
let setup;
|
||||
|
||||
beforeEach(() => {
|
||||
teardowns.push(() => sandbox.restore());
|
||||
|
||||
sandbox.stub(Wreck, 'request', createWreckResponseStub());
|
||||
|
||||
setup = () => {
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
||||
teardowns.push(() => server.stop());
|
||||
|
||||
return { server };
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(teardowns.splice(0).map(fn => fn()));
|
||||
});
|
||||
|
||||
describe('headers', function () {
|
||||
this.timeout(Infinity);
|
||||
|
||||
it('forwards the remote header info', async () => {
|
||||
const { server } = setup();
|
||||
await server.start();
|
||||
|
||||
const resp = await new Promise(resolve => {
|
||||
request({
|
||||
protocol: server.info.protocol + ':',
|
||||
host: server.info.address,
|
||||
port: server.info.port,
|
||||
method: 'POST',
|
||||
path: '/api/console/proxy?method=GET&path=/'
|
||||
}, resolve).end();
|
||||
});
|
||||
|
||||
resp.destroy();
|
||||
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
const { headers } = Wreck.request.getCall(0).args[2];
|
||||
expect(headers).to.have.property('x-forwarded-for').and.not.be('');
|
||||
expect(headers).to.have.property('x-forwarded-port').and.not.be('');
|
||||
expect(headers).to.have.property('x-forwarded-proto').and.not.be('');
|
||||
expect(headers).to.have.property('x-forwarded-host').and.not.be('');
|
||||
});
|
||||
});
|
||||
});
|
163
src/core_plugins/console/server/__tests__/proxy_route/params.js
Normal file
163
src/core_plugins/console/server/__tests__/proxy_route/params.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
import { Agent } from 'http';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Wreck from 'wreck';
|
||||
import expect from 'expect.js';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { createProxyRoute } from '../../';
|
||||
|
||||
import { createWreckResponseStub } from './stubs';
|
||||
|
||||
describe('Console Proxy Route', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const teardowns = [];
|
||||
let setup;
|
||||
|
||||
beforeEach(() => {
|
||||
teardowns.push(() => sandbox.restore());
|
||||
|
||||
sandbox.stub(Wreck, 'request', createWreckResponseStub());
|
||||
|
||||
setup = () => {
|
||||
const server = new Server();
|
||||
server.connection({ port: 0 });
|
||||
teardowns.push(() => server.stop());
|
||||
return { server };
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(teardowns.splice(0).map(fn => fn()));
|
||||
});
|
||||
|
||||
describe('params', () => {
|
||||
describe('pathFilters', () => {
|
||||
context('no matches', () => {
|
||||
it('rejects with 403', async () => {
|
||||
const { server } = setup();
|
||||
server.route(createProxyRoute({
|
||||
pathFilters: [
|
||||
/^\/foo\//,
|
||||
/^\/bar\//,
|
||||
]
|
||||
}));
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/console/proxy?method=GET&path=/baz/type/id',
|
||||
});
|
||||
|
||||
expect(statusCode).to.be(403);
|
||||
});
|
||||
});
|
||||
context('one match', () => {
|
||||
it('allows the request', async () => {
|
||||
const { server } = setup();
|
||||
server.route(createProxyRoute({
|
||||
pathFilters: [
|
||||
/^\/foo\//,
|
||||
/^\/bar\//,
|
||||
]
|
||||
}));
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/console/proxy?method=GET&path=/foo/type/id',
|
||||
});
|
||||
|
||||
expect(statusCode).to.be(200);
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('all match', () => {
|
||||
it('allows the request', async () => {
|
||||
const { server } = setup();
|
||||
server.route(createProxyRoute({
|
||||
pathFilters: [
|
||||
/^\/foo\//,
|
||||
/^\/bar\//,
|
||||
]
|
||||
}));
|
||||
|
||||
const { statusCode } = await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/console/proxy?method=GET&path=/foo/type/id',
|
||||
});
|
||||
|
||||
expect(statusCode).to.be(200);
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfigForReq()', () => {
|
||||
it('passes the request and targeted uri', async () => {
|
||||
const { server } = setup();
|
||||
|
||||
const getConfigForReq = sinon.stub().returns({});
|
||||
|
||||
server.route(createProxyRoute({ getConfigForReq }));
|
||||
await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/console/proxy?method=HEAD&path=/index/type/id',
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(getConfigForReq);
|
||||
const args = getConfigForReq.getCall(0).args;
|
||||
expect(args[0]).to.have.property('path', '/api/console/proxy');
|
||||
expect(args[0]).to.have.property('method', 'post');
|
||||
expect(args[0]).to.have.property('query').eql({ method: 'HEAD', path: '/index/type/id' });
|
||||
expect(args[1]).to.be('/index/type/id');
|
||||
});
|
||||
|
||||
it('sends the returned timeout, rejectUnauthorized, agent, and base headers to Wreck', async () => {
|
||||
const { server } = setup();
|
||||
|
||||
const timeout = Math.round(Math.random() * 10000);
|
||||
const agent = new Agent();
|
||||
const rejectUnauthorized = !!Math.round(Math.random());
|
||||
const headers = {
|
||||
foo: 'bar',
|
||||
baz: 'bop'
|
||||
};
|
||||
|
||||
server.route(createProxyRoute({
|
||||
getConfigForReq: () => ({
|
||||
timeout,
|
||||
agent,
|
||||
rejectUnauthorized,
|
||||
headers
|
||||
})
|
||||
}));
|
||||
|
||||
await server.inject({
|
||||
method: 'POST',
|
||||
url: '/api/console/proxy?method=HEAD&path=/index/type/id',
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
const opts = Wreck.request.getCall(0).args[2];
|
||||
expect(opts).to.have.property('timeout', timeout);
|
||||
expect(opts).to.have.property('agent', agent);
|
||||
expect(opts).to.have.property('rejectUnauthorized', rejectUnauthorized);
|
||||
expect(opts.headers).to.have.property('foo', 'bar');
|
||||
expect(opts.headers).to.have.property('baz', 'bop');
|
||||
});
|
||||
});
|
||||
|
||||
describe('baseUrl', () => {
|
||||
context('default', () => {
|
||||
it('ensures that the path starts with a /');
|
||||
});
|
||||
context('url ends with a slash', () => {
|
||||
it('combines clean with paths that start with a slash');
|
||||
it(`combines clean with paths that don't start with a slash`);
|
||||
});
|
||||
context(`url doesn't end with a slash`, () => {
|
||||
it('combines clean with paths that start with a slash');
|
||||
it(`combines clean with paths that don't start with a slash`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,115 @@
|
|||
import sinon from 'sinon';
|
||||
import Wreck from 'wreck';
|
||||
import expect from 'expect.js';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { createProxyRoute } from '../../';
|
||||
|
||||
import { createWreckResponseStub } from './stubs';
|
||||
|
||||
describe('Console Proxy Route', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const teardowns = [];
|
||||
let request;
|
||||
|
||||
beforeEach(() => {
|
||||
teardowns.push(() => sandbox.restore());
|
||||
|
||||
sandbox.stub(Wreck, 'request', createWreckResponseStub());
|
||||
|
||||
request = async (method, path) => {
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
||||
teardowns.push(() => server.stop());
|
||||
|
||||
const params = [];
|
||||
if (path != null) params.push(`path=${path}`);
|
||||
if (method != null) params.push(`method=${method}`);
|
||||
return await server.inject({
|
||||
method: 'POST',
|
||||
url: `/api/console/proxy${params.length ? `?${params.join('&')}` : ''}`,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(teardowns.splice(0).map(fn => fn()));
|
||||
});
|
||||
|
||||
describe('query string', () => {
|
||||
describe('path', () => {
|
||||
context('contains full url', () => {
|
||||
it('treats the url as a path', async () => {
|
||||
await request('GET', 'http://evil.com/test');
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
const args = Wreck.request.getCall(0).args;
|
||||
expect(args[1]).to.be('http://localhost:9200/http://evil.com/test');
|
||||
});
|
||||
});
|
||||
context('is missing', () => {
|
||||
it('returns a 400 error', async () => {
|
||||
const { statusCode } = await request('GET', undefined);
|
||||
expect(statusCode).to.be(400);
|
||||
sinon.assert.notCalled(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('is empty', () => {
|
||||
it('returns a 400 error', async () => {
|
||||
const { statusCode } = await request('GET', '');
|
||||
expect(statusCode).to.be(400);
|
||||
sinon.assert.notCalled(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('starts with a slash', () => {
|
||||
it('combines well with the base url', async () => {
|
||||
await request('GET', '/index/type/id');
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
expect(Wreck.request.getCall(0).args[1]).to.be('http://localhost:9200/index/type/id');
|
||||
});
|
||||
});
|
||||
context(`doesn't start with a slash`, () => {
|
||||
it('combines well with the base url', async () => {
|
||||
await request('GET', 'index/type/id');
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
expect(Wreck.request.getCall(0).args[1]).to.be('http://localhost:9200/index/type/id');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('method', () => {
|
||||
context('is missing', () => {
|
||||
it('returns a 400 error', async () => {
|
||||
const { statusCode } = await request(null, '/');
|
||||
expect(statusCode).to.be(400);
|
||||
sinon.assert.notCalled(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('is empty', () => {
|
||||
it('returns a 400 error', async () => {
|
||||
const { statusCode } = await request('', '/');
|
||||
expect(statusCode).to.be(400);
|
||||
sinon.assert.notCalled(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('is an invalid http method', () => {
|
||||
it('returns a 400 error', async () => {
|
||||
const { statusCode } = await request('foo', '/');
|
||||
expect(statusCode).to.be(400);
|
||||
sinon.assert.notCalled(Wreck.request);
|
||||
});
|
||||
});
|
||||
context('is mixed case', () => {
|
||||
it('sends a request with the exact method', async () => {
|
||||
const { statusCode } = await request('HeAd', '/');
|
||||
expect(statusCode).to.be(200);
|
||||
sinon.assert.calledOnce(Wreck.request);
|
||||
expect(Wreck.request.getCall(0).args[0]).to.be('HeAd');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { Readable } from 'stream';
|
||||
|
||||
export function createWreckResponseStub(response) {
|
||||
return (...args) => {
|
||||
const resp = new Readable({
|
||||
read() {
|
||||
if (response) {
|
||||
this.push(response);
|
||||
}
|
||||
this.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
resp.statusCode = 200;
|
||||
resp.statusMessage = 'OK';
|
||||
resp.headers = {
|
||||
'content-type': 'text/plain',
|
||||
'content-length': String(response ? response.length : 0)
|
||||
};
|
||||
|
||||
args.pop()(null, resp);
|
||||
};
|
||||
}
|
3
src/core_plugins/console/server/index.js
Normal file
3
src/core_plugins/console/server/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { ProxyConfigCollection } from './proxy_config_collection';
|
||||
export { getElasticsearchProxyConfig } from './elasticsearch_proxy_config';
|
||||
export { createProxyRoute } from './proxy_route';
|
113
src/core_plugins/console/server/proxy_route.js
Normal file
113
src/core_plugins/console/server/proxy_route.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
import Joi from 'joi';
|
||||
import Boom from 'boom';
|
||||
import Wreck from 'wreck';
|
||||
import { trimLeft, trimRight } from 'lodash';
|
||||
|
||||
function resolveUri(base, path) {
|
||||
return `${trimRight(base, '/')}/${trimLeft(path, '/')}`;
|
||||
}
|
||||
|
||||
function extendCommaList(obj, property, value) {
|
||||
obj[property] = (obj[property] ? obj[property] + ',' : '') + value;
|
||||
}
|
||||
|
||||
function getProxyHeaders(req) {
|
||||
const headers = {};
|
||||
|
||||
if (req.info.remotePort && req.info.remoteAddress) {
|
||||
// see https://git.io/vytQ7
|
||||
extendCommaList(headers, 'x-forwarded-for', req.info.remoteAddress);
|
||||
extendCommaList(headers, 'x-forwarded-port', req.info.remotePort);
|
||||
extendCommaList(headers, 'x-forwarded-proto', req.connection.info.protocol);
|
||||
extendCommaList(headers, 'x-forwarded-host', req.info.host);
|
||||
}
|
||||
|
||||
const contentType = req.headers['content-type'];
|
||||
if (contentType) {
|
||||
headers['content-type'] = contentType;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
export const createProxyRoute = ({
|
||||
baseUrl = '/',
|
||||
pathFilters = [/.*/],
|
||||
getConfigForReq = () => ({}),
|
||||
}) => ({
|
||||
path: '/api/console/proxy',
|
||||
method: 'POST',
|
||||
config: {
|
||||
payload: {
|
||||
output: 'stream',
|
||||
parse: false
|
||||
},
|
||||
|
||||
validate: {
|
||||
query: Joi.object().keys({
|
||||
method: Joi.string()
|
||||
.valid('HEAD', 'GET', 'POST', 'PUT', 'DELETE')
|
||||
.insensitive()
|
||||
.required(),
|
||||
path: Joi.string().required()
|
||||
}).unknown(true),
|
||||
},
|
||||
|
||||
pre: [
|
||||
function filterPath(req, reply) {
|
||||
const { path } = req.query;
|
||||
|
||||
if (!pathFilters.some(re => re.test(path))) {
|
||||
const err = Boom.forbidden();
|
||||
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`;
|
||||
err.output.headers['content-type'] = 'text/plain';
|
||||
reply(err);
|
||||
} else {
|
||||
reply();
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
handler(req, reply) {
|
||||
const { payload, query } = req;
|
||||
const { path, method } = query;
|
||||
const uri = resolveUri(baseUrl, path);
|
||||
|
||||
const {
|
||||
timeout,
|
||||
rejectUnauthorized,
|
||||
agent,
|
||||
headers,
|
||||
} = getConfigForReq(req, uri);
|
||||
|
||||
const wreckOptions = {
|
||||
payload,
|
||||
timeout,
|
||||
rejectUnauthorized,
|
||||
agent,
|
||||
headers: {
|
||||
...headers,
|
||||
...getProxyHeaders(req)
|
||||
},
|
||||
};
|
||||
|
||||
Wreck.request(method, uri, wreckOptions, (err, esResponse) => {
|
||||
if (err) {
|
||||
return reply(err);
|
||||
}
|
||||
|
||||
if (method.toUpperCase() !== 'HEAD') {
|
||||
reply(esResponse)
|
||||
.code(esResponse.statusCode)
|
||||
.header('warning', esResponse.headers.warning);
|
||||
return;
|
||||
}
|
||||
|
||||
reply(`${esResponse.statusCode} - ${esResponse.statusMessage}`)
|
||||
.code(esResponse.statusCode)
|
||||
.type('text/plain')
|
||||
.header('warning', esResponse.headers.warning);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue