Fix problems with multitenancy

This commit fixes the following problems:
- Kibana is not auto-detected by monasca-agent (Bug-Id: 13118)
- Kibana renders error page when token expires
- Keystone-token can be 'stolen' due to sharing
   of default session in Kibana
- Some endpoints were not secured

Related change for monasca-ui: https://review.openstack.org/#/c/387269/
Bug-Id: 13118


Change-Id: I2a363ebabcf593469943f7d071a92a680344ec73
This commit is contained in:
Jakub Wachowski 2016-10-24 15:56:49 +02:00
parent aa1b40a94d
commit 4d13e5d03b
8 changed files with 48 additions and 18 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "fts-keystone", "name": "fts-keystone",
"version": "0.0.3", "version": "0.0.4",
"description": "Keystone authentication & multitenancy support for Kibana 4.4.x", "description": "Keystone authentication & multitenancy support for Kibana 4.4.x",
"author": "Fujitsu Enabling Software Technology GmbH", "author": "Fujitsu Enabling Software Technology GmbH",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -27,13 +27,22 @@ describe('plugins/fts-keystone', ()=> {
let server; let server;
beforeEach(()=> { beforeEach(()=> {
let configGet = sinon.stub();
configGet.withArgs('fts-keystone.cookie.name').returns('keystone');
server = { server = {
log: sinon.stub() log: sinon.stub(),
config: function () {
return {
get: configGet
};
}
}; };
}); });
it('should return isBoom if session not available', ()=> { it('should return isBoom if session not available', ()=> {
let request = {}; let request = {
state: {}
};
let errMsg = /Session support is missing/; let errMsg = /Session support is missing/;
chai.expect(()=> { chai.expect(()=> {
@ -41,14 +50,16 @@ describe('plugins/fts-keystone', ()=> {
}).to.throw(errMsg); }).to.throw(errMsg);
request = { request = {
yar: undefined yar: undefined,
state: {}
}; };
chai.expect(()=> { chai.expect(()=> {
retrieveToken(server, request); retrieveToken(server, request);
}).to.throw(errMsg); }).to.throw(errMsg);
request = { request = {
session: null session: null,
state: {}
}; };
chai.expect(()=> { chai.expect(()=> {
retrieveToken(server, request); retrieveToken(server, request);
@ -62,9 +73,11 @@ describe('plugins/fts-keystone', ()=> {
'get': sinon 'get': sinon
.stub() .stub()
.withArgs(CONSTANTS.SESSION_TOKEN_KEY) .withArgs(CONSTANTS.SESSION_TOKEN_KEY)
.returns(undefined) .returns(undefined),
'reset': sinon.stub()
}, },
headers: {} headers: {},
state: {}
}; };
let result = retrieveToken(server, request); let result = retrieveToken(server, request);
@ -82,7 +95,8 @@ describe('plugins/fts-keystone', ()=> {
}; };
let request = { let request = {
yar : yar, yar : yar,
headers: {} headers: {},
state: {}
}; };
let token; let token;
@ -102,7 +116,7 @@ describe('plugins/fts-keystone', ()=> {
it('should set token in session if not there and request has it', () => { it('should set token in session if not there and request has it', () => {
let expectedToken = 'SOME_RANDOM_TOKEN'; let expectedToken = 'SOME_RANDOM_TOKEN';
let yar = { let yar = {
'reset': sinon.spy(), 'reset': sinon.stub(),
'set' : sinon.spy(), 'set' : sinon.spy(),
'get' : sinon.stub() 'get' : sinon.stub()
}; };
@ -110,7 +124,8 @@ describe('plugins/fts-keystone', ()=> {
yar : yar, yar : yar,
headers: { headers: {
'x-auth-token': expectedToken 'x-auth-token': expectedToken
} },
state: {}
}; };
let token; let token;
@ -158,14 +173,15 @@ describe('plugins/fts-keystone', ()=> {
let token; let token;
let request = { let request = {
yar : yar, yar : yar,
headers: headers headers: headers,
state: {}
}; };
token = retrieveToken(server, request); token = retrieveToken(server, request);
chai.expect(token).to.not.be.undefined; chai.expect(token).to.not.be.undefined;
chai.expect(token).to.be.eql(RELOAD_SYMBOL); chai.expect(token).to.be.eql(RELOAD_SYMBOL);
chai.expect(yar.reset.calledOnce).to.be.ok; chai.expect(yar.reset.calledTwice).to.be.ok;
chai.expect(yar.get.calledOnce).to.be.ok; chai.expect(yar.get.calledOnce).to.be.ok;
chai.expect(yar.set.callCount).to.be.eq(2); chai.expect(yar.set.callCount).to.be.eq(2);

View File

@ -43,6 +43,14 @@ module.exports = (server, request) => {
throw new Error('Session support is missing'); throw new Error('Session support is missing');
} }
// this is a workaround for problem with 'default' session:
// when there is no session cookie present, then yar uses default session,
// as a result many clients use the same session - security risk!
const cookieName = server.config().get('fts-keystone.cookie.name');
if (!request.state[cookieName]) {
request.yar.reset();
}
// DEV PURPOSE ONLY // DEV PURPOSE ONLY
// request.yar.set(SESSION_TOKEN_KEY, 'a60e832483c34526a0c2bc3c6f8fa320'); // request.yar.set(SESSION_TOKEN_KEY, 'a60e832483c34526a0c2bc3c6f8fa320');

View File

@ -42,7 +42,7 @@ export default () => {
if (diff >= 0) { if (diff >= 0) {
session.reset(); session.reset();
return reply(Boom.unauthorized('User token has expired')).takeover(); return reply(Boom.unauthorized('User token has expired'));
} else { } else {
return reply.continue({ return reply.continue({
credentials: userObj, credentials: userObj,

View File

@ -46,7 +46,7 @@ function bindAuthScheme(server) {
server.auth.strategy( server.auth.strategy(
'session', 'session',
'keystone-token', 'keystone-token',
true, false,
require('./auth/strategy')(server) require('./auth/strategy')(server)
) )
]); ]);

View File

@ -27,7 +27,7 @@ export default function (server, method, path) {
method : method, method : method,
path : path, path : path,
config : { config : {
auth : false, auth : 'session',
payload: { payload: {
output: 'data', output: 'data',
parse : false parse : false

View File

@ -26,7 +26,7 @@ export default function (server, method, path) {
method : method, method : method,
path : path, path : path,
config : { config : {
auth : false, auth : 'session',
payload: { payload: {
output: 'data', output: 'data',
parse : false parse : false

View File

@ -13,6 +13,7 @@
*/ */
import Wreck from 'wreck'; import Wreck from 'wreck';
import Boom from 'boom';
import { SESSION_USER_KEY } from '../../../const'; import { SESSION_USER_KEY } from '../../../const';
import { getOpts } from '../_utils'; import { getOpts } from '../_utils';
@ -20,14 +21,15 @@ import kibanaIndex from '../../kibana/kibanaIndex';
import mapUri from '../_map_uri'; import mapUri from '../_map_uri';
export default function (server, method, path) { export default function (server, method, path) {
const defaultKibanaIndex = defaultKibanaIndex; const defaultKibanaIndex = server.config().get('kibana.index');
const logIndexPostionInUrl = 3;
return { return {
method : method, method : method,
path : path, path : path,
config : { config : {
tags: ['elasticsearch', 'multitenancy'], tags: ['elasticsearch', 'multitenancy'],
auth: false auth: 'session'
}, },
handler: handler handler: handler
}; };
@ -42,7 +44,11 @@ export default function (server, method, path) {
if (indexPos > -1) { if (indexPos > -1) {
url[indexPos] = kibanaIndex(server, session[SESSION_USER_KEY]); url[indexPos] = kibanaIndex(server, session[SESSION_USER_KEY]);
kibanaIndexRequest = true; kibanaIndexRequest = true;
} else if (url.length > logIndexPostionInUrl
&& !url[logIndexPostionInUrl].startsWith(session[SESSION_USER_KEY].project.id)) {
return reply(Boom.unauthorized('User does not have access to this resource'));
} }
url = url.join('/'); url = url.join('/');
const opts = getOpts(server, request, url); const opts = getOpts(server, request, url);