mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-09 00:15:13 -04:00
express-session: Enable key rotation
This commit is contained in:
parent
97a61bf633
commit
d7021884af
6 changed files with 70 additions and 7 deletions
|
@ -37,6 +37,11 @@
|
||||||
session expires (with some exceptions that will be fixed in the future).
|
session expires (with some exceptions that will be fixed in the future).
|
||||||
* Requests for static content (e.g., `/robots.txt`) and special pages (e.g.,
|
* Requests for static content (e.g., `/robots.txt`) and special pages (e.g.,
|
||||||
the HTTP API, `/stats`) no longer create login session state.
|
the HTTP API, `/stats`) no longer create login session state.
|
||||||
|
* The secret used to sign the `express_sid` cookie is now automatically
|
||||||
|
regenerated every day (called *key rotation*) by default. If key rotation is
|
||||||
|
enabled, the now-deprecated `SESSIONKEY.txt` file can be safely deleted
|
||||||
|
after Etherpad starts up (its content is read and saved to the database and
|
||||||
|
used to validate signatures from old cookies until they expire).
|
||||||
* The following settings from `settings.json` are now applied as expected (they
|
* The following settings from `settings.json` are now applied as expected (they
|
||||||
were unintentionally ignored before):
|
were unintentionally ignored before):
|
||||||
* `padOptions.lang`
|
* `padOptions.lang`
|
||||||
|
|
0
doc/docker.md
Normal file
0
doc/docker.md
Normal file
|
@ -363,6 +363,23 @@
|
||||||
* Settings controlling the session cookie issued by Etherpad.
|
* Settings controlling the session cookie issued by Etherpad.
|
||||||
*/
|
*/
|
||||||
"cookie": {
|
"cookie": {
|
||||||
|
/*
|
||||||
|
* How often (in milliseconds) the key used to sign the express_sid cookie
|
||||||
|
* should be rotated. Long rotation intervals reduce signature verification
|
||||||
|
* overhead (because there are fewer historical keys to check) and database
|
||||||
|
* load (fewer historical keys to store, and less frequent queries to
|
||||||
|
* get/update the keys). Short rotation intervals are slightly more secure.
|
||||||
|
*
|
||||||
|
* Multiple Etherpad processes sharing the same database (table) is
|
||||||
|
* supported as long as the clock sync error is significantly less than this
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* Key rotation can be disabled (not recommended) by setting this to 0 or
|
||||||
|
* null, or by disabling session expiration (see sessionLifetime).
|
||||||
|
*/
|
||||||
|
// 86400000 = 1d * 24h/d * 60m/h * 60s/m * 1000ms/s
|
||||||
|
"keyRotationInterval": "${COOKIE_KEY_ROTATION_INTERVAL:86400000}",
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Value of the SameSite cookie property. "Lax" is recommended unless
|
* Value of the SameSite cookie property. "Lax" is recommended unless
|
||||||
* Etherpad will be embedded in an iframe from another site, in which case
|
* Etherpad will be embedded in an iframe from another site, in which case
|
||||||
|
@ -392,6 +409,8 @@
|
||||||
* indefinitely without consulting authentication or authorization
|
* indefinitely without consulting authentication or authorization
|
||||||
* hooks, so once a user has accessed a pad, the user can continue to
|
* hooks, so once a user has accessed a pad, the user can continue to
|
||||||
* use the pad until the user leaves for longer than sessionLifetime.
|
* use the pad until the user leaves for longer than sessionLifetime.
|
||||||
|
* - More historical keys (sessionLifetime / keyRotationInterval) must be
|
||||||
|
* checked when verifying signatures.
|
||||||
*
|
*
|
||||||
* Session lifetime can be set to infinity (not recommended) by setting this
|
* Session lifetime can be set to infinity (not recommended) by setting this
|
||||||
* to null or 0. Note that if the session does not expire, most browsers
|
* to null or 0. Note that if the session does not expire, most browsers
|
||||||
|
|
|
@ -364,6 +364,22 @@
|
||||||
* Settings controlling the session cookie issued by Etherpad.
|
* Settings controlling the session cookie issued by Etherpad.
|
||||||
*/
|
*/
|
||||||
"cookie": {
|
"cookie": {
|
||||||
|
/*
|
||||||
|
* How often (in milliseconds) the key used to sign the express_sid cookie
|
||||||
|
* should be rotated. Long rotation intervals reduce signature verification
|
||||||
|
* overhead (because there are fewer historical keys to check) and database
|
||||||
|
* load (fewer historical keys to store, and less frequent queries to
|
||||||
|
* get/update the keys). Short rotation intervals are slightly more secure.
|
||||||
|
*
|
||||||
|
* Multiple Etherpad processes sharing the same database (table) is
|
||||||
|
* supported as long as the clock sync error is significantly less than this
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* Key rotation can be disabled (not recommended) by setting this to 0 or
|
||||||
|
* null, or by disabling session expiration (see sessionLifetime).
|
||||||
|
*/
|
||||||
|
"keyRotationInterval": 86400000, // = 1d * 24h/d * 60m/h * 60s/m * 1000ms/s
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Value of the SameSite cookie property. "Lax" is recommended unless
|
* Value of the SameSite cookie property. "Lax" is recommended unless
|
||||||
* Etherpad will be embedded in an iframe from another site, in which case
|
* Etherpad will be embedded in an iframe from another site, in which case
|
||||||
|
@ -393,6 +409,8 @@
|
||||||
* indefinitely without consulting authentication or authorization
|
* indefinitely without consulting authentication or authorization
|
||||||
* hooks, so once a user has accessed a pad, the user can continue to
|
* hooks, so once a user has accessed a pad, the user can continue to
|
||||||
* use the pad until the user leaves for longer than sessionLifetime.
|
* use the pad until the user leaves for longer than sessionLifetime.
|
||||||
|
* - More historical keys (sessionLifetime / keyRotationInterval) must be
|
||||||
|
* checked when verifying signatures.
|
||||||
*
|
*
|
||||||
* Session lifetime can be set to infinity (not recommended) by setting this
|
* Session lifetime can be set to infinity (not recommended) by setting this
|
||||||
* to null or 0. Note that if the session does not expire, most browsers
|
* to null or 0. Note that if the session does not expire, most browsers
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
|
const SecretRotator = require('../utils/SecretRotator');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const events = require('events');
|
const events = require('events');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
@ -14,6 +15,7 @@ const stats = require('../stats');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const webaccess = require('./express/webaccess');
|
const webaccess = require('./express/webaccess');
|
||||||
|
|
||||||
|
let secretRotator = null;
|
||||||
const logger = log4js.getLogger('http');
|
const logger = log4js.getLogger('http');
|
||||||
let serverName;
|
let serverName;
|
||||||
let sessionStore;
|
let sessionStore;
|
||||||
|
@ -53,6 +55,8 @@ const closeServer = async () => {
|
||||||
}
|
}
|
||||||
if (sessionStore) sessionStore.shutdown();
|
if (sessionStore) sessionStore.shutdown();
|
||||||
sessionStore = null;
|
sessionStore = null;
|
||||||
|
if (secretRotator) secretRotator.stop();
|
||||||
|
secretRotator = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createServer = async () => {
|
exports.createServer = async () => {
|
||||||
|
@ -174,13 +178,23 @@ exports.restartServer = async () => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(cookieParser(settings.sessionKey, {}));
|
const {keyRotationInterval, sessionLifetime} = settings.cookie;
|
||||||
|
let secret = settings.sessionKey;
|
||||||
|
if (keyRotationInterval && sessionLifetime) {
|
||||||
|
secretRotator = new SecretRotator(
|
||||||
|
'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey);
|
||||||
|
await secretRotator.start();
|
||||||
|
secret = secretRotator.secrets;
|
||||||
|
}
|
||||||
|
if (!secret) throw new Error('missing cookie signing secret');
|
||||||
|
|
||||||
|
app.use(cookieParser(secret, {}));
|
||||||
|
|
||||||
sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
|
sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
|
||||||
exports.sessionMiddleware = expressSession({
|
exports.sessionMiddleware = expressSession({
|
||||||
propagateTouch: true,
|
propagateTouch: true,
|
||||||
rolling: true,
|
rolling: true,
|
||||||
secret: settings.sessionKey,
|
secret,
|
||||||
store: sessionStore,
|
store: sessionStore,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
|
@ -188,7 +202,7 @@ exports.restartServer = async () => {
|
||||||
// cleaner :)
|
// cleaner :)
|
||||||
name: 'express_sid',
|
name: 'express_sid',
|
||||||
cookie: {
|
cookie: {
|
||||||
maxAge: settings.cookie.sessionLifetime || null, // Convert 0 to null.
|
maxAge: sessionLifetime || null, // Convert 0 to null.
|
||||||
sameSite: settings.cookie.sameSite,
|
sameSite: settings.cookie.sameSite,
|
||||||
|
|
||||||
// The automatic express-session mechanism for determining if the application is being served
|
// The automatic express-session mechanism for determining if the application is being served
|
||||||
|
|
|
@ -297,9 +297,9 @@ exports.indentationOnNewLine = true;
|
||||||
exports.logconfig = defaultLogConfig();
|
exports.logconfig = defaultLogConfig();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Session Key, do not sure this.
|
* Deprecated cookie signing key.
|
||||||
*/
|
*/
|
||||||
exports.sessionKey = false;
|
exports.sessionKey = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Trust Proxy, whether or not trust the x-forwarded-for header.
|
* Trust Proxy, whether or not trust the x-forwarded-for header.
|
||||||
|
@ -310,6 +310,7 @@ exports.trustProxy = false;
|
||||||
* Settings controlling the session cookie issued by Etherpad.
|
* Settings controlling the session cookie issued by Etherpad.
|
||||||
*/
|
*/
|
||||||
exports.cookie = {
|
exports.cookie = {
|
||||||
|
keyRotationInterval: 1 * 24 * 60 * 60 * 1000,
|
||||||
/*
|
/*
|
||||||
* Value of the SameSite cookie property. "Lax" is recommended unless
|
* Value of the SameSite cookie property. "Lax" is recommended unless
|
||||||
* Etherpad will be embedded in an iframe from another site, in which case
|
* Etherpad will be embedded in an iframe from another site, in which case
|
||||||
|
@ -805,12 +806,14 @@ exports.reloadSettings = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exports.sessionKey) {
|
|
||||||
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
|
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
|
||||||
|
if (!exports.sessionKey) {
|
||||||
try {
|
try {
|
||||||
exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
|
exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
|
||||||
logger.info(`Session key loaded from: ${sessionkeyFilename}`);
|
logger.info(`Session key loaded from: ${sessionkeyFilename}`);
|
||||||
} catch (e) {
|
} catch (err) { /* ignored */ }
|
||||||
|
const keyRotationEnabled = exports.cookie.keyRotationInterval && exports.cookie.sessionLifetime;
|
||||||
|
if (!exports.sessionKey && !keyRotationEnabled) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
|
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
|
||||||
exports.sessionKey = randomString(32);
|
exports.sessionKey = randomString(32);
|
||||||
|
@ -822,6 +825,10 @@ exports.reloadSettings = () => {
|
||||||
'If you are seeing this error after restarting using the Admin User ' +
|
'If you are seeing this error after restarting using the Admin User ' +
|
||||||
'Interface then you can ignore this message.');
|
'Interface then you can ignore this message.');
|
||||||
}
|
}
|
||||||
|
if (exports.sessionKey) {
|
||||||
|
logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` +
|
||||||
|
'use automatic key rotation instead (see the cookie.keyRotationInterval setting).');
|
||||||
|
}
|
||||||
|
|
||||||
if (exports.dbType === 'dirty') {
|
if (exports.dbType === 'dirty') {
|
||||||
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
|
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue