Merge pull request #18088 from overleaf/ab-session-secret-rotation

[web/realtime/history-v1] Support session secret rotation

GitOrigin-RevId: 3c2fa27b1b3e0a8e0c9d1af2e616ce873d54aedf
This commit is contained in:
Brian Gough 2024-05-23 15:51:27 +01:00 committed by Copybot
parent a4816d6e75
commit 344b4d0fa0
11 changed files with 41 additions and 70 deletions

60
package-lock.json generated
View file

@ -18079,6 +18079,7 @@
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dev": true,
"dependencies": {
"object-assign": "^4",
"vary": "^1"
@ -42416,8 +42417,6 @@
"check-types": "^11.1.2",
"command-line-args": "^3.0.3",
"config": "^1.19.0",
"cookie-parser": "~1.4.5",
"cors": "^2.8.5",
"express": "^4.19.2",
"fs-extra": "^9.0.1",
"generic-pool": "^2.1.1",
@ -43962,7 +43961,7 @@
"body-parser": "^1.19.0",
"bunyan": "^1.8.15",
"connect-redis": "^6.1.3",
"cookie-parser": "^1.4.5",
"cookie-parser": "^1.4.6",
"express": "^4.19.2",
"express-session": "^1.17.1",
"joi": "^17.12.0",
@ -44457,7 +44456,7 @@
"content-disposition": "^0.5.0",
"contentful": "^10.8.5",
"cookie": "^0.2.3",
"cookie-parser": "1.3.5",
"cookie-parser": "1.4.6",
"core-js": "^3.30.2",
"crc-32": "^1.2.2",
"csurf": "^1.11.0",
@ -45466,31 +45465,6 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"services/web/node_modules/cookie-parser": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz",
"integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=",
"dependencies": {
"cookie": "0.1.3",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"services/web/node_modules/cookie-parser/node_modules/cookie": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz",
"integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU=",
"engines": {
"node": "*"
}
},
"services/web/node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"services/web/node_modules/csv": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.2.5.tgz",
@ -52554,7 +52528,7 @@
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"connect-redis": "^6.1.3",
"cookie-parser": "^1.4.5",
"cookie-parser": "^1.4.6",
"cookie-signature": "^1.1.0",
"express": "^4.19.2",
"express-session": "^1.17.1",
@ -53060,7 +53034,7 @@
"content-disposition": "^0.5.0",
"contentful": "^10.8.5",
"cookie": "^0.2.3",
"cookie-parser": "1.3.5",
"cookie-parser": "1.4.6",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.30.2",
"crc-32": "^1.2.2",
@ -53732,27 +53706,6 @@
"supports-color": "^7.1.0"
}
},
"cookie-parser": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.3.5.tgz",
"integrity": "sha1-nXVVcPtdF4kHcSJ6AjFNm+fPg1Y=",
"requires": {
"cookie": "0.1.3",
"cookie-signature": "1.0.6"
},
"dependencies": {
"cookie": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.3.tgz",
"integrity": "sha1-5zSlwUF/zkctWu+Cw4HKu2TRpDU="
}
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"csv": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/csv/-/csv-6.2.5.tgz",
@ -61672,6 +61625,7 @@
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dev": true,
"requires": {
"object-assign": "^4",
"vary": "^1"
@ -72400,8 +72354,6 @@
"check-types": "^11.1.2",
"command-line-args": "^3.0.3",
"config": "^1.19.0",
"cookie-parser": "~1.4.5",
"cors": "^2.8.5",
"express": "^4.19.2",
"fs-extra": "^9.0.1",
"generic-pool": "^2.1.1",

View file

@ -9,12 +9,10 @@ const config = require('config')
const Events = require('events')
const BPromise = require('bluebird')
const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const HTTPStatus = require('http-status')
const logger = require('@overleaf/logger')
const Metrics = require('@overleaf/metrics')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const swaggerTools = require('swagger-tools')
const swaggerDoc = require('./api/swagger')
@ -42,8 +40,6 @@ app.use(
extended: false,
})
)
app.use(cookieParser())
app.use(cors())
security.setupSSL(app)
security.setupBasicHttpAuthForSwaggerDocs(app)

View file

@ -21,8 +21,6 @@
"check-types": "^11.1.2",
"command-line-args": "^3.0.3",
"config": "^1.19.0",
"cookie-parser": "~1.4.5",
"cors": "^2.8.5",
"express": "^4.19.2",
"fs-extra": "^9.0.1",
"generic-pool": "^2.1.1",

View file

@ -56,7 +56,17 @@ const io = require('socket.io').listen(server, {
// Bind to sessions
const sessionStore = new RedisStore({ client: sessionRedisClient })
const cookieParser = CookieParser(Settings.security.sessionSecret)
if (!Settings.security.sessionSecret) {
throw new Error('No SESSION_SECRET provided.')
}
const sessionSecrets = [
Settings.security.sessionSecret,
Settings.security.sessionSecretUpcoming,
Settings.security.sessionSecretFallback,
].filter(Boolean)
const cookieParser = CookieParser(sessionSecrets)
const sessionSockets = new SessionSockets(
io,

View file

@ -105,7 +105,9 @@ const settings = {
},
security: {
sessionSecret: process.env.SESSION_SECRET || 'secret-please-change',
sessionSecret: process.env.SESSION_SECRET,
sessionSecretUpcoming: process.env.SESSION_SECRET_UPCOMING,
sessionSecretFallback: process.env.SESSION_SECRET_FALLBACK,
},
cookieName: process.env.COOKIE_NAME || 'overleaf.sid',

View file

@ -2,4 +2,9 @@ module.exports = {
errors: {
catchUncaughtErrors: false,
},
security: {
sessionSecret: 'static-secret-for-tests',
sessionSecretFallback: 'static-secret-fallback-for-tests',
},
}

View file

@ -27,7 +27,7 @@
"body-parser": "^1.19.0",
"bunyan": "^1.8.15",
"connect-redis": "^6.1.3",
"cookie-parser": "^1.4.5",
"cookie-parser": "^1.4.6",
"express": "^4.19.2",
"express-session": "^1.17.1",
"joi": "^17.12.0",

View file

@ -155,10 +155,16 @@ if (Settings.useHttpPermissionsPolicy) {
RedirectManager.apply(webRouter)
if (!Settings.security.sessionSecret) {
throw new Error('Session secret is not set - refusing to start server')
throw new Error('No SESSION_SECRET provided.')
}
webRouter.use(cookieParser(Settings.security.sessionSecret))
const sessionSecrets = [
Settings.security.sessionSecret,
Settings.security.sessionSecretUpcoming,
Settings.security.sessionSecretFallback,
].filter(Boolean)
webRouter.use(cookieParser(sessionSecrets))
SessionAutostartMiddleware.applyInitialMiddleware(webRouter)
Modules.registerMiddleware(webRouter, 'sessionMiddleware', {
store: sessionStore,
@ -167,7 +173,7 @@ webRouter.use(
session({
resave: false,
saveUninitialized: false,
secret: Settings.security.sessionSecret,
secret: sessionSecrets,
proxy: Settings.behindProxy,
cookie: {
domain: Settings.cookieDomain,

View file

@ -43,8 +43,6 @@ if (httpAuthUser && httpAuthPass) {
httpAuthUsers[httpAuthUser] = httpAuthPass
}
const sessionSecret = process.env.SESSION_SECRET
const intFromEnv = function (name, defaultValue) {
if (
[null, undefined].includes(defaultValue) ||
@ -386,7 +384,9 @@ module.exports = {
// Security
// --------
security: {
sessionSecret,
sessionSecret: process.env.SESSION_SECRET,
sessionSecretUpcoming: process.env.SESSION_SECRET_UPCOMING,
sessionSecretFallback: process.env.SESSION_SECRET_FALLBACK,
bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS, 10) || 12,
}, // number of rounds used to hash user passwords (raised to power 2)

View file

@ -100,7 +100,7 @@
"content-disposition": "^0.5.0",
"contentful": "^10.8.5",
"cookie": "^0.2.3",
"cookie-parser": "1.3.5",
"cookie-parser": "1.4.6",
"core-js": "^3.30.2",
"crc-32": "^1.2.2",
"csurf": "^1.11.0",

View file

@ -17,6 +17,8 @@ module.exports = {
secureCookie: false,
security: {
sessionSecret: 'static-secret-for-tests',
sessionSecretUpcoming: 'static-secret-upcoming-for-tests',
sessionSecretFallback: 'static-secret-fallback-for-tests',
},
adminDomains: process.env.ADMIN_DOMAINS
? JSON.parse(process.env.ADMIN_DOMAINS)