Context
I am embedding a Kibana 4 dashboard at several places in a web app I am working on using Node.js and Express. The embedding is context-dependent, meaning that depending on where the dashboard is embedded, the query to Elasticsearch changes.
Moreover, some pages where dashboards are embedded are limited to some users who have the right permissions.
As there is no authentication implemented yet in Kibana 4, by changing the query in the dashboard embed URL, any user could show the dashboard of a page they don't have access.
Our solution
The idea is to block any external connection to Kibana (port 5601) and to have a custom URL going through our web app and check the user permissions (kind of a proxy). And if the user has the permission, then our web app forward the HTTP queries to Kibana. As our web app and Kibana are on the same server, these queries are not blocked.
To route the custom URL and check the permission, we have the following:
app.route('/items/:itemId/analytics/*')
.all(api.requiresAccess('user'),
api.requiresPermissions('item', 'modify'),
items.getAnalytics);
Then we use Request in the getAnalytics
function:
exports.getAnalytics = function (req, res) {
var itemId = req.item.id;
var kibanaParamsRegexp = /analytics\/(.*)/;
var kibanaParamsMatch = kibanaParamsRegexp.exec(req.originalUrl);
var kibanaParams = kibanaParamsMatch[1];
var method = req.method;
var dashboardId = 'Standard-Dashboard';
var kibanaDashboardURL = 'http://localhost:5601';
if (kibanaParams === '') { // First request to get the dashboard
kibanaDashboardURL += '/#/dashboard/' + dashboardId + '?embed' +
'&_a=(query:(query_string:(analyze_wildcard:!t,query:\'path:' + itemId + '\')))' +
'&_g=(time:(from:now-1y,mode:quick,to:now))';
} else { // Following requests to get the dashboard
kibanaDashboardURL += '/' + kibanaParams;
}
app.logger.info('Received from the dashboard: ' + req.originalUrl);
app.logger.info('Requesting to Kibana: ' + kibanaDashboardURL);
app.logger.info('Query body: ' + JSON.stringify(req.body));
if (method === 'POST') {
var options = {
url: kibanaDashboardURL,
json: true,
body: req.body
};
request.post(options,
function (err, httpResponse, body) {
if (err) {
return console.log('POST failed: ', err);
}
console.log('POST successful. Sever response: ', body);
}).pipe(res);
} else if (method === 'GET' || method === 'HEAD') {
request.get(kibanaDashboardURL).pipe(res);
}
};
Therefore, instead of http://localhost:5601/#/dashboard/Standard-dashboard?embed&_a=(query:(query_string:(analyze_wildcard:!t,query:'path: 5539e831b5b79bf9f4b06a3c')))&_g=(time:(from:now-1y,mode:quick,to:now))
,
I have to use http://localhost:8000/items/5539e831b5b79bf9f4b06a3c/analytics/
which checks the permissions.
The problem
The first requests, which are mainly GET
s and a few POST
s, are passing without problem. But at some point I have a POST
request that receives back a 500 Internal Sever error in response to the request, which stops the dashboard loading.
To try to find the issue I compared the network traffic (in the web inspector) between the load of the dashboard with the original Kibana URL and the redirected URL through the web app. The beginning is identical, but at some point (before the request receiving a 500 error back), the dashboard load through the original URL makes a request that is not done with the redirected URL. The request is a POST
to http://localhost:5601/elasticsearch/_mget?timeout=0&ignore_unavailable=true&preference=1434374357241
with the following body:{"docs":[{"_index":".kibana","_type":"dashboard","_id":"Standard-Dashboard"}]}
.
This request seem quite important as it contains the dashboard id and I suppose that its absence is what generate the 500 error later on. But I don't know why this request is not sent when I redirect the queries through the web app.
I also have tried with the modules http-proxy and express-http-proxy but I am facing the same issues.