Skip to content
Closed
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
4 changes: 4 additions & 0 deletions config/kibana.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
# This must be > 0
# elasticsearch.requestTimeout: 30000

# List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side
# headers, set this value to [] (an empty list).
# elasticsearch.requestHeadersWhitelist: [ authorization ]

# Time in milliseconds for Elasticsearch to wait for responses from shards.
# Set to 0 to disable.
# elasticsearch.shardTimeout: 0
Expand Down
4 changes: 4 additions & 0 deletions docs/settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ deprecated[4.2, The names of several Kibana server properties changed in the 4.2
+
*default*: `500000`

`elasticsearch.requestHeadersWhitelist:` added[4.6]:: List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side headers, set this value to [] (an empty list).
+
*default*: `[ 'authorization' ]`

`elasticsearch.shardTimeout` added[4.2]:: How long Elasticsearch should wait for responses from shards. Set to 0 to disable.
+
*alias*: `shard_timeout` deprecated[4.2]
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/elasticsearch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import healthCheck from './lib/health_check';
import exposeClient from './lib/expose_client';
import createProxy, { createPath } from './lib/create_proxy';

const DEFAULT_REQUEST_HEADERS = [ 'authorization' ];

module.exports = function ({ Plugin }) {
return new Plugin({
require: ['kibana'],
Expand All @@ -20,6 +22,7 @@ module.exports = function ({ Plugin }) {
password: string(),
shardTimeout: number().default(0),
requestTimeout: number().default(30000),
requestHeadersWhitelist: array().items().single().default(DEFAULT_REQUEST_HEADERS),
pingTimeout: number().default(30000),
startupTimeout: number().default(5000),
ssl: object({
Expand Down
76 changes: 76 additions & 0 deletions src/plugins/elasticsearch/lib/__tests__/map_uri.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import expect from 'expect.js';
import mapUri from '../map_uri';
import sinon from 'sinon';

describe('plugins/elasticsearch', function () {
describe('lib/map_uri', function () {

let request;

beforeEach(function () {
request = {
path: '/elasticsearch/some/path',
headers: {
cookie: 'some_cookie_string',
'accept-encoding': 'gzip, deflate',
origin: 'https://localhost:5601',
'content-type': 'application/json',
'x-my-custom-header': '42',
accept: 'application/json, text/plain, */*',
authorization: '2343d322eda344390fdw42'
}
};
});

it('only sends the whitelisted request headers', function () {

const get = sinon.stub()
.withArgs('elasticsearch.url').returns('http://foobar:9200')
.withArgs('elasticsearch.requestHeadersWhitelist').returns(['x-my-custom-HEADER', 'Authorization']);
const config = function () { return { get: get }; };
const server = {
config: config
};

mapUri(server)(request, function (err, upstreamUri, upstreamHeaders) {
expect(err).to.be(null);
expect(upstreamHeaders).to.have.property('authorization');
expect(upstreamHeaders).to.have.property('x-my-custom-header');
expect(Object.keys(upstreamHeaders).length).to.be(2);
});
});

it('sends no headers if whitelist is set to []', function () {

const get = sinon.stub()
.withArgs('elasticsearch.url').returns('http://foobar:9200')
.withArgs('elasticsearch.requestHeadersWhitelist').returns([]);
const config = function () { return { get: get }; };
const server = {
config: config
};

mapUri(server)(request, function (err, upstreamUri, upstreamHeaders) {
expect(err).to.be(null);
expect(Object.keys(upstreamHeaders).length).to.be(0);
});
});

it('sends no headers if whitelist is set to no value', function () {

const get = sinon.stub()
.withArgs('elasticsearch.url').returns('http://foobar:9200')
.withArgs('elasticsearch.requestHeadersWhitelist').returns([ null ]); // This is how Joi returns it
const config = function () { return { get: get }; };
const server = {
config: config
};

mapUri(server)(request, function (err, upstreamUri, upstreamHeaders) {
expect(err).to.be(null);
expect(Object.keys(upstreamHeaders).length).to.be(0);
});
});

});
});
10 changes: 6 additions & 4 deletions src/plugins/elasticsearch/lib/call_with_request.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ const Promise = require('bluebird');
const Boom = require('boom');
const getBasicAuthRealm = require('./get_basic_auth_realm');
const toPath = require('lodash/internal/toPath');
const filterHeaders = require('./filter_headers');

module.exports = (client) => {
module.exports = (server, client) => {
return (req, endpoint, params = {}) => {
if (req.headers.authorization) {
_.set(params, 'headers.authorization', req.headers.authorization);
}
const filteredHeaders = filterHeaders(req.headers, server.config().get('elasticsearch.requestHeadersWhitelist'));
_.set(params, 'headers', filteredHeaders);
const path = toPath(endpoint);
const api = _.get(client, path);
let apiContext = _.get(client, path.slice(0, -1));
if (_.isEmpty(apiContext)) {
apiContext = client;
}

if (!api) throw new Error(`callWithRequest called with an invalid endpoint: ${endpoint}`);

return api.call(apiContext, params)
.catch((err) => {
if (err.status === 401) {
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/elasticsearch/lib/create_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ function createProxy(server, method, route, config) {
handler: {
proxy: {
mapUri: mapUri(server),
passThrough: true,
agent: createAgent(server),
xforward: true,
timeout: server.config().get('elasticsearch.requestTimeout')
timeout: server.config().get('elasticsearch.requestTimeout'),
onResponse: function (err, responseFromUpstream, request, reply) {
reply(err, responseFromUpstream);
}
}
},
};
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/elasticsearch/lib/expose_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const readFile = (file) => require('fs').readFileSync(file, 'utf8');
const util = require('util');
const url = require('url');
const callWithRequest = require('./call_with_request');
const filterHeaders = require('./filter_headers');

module.exports = function (server) {
const config = server.config();
Expand Down Expand Up @@ -71,8 +72,9 @@ module.exports = function (server) {

server.expose('client', client);
server.expose('createClient', createClient);
server.expose('callWithRequestFactory', callWithRequest);
server.expose('callWithRequest', callWithRequest(noAuthClient));
server.expose('callWithRequestFactory', _.partial(callWithRequest, server));
server.expose('callWithRequest', callWithRequest(server, noAuthClient));
server.expose('filterHeaders', filterHeaders);
server.expose('errors', elasticsearch.errors);

return client;
Expand Down
22 changes: 22 additions & 0 deletions src/plugins/elasticsearch/lib/filter_headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const _ = require('lodash');

module.exports = function (originalHeaders, headersToKeep) {

const normalizeHeader = function (header) {
if (!header) {
return '';
}
header = header.toString();
return header.trim().toLowerCase();
};

// Normalize list of headers we want to allow in upstream request
const headersToKeepNormalized = headersToKeep.map(normalizeHeader);

// Normalize original headers in request
const originalHeadersNormalized = _.mapKeys(originalHeaders, function (headerValue, headerName) {
return normalizeHeader(headerName);
});

return _.pick(originalHeaders, headersToKeepNormalized);
};
5 changes: 4 additions & 1 deletion src/plugins/elasticsearch/lib/map_uri.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const querystring = require('querystring');
const resolve = require('url').resolve;
const filterHeaders = require('./filter_headers');

module.exports = function mapUri(server, prefix) {
const config = server.config();
return function (request, done) {
Expand All @@ -11,6 +13,7 @@ module.exports = function mapUri(server, prefix) {
}
const query = querystring.stringify(request.query);
if (query) url += '?' + query;
done(null, url);
const filteredHeaders = filterHeaders(request.headers, server.config().get('elasticsearch.requestHeadersWhitelist'));
done(null, url, filteredHeaders);
};
};