mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[server/xsrf] use the current version as the xsrf header, rather than a random value
This commit is contained in:
parent
5ee11c894b
commit
83d1cde0ed
7 changed files with 95 additions and 131 deletions
|
@ -43,7 +43,6 @@ module.exports = () => Joi.object({
|
|||
otherwise: Joi.boolean().default(false)
|
||||
}),
|
||||
xsrf: Joi.object({
|
||||
token: Joi.string().default(randomBytes(32).toString('hex')),
|
||||
disableProtection: Joi.boolean().default(false),
|
||||
}).default(),
|
||||
}).default(),
|
||||
|
|
|
@ -8,6 +8,9 @@ const nonDestructiveMethods = ['GET'];
|
|||
const destructiveMethods = ['POST', 'PUT', 'DELETE'];
|
||||
const src = resolve.bind(null, __dirname, '../../../../src');
|
||||
|
||||
const xsrfHeader = 'kbn-version';
|
||||
const version = require(src('../package.json')).version;
|
||||
|
||||
describe('xsrf request filter', function () {
|
||||
function inject(kbnServer, opts) {
|
||||
return fn(cb => {
|
||||
|
@ -17,9 +20,9 @@ describe('xsrf request filter', function () {
|
|||
});
|
||||
}
|
||||
|
||||
const makeServer = async function (token) {
|
||||
const makeServer = async function () {
|
||||
const kbnServer = new KbnServer({
|
||||
server: { autoListen: false, xsrf: { token } },
|
||||
server: { autoListen: false },
|
||||
plugins: { scanDirs: [src('plugins')] },
|
||||
logging: { quiet: true },
|
||||
optimize: { enabled: false },
|
||||
|
@ -41,108 +44,74 @@ describe('xsrf request filter', function () {
|
|||
return kbnServer;
|
||||
};
|
||||
|
||||
describe('issuing tokens', function () {
|
||||
const token = 'secur3';
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer(token));
|
||||
afterEach(async () => await kbnServer.close());
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer());
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
it('sends a token when rendering an app', async function () {
|
||||
var resp = await inject(kbnServer, {
|
||||
method: 'GET',
|
||||
url: '/app/kibana',
|
||||
for (const method of nonDestructiveMethods) {
|
||||
context(`nonDestructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
expect(resp.payload).to.contain(`"xsrfToken":"${token}"`);
|
||||
it('failes on invalid tokens', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: `invalid:${version}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.headers).to.have.property(xsrfHeader, version);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
context('without configured token', function () {
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer());
|
||||
afterEach(async () => await kbnServer.close());
|
||||
for (const method of destructiveMethods) {
|
||||
context(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests with the correct token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: version,
|
||||
},
|
||||
});
|
||||
|
||||
it('responds with a random token', async function () {
|
||||
var resp = await inject(kbnServer, {
|
||||
method: 'GET',
|
||||
url: '/app/kibana',
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
expect(resp.payload).to.match(/"xsrfToken":".{64}"/);
|
||||
it('rejects requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.payload).to.match(/"Missing kbn-version header/);
|
||||
});
|
||||
|
||||
it('rejects requests with an invalid token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
[xsrfHeader]: `invalid:${version}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(400);
|
||||
expect(resp.payload).to.match(/"Invalid kbn-version, expected/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('with configured token', function () {
|
||||
const token = 'mytoken';
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer(token));
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
for (const method of nonDestructiveMethods) {
|
||||
context(`nonDestructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('ignores invalid tokens', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': `invalid:${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.headers).to.not.have.property('kbn-xsrf-token');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const method of destructiveMethods) {
|
||||
context(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests with the correct token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': token,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('rejects requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(403);
|
||||
expect(resp.payload).to.match(/"Missing XSRF token"/);
|
||||
});
|
||||
|
||||
it('rejects requests with an invalid token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': `invalid:${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(403);
|
||||
expect(resp.payload).to.match(/"Invalid XSRF token"/);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -114,11 +114,11 @@ module.exports = function (kbnServer, server, config) {
|
|||
let response = req.response;
|
||||
|
||||
if (response.isBoom) {
|
||||
response.output.headers['x-app-name'] = kbnServer.name;
|
||||
response.output.headers['x-app-version'] = kbnServer.version;
|
||||
response.output.headers['kbn-name'] = kbnServer.name;
|
||||
response.output.headers['kbn-version'] = kbnServer.version;
|
||||
} else {
|
||||
response.header('x-app-name', kbnServer.name);
|
||||
response.header('x-app-version', kbnServer.version);
|
||||
response.header('kbn-name', kbnServer.name);
|
||||
response.header('kbn-version', kbnServer.version);
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { forbidden } from 'boom';
|
||||
import { badRequest } from 'boom';
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
const token = config.get('server.xsrf.token');
|
||||
const version = config.get('pkg.version');
|
||||
const disabled = config.get('server.xsrf.disableProtection');
|
||||
|
||||
server.decorate('reply', 'issueXsrfToken', function () {
|
||||
return token;
|
||||
});
|
||||
const header = 'kbn-version';
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
if (disabled || req.method === 'get') return reply.continue();
|
||||
const noHeaderGet = req.method === 'get' && !req.headers[header];
|
||||
if (disabled || noHeaderGet) return reply.continue();
|
||||
|
||||
const attempt = req.headers['kbn-xsrf-token'];
|
||||
if (!attempt) return reply(forbidden('Missing XSRF token'));
|
||||
if (attempt !== token) return reply(forbidden('Invalid XSRF token'));
|
||||
const submission = req.headers[header];
|
||||
if (!submission) return reply(badRequest(`Missing ${header} header`));
|
||||
if (submission !== version) return reply(badRequest(`Invalid ${header}, expected ${version}`, { version }));
|
||||
|
||||
return reply.continue();
|
||||
});
|
||||
|
|
|
@ -75,7 +75,6 @@ module.exports = async (kbnServer, server, config) => {
|
|||
buildSha: config.get('pkg.buildSha'),
|
||||
basePath: config.get('server.basePath'),
|
||||
vars: defaults(app.getInjectedVars(), defaultInjectedVars),
|
||||
xsrfToken: this.issueXsrfToken(),
|
||||
};
|
||||
|
||||
return this.view(app.templateName, {
|
||||
|
|
|
@ -5,43 +5,42 @@ import ngMock from 'ngMock';
|
|||
|
||||
import xsrfChromeApi from '../xsrf';
|
||||
|
||||
const xsrfHeader = 'kbn-xsrf-token';
|
||||
const xsrfToken = 'xsrfToken';
|
||||
const xsrfHeader = 'kbn-version';
|
||||
const { version } = require('../../../../../../package.json');
|
||||
|
||||
describe('chrome xsrf apis', function () {
|
||||
describe('#getXsrfToken()', function () {
|
||||
it('exposes the token', function () {
|
||||
const chrome = {};
|
||||
xsrfChromeApi(chrome, { xsrfToken });
|
||||
expect(chrome.getXsrfToken()).to.be(xsrfToken);
|
||||
xsrfChromeApi(chrome, { version });
|
||||
expect(chrome.getXsrfToken()).to.be(version);
|
||||
});
|
||||
});
|
||||
|
||||
context('jQuery support', function () {
|
||||
it('adds a global jQuery prefilter', function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
xsrfChromeApi({}, {});
|
||||
xsrfChromeApi({}, { version });
|
||||
expect($.ajaxPrefilter.callCount).to.be(1);
|
||||
});
|
||||
|
||||
context('jQuery prefilter', function () {
|
||||
let prefilter;
|
||||
const xsrfToken = 'xsrfToken';
|
||||
|
||||
beforeEach(function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
xsrfChromeApi({}, { xsrfToken });
|
||||
xsrfChromeApi({}, { version });
|
||||
prefilter = $.ajaxPrefilter.args[0][0];
|
||||
});
|
||||
|
||||
it('sets the kbn-xsrf-token header', function () {
|
||||
it(`sets the ${xsrfHeader} header`, function () {
|
||||
const setHeader = stub();
|
||||
prefilter({}, {}, { setRequestHeader: setHeader });
|
||||
|
||||
expect(setHeader.callCount).to.be(1);
|
||||
expect(setHeader.args[0]).to.eql([
|
||||
xsrfHeader,
|
||||
xsrfToken
|
||||
version
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -60,7 +59,7 @@ describe('chrome xsrf apis', function () {
|
|||
beforeEach(function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
const chrome = {};
|
||||
xsrfChromeApi(chrome, { xsrfToken });
|
||||
xsrfChromeApi(chrome, { version });
|
||||
ngMock.module(chrome.$setupXsrfRequestInterceptor);
|
||||
});
|
||||
|
||||
|
@ -78,9 +77,9 @@ describe('chrome xsrf apis', function () {
|
|||
$httpBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
it('injects a kbn-xsrf-token header on every request', function () {
|
||||
it(`injects a ${xsrfHeader} header on every request`, function () {
|
||||
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
|
||||
return headers[xsrfHeader] === xsrfToken;
|
||||
return headers[xsrfHeader] === version;
|
||||
}).respond(200, '');
|
||||
|
||||
$http.post('/api/test');
|
||||
|
@ -114,7 +113,7 @@ describe('chrome xsrf apis', function () {
|
|||
});
|
||||
|
||||
it('accepts alternate tokens to use', function () {
|
||||
const customToken = `custom:${xsrfToken}`;
|
||||
const customToken = `custom:${version}`;
|
||||
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
|
||||
return headers[xsrfHeader] === customToken;
|
||||
}).respond(200, '');
|
||||
|
|
|
@ -4,12 +4,12 @@ import { set } from 'lodash';
|
|||
export default function (chrome, internals) {
|
||||
|
||||
chrome.getXsrfToken = function () {
|
||||
return internals.xsrfToken;
|
||||
return internals.version;
|
||||
};
|
||||
|
||||
$.ajaxPrefilter(function ({ kbnXsrfToken = internals.xsrfToken }, originalOptions, jqXHR) {
|
||||
$.ajaxPrefilter(function ({ kbnXsrfToken = internals.version }, originalOptions, jqXHR) {
|
||||
if (kbnXsrfToken) {
|
||||
jqXHR.setRequestHeader('kbn-xsrf-token', kbnXsrfToken);
|
||||
jqXHR.setRequestHeader('kbn-version', kbnXsrfToken);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -17,9 +17,9 @@ export default function (chrome, internals) {
|
|||
$httpProvider.interceptors.push(function () {
|
||||
return {
|
||||
request: function (opts) {
|
||||
const { kbnXsrfToken = internals.xsrfToken } = opts;
|
||||
const { kbnXsrfToken = internals.version } = opts;
|
||||
if (kbnXsrfToken) {
|
||||
set(opts, ['headers', 'kbn-xsrf-token'], kbnXsrfToken);
|
||||
set(opts, ['headers', 'kbn-version'], kbnXsrfToken);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue