Skip to content

Commit

Permalink
[6.8] Make SameSite cookie's attribute configurable (#68108) (#68993)
Browse files Browse the repository at this point in the history
* add SameSite:None support

* add docs

* Update docs/settings/security-settings.asciidoc

Co-authored-by: Aleh Zasypkin <[email protected]>

Co-authored-by: Aleh Zasypkin <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored Jun 15, 2020
1 parent 335d2a6 commit 293d6de
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs/settings/security-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
22 changes: 21 additions & 1 deletion x-pack/plugins/security/server/lib/authentication/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit 293d6de

Please sign in to comment.