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

[Console] Proxy fallback #50185

Merged
merged 10 commits into from
Nov 27, 2019
2 changes: 1 addition & 1 deletion src/legacy/core_plugins/console/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default function(kibana: any) {

server.route(
createProxyRoute({
baseUrl: head(legacyEsConfig.hosts),
hosts: legacyEsConfig.hosts,
pathFilters: proxyPathFilters,
getConfigForReq(req: any, uri: any) {
const filteredHeaders = filterHeaders(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Console Proxy Route', () => {
const server = new Server();
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('Console Proxy Route', () => {
const server = new Server();
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('Console Proxy Route', () => {
const { server } = setup();
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
pathFilters: [/^\/foo\//, /^\/bar\//],
})
);
Expand All @@ -91,7 +91,7 @@ describe('Console Proxy Route', () => {
const { server } = setup();
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
pathFilters: [/^\/foo\//, /^\/bar\//],
})
);
Expand All @@ -113,7 +113,7 @@ describe('Console Proxy Route', () => {

const getConfigForReq = sinon.stub().returns({});

server.route(createProxyRoute({ baseUrl: 'http://localhost:9200', getConfigForReq }));
server.route(createProxyRoute({ hosts: ['http://localhost:9200'], getConfigForReq }));
await server.inject({
method: 'POST',
url: '/api/console/proxy?method=HEAD&path=/index/id',
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('Console Proxy Route', () => {

server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
getConfigForReq: () => ({
timeout,
agent,
Expand All @@ -166,19 +166,5 @@ describe('Console Proxy Route', () => {
expect(opts.headers).to.have.property('baz', 'bop');
});
});

describe('baseUrl', () => {
describe('default', () => {
it('ensures that the path starts with a /');
});
describe('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`);
});
describe(`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`);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('Console Proxy Route', () => {
const server = new Server();
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
hosts: ['http://localhost:9200'],
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
*/

import Joi from 'joi';
import * as url from 'url';
import { IncomingMessage } from 'http';
import Boom from 'boom';
import { trimLeft, trimRight } from 'lodash';
import { sendRequest } from './request';
import * as url from 'url';

function toURL(base, path) {
function toURL(base: string, path: string) {
const urlResult = new url.URL(`${trimRight(base, '/')}/${trimLeft(path, '/')}`);
// Appending pretty here to have Elasticsearch do the JSON formatting, as doing
// in JS can lead to data loss (7.0 will get munged into 7, thus losing indication of
Expand All @@ -34,11 +35,11 @@ function toURL(base, path) {
return urlResult;
}

function getProxyHeaders(req) {
function getProxyHeaders(req: any) {
const headers = Object.create(null);

// Scope this proto-unsafe functionality to where it is being used.
function extendCommaList(obj, property, value) {
function extendCommaList(obj: Record<string, any>, property: string, value: any) {
obj[property] = (obj[property] ? obj[property] + ',' : '') + value;
}

Expand All @@ -58,9 +59,13 @@ function getProxyHeaders(req) {
}

export const createProxyRoute = ({
baseUrl = '/',
hosts,
pathFilters = [/.*/],
getConfigForReq = () => ({}),
}: {
hosts: string[];
pathFilters: RegExp[];
getConfigForReq: (...args: any[]) => any;
}) => ({
path: '/api/console/proxy',
method: 'POST',
Expand All @@ -84,63 +89,82 @@ export const createProxyRoute = ({
},

pre: [
function filterPath(req) {
function filterPath(req: any) {
const { path } = req.query;

if (pathFilters.some(re => re.test(path))) {
return null;
}

const err = Boom.forbidden();
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`;
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.` as any;
err.output.headers['content-type'] = 'text/plain';
throw err;
},
],

handler: async (req, h) => {
handler: async (req: any, h: any) => {
const { payload, query } = req;
const { path, method } = query;
const uri = toURL(baseUrl, path);

// Because this can technically be provided by a settings-defined proxy config, we need to
// preserve these property names to maintain BWC.
const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(req, uri.toString());

const requestHeaders = {
...headers,
...getProxyHeaders(req),
};

const esIncomingMessage = await sendRequest({
method,
headers: requestHeaders,
uri,
timeout,
payload,
rejectUnauthorized,
agent,
});

let esIncomingMessage: IncomingMessage;

for (let idx = 0; idx < hosts.length; ++idx) {
const host = hosts[idx];
try {
const uri = toURL(host, path);

// Because this can technically be provided by a settings-defined proxy config, we need to
// preserve these property names to maintain BWC.
const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(
req,
uri.toString()
);

const requestHeaders = {
...headers,
...getProxyHeaders(req),
};

esIncomingMessage = await sendRequest({
method,
headers: requestHeaders,
uri,
timeout,
payload,
rejectUnauthorized,
agent,
});

break;
} catch (e) {
if (e.code !== 'ECONNREFUSED') {
throw Boom.boomify(e);
}
if (idx === hosts.length - 1) {
throw Boom.badGateway('Could not reach any configured nodes.');
}
// Otherwise, try the next host...
}
}

const {
statusCode,
statusMessage,
headers: responseHeaders,
} = esIncomingMessage;

const { warning } = responseHeaders;
headers: { warning },
} = esIncomingMessage!;

if (method.toUpperCase() !== 'HEAD') {
return h
.response(esIncomingMessage)
.response(esIncomingMessage!)
.code(statusCode)
.header('warning', warning);
.header('warning', warning!);
} else {
return h
.response(`${statusCode} - ${statusMessage}`)
.code(statusCode)
.type('text/plain')
.header('warning', warning);
.header('warning', warning!);
}
},
},
Expand Down
4 changes: 2 additions & 2 deletions src/legacy/core_plugins/console/server/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { fail } from 'assert';

describe(`Console's send request`, () => {
let sandbox: sinon.SinonSandbox;
let stub: sinon.SinonStub<Parameters<typeof http['request']>, ClientRequest>;
let stub: sinon.SinonStub<Parameters<typeof http.request>, ClientRequest>;
let fakeRequest: http.ClientRequest;

beforeEach(() => {
Expand Down Expand Up @@ -52,7 +52,7 @@ describe(`Console's send request`, () => {
method: 'get',
payload: null as any,
timeout: 0, // immediately timeout
uri: new URL('http://noone.nowhere.com'),
uri: new URL('http://noone.nowhere.none'),
});
fail('Should not reach here!');
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions src/legacy/core_plugins/console/server/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const sendRequest = ({
}
});

const onError = () => reject();
const onError = (e: Error) => reject(e);
req.once('error', onError);

const timeoutPromise = new Promise<any>((timeoutResolve, timeoutReject) => {
Expand All @@ -103,5 +103,5 @@ export const sendRequest = ({
}, timeout);
});

return Promise.race<http.ServerResponse>([reqPromise, timeoutPromise]);
return Promise.race<http.IncomingMessage>([reqPromise, timeoutPromise]);
};