[6.8] Make SameSite cookie's attribute configurable (#68108) (#68993)

* add SameSite:None support

* add docs

* Update docs/settings/security-settings.asciidoc

Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>

Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Mikhail Shustov 2020-06-15 16:33:20 +03:00 committed by GitHub
parent 335d2a630b
commit 293d6de10c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 79 additions and 1 deletions

View file

@ -50,6 +50,11 @@ is set to `true` if `server.ssl.certificate` and `server.ssl.key` are set. Set
this to `true` if SSL is configured outside of {kib} (for example, you are
routing requests through a load balancer or proxy).
`xpack.security.sameSiteCookies`::
Sets the `SameSite` attribute of the session cookie. This allows you to declare whether your cookie should be restricted to a first-party or same-site context.
Valid values are `Strict`, `Lax`, `None`.
This is *not set* by default, which modern browsers will treat as `Lax`. If you use {kib} embedded in an iframe in modern browsers, you might need to set it to `None`. Setting this value to `None` requires cookies to be sent over a secure connection by setting `xpack.security.secureCookies: true`. Some old versions of IE11 do not support `SameSite: None`.
`xpack.security.sessionTimeout`::
Sets the session duration (in milliseconds). By default, sessions stay active
until the browser is closed. When this is set to an explicit timeout, closing the

View file

@ -38,6 +38,7 @@ export const security = (kibana) => new kibana.Plugin({
encryptionKey: Joi.string(),
sessionTimeout: Joi.number().allow(null).default(null),
secureCookies: Joi.boolean().default(false),
sameSiteCookies: Joi.string().valid(['Strict', 'Lax', 'None']).optional(),
loginAssistanceMessage: Joi.string(),
public: Joi.object({
protocol: Joi.string().valid(['http', 'https']),

View file

@ -50,6 +50,58 @@ describe('Session', () => {
path: 'base/path/'
});
});
it('throws an exception if "SameSite: None" set on not Secure connection', async () => {
config.get.withArgs('xpack.security.secureCookies').returns(false);
config.get.withArgs('xpack.security.sameSiteCookies').returns('None');
try {
await Session.create(server);
expect().fail('`Session.create` should fail.');
} catch(err) {
expect(err).to.be.a(Error);
expect(err.message).to.be('"SameSite: None" requires Secure connection');
}
});
it('sets isSameSite:false when sameSiteCookies: None', async () => {
config.get.withArgs('xpack.security.cookieName').returns('cookie-name');
config.get.withArgs('xpack.security.encryptionKey').returns('encryption-key');
config.get.withArgs('server.basePath').returns('base/path');
config.get.withArgs('xpack.security.secureCookies').returns(true);
config.get.withArgs('xpack.security.sameSiteCookies').returns('None');
await Session.create(server);
sinon.assert.calledOnce(server.auth.strategy);
sinon.assert.calledWith(server.auth.strategy, 'security-cookie', 'cookie', sinon.match.has('isSameSite', false));
});
it('sets isSameSite:Lax when sameSiteCookies: Lax', async () => {
config.get.withArgs('xpack.security.cookieName').returns('cookie-name');
config.get.withArgs('xpack.security.encryptionKey').returns('encryption-key');
config.get.withArgs('server.basePath').returns('base/path');
config.get.withArgs('xpack.security.secureCookies').returns(true);
config.get.withArgs('xpack.security.sameSiteCookies').returns('Lax');
await Session.create(server);
sinon.assert.calledOnce(server.auth.strategy);
sinon.assert.calledWith(server.auth.strategy, 'security-cookie', 'cookie', sinon.match.has('isSameSite', 'Lax'));
});
it('sets isSameSite:Strict when sameSiteCookies: Strict', async () => {
config.get.withArgs('xpack.security.cookieName').returns('cookie-name');
config.get.withArgs('xpack.security.encryptionKey').returns('encryption-key');
config.get.withArgs('server.basePath').returns('base/path');
config.get.withArgs('xpack.security.secureCookies').returns(true);
config.get.withArgs('xpack.security.sameSiteCookies').returns('Strict');
await Session.create(server);
sinon.assert.calledOnce(server.auth.strategy);
sinon.assert.calledWith(server.auth.strategy, 'security-cookie', 'cookie', sinon.match.has('isSameSite', 'Strict'));
});
});
describe('`get` method', () => {

View file

@ -5,6 +5,8 @@
*/
import hapiAuthCookie from 'hapi-auth-cookie';
// eslint-disable-next-line import/no-extraneous-dependencies
import Statehood from 'statehood';
const HAPI_STRATEGY_NAME = 'security-cookie';
// Forbid applying of Hapi authentication strategies to routes automatically.
@ -121,6 +123,11 @@ export class Session {
const password = config.get('xpack.security.encryptionKey');
const path = `${config.get('server.basePath')}/`;
const secure = config.get('xpack.security.secureCookies');
const sameSiteCookies = config.get('xpack.security.sameSiteCookies');
if (sameSiteCookies === 'None' && secure !== true) {
throw new Error('"SameSite: None" requires Secure connection');
}
server.auth.strategy(HAPI_STRATEGY_NAME, 'cookie', {
cookie: name,
@ -129,10 +136,23 @@ export class Session {
validateFunc: Session._validateCookie,
isHttpOnly: httpOnly,
isSecure: secure,
isSameSite: false,
isSameSite: sameSiteCookies === 'None' ? false : sameSiteCookies || false,
path: path,
});
// A hack to support SameSite: 'None'.
// Remove it after update Hapi to v19 that supports SameSite: 'None' out of the box.
if (sameSiteCookies === 'None') {
server.log(['debug', 'security', 'auth', 'session'], 'Patching Statehood.prepareValue');
const originalPrepareValue = Statehood.prepareValue;
Statehood.prepareValue = function kibanaStatehoodPrepareValueWrapper(key, value, options) {
if (key === name) {
options.isSameSite = 'None';
}
return originalPrepareValue(key, value, options);
};
}
if (HAPI_STRATEGY_MODE) {
server.auth.default({
strategy: HAPI_STRATEGY_NAME,