feat(xo-server): http.useForwardedHeaders (#7233)

Fixes https://xcp-ng.org/forum/post/67625

This setting can be enabled when XO is behind a reverse proxy to
fetch clients IP addresses from `X-Forwarded-*` headers.
This commit is contained in:
Julien Fontanet 2023-12-16 12:34:58 +01:00 committed by GitHub
parent bee0eb9091
commit b7daee81c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 7 deletions

View File

@ -13,6 +13,7 @@
- [REST API] _XO config & Pool metadata Backup_ jobs are available at `/backup/jobs/metadata`
- [REST API] _Mirror Backup_ jobs are available at `/backup/jobs/metadata`
- [Plugin/auth-saml] Add _Force re-authentication_ setting [Forum#67764](https://xcp-ng.org/forum/post/67764) (PR [#7232](https://github.com/vatesfr/xen-orchestra/pull/7232))
- [HTTP] `http.useForwardedHeaders` setting can be enabled when XO is behind a reverse proxy to fetch clients IP addresses from `X-Forwarded-*` headers [Forum#67625](https://xcp-ng.org/forum/post/67625) (PR [#7233](https://github.com/vatesfr/xen-orchestra/pull/7233))
### Bug fixes

View File

@ -107,6 +107,9 @@ writeBlockConcurrency = 16
enabled = false
threshold = 1000
[http]
useForwardedHeaders = false
# Helmet handles HTTP security via headers
#
# https://helmetjs.github.io/docs/

View File

@ -107,6 +107,7 @@
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"promise-toolbox": "^0.21.0",
"proxy-addr": "^2.0.7",
"proxy-agent": "^5.0.0",
"pug": "^3.0.0",
"pumpify": "^2.0.0",

View File

@ -72,6 +72,20 @@
# good enough (e.g. a domain name must be used or there is a reverse proxy).
#publicUrl = 'https://xoa.company.lan'
# Uncomment this if xo-server is behind a reverse proxy to make xo-server use
# X-Forwarded-* headers to determine the IP address of clients.
#
# Accepted values for this setting:
# - false (default): do not use the headers
# - true: always use the headers
# - a list of trusted addresses: the headers will be used only if the connection
# is coming from one of these addresses
#
# More info about the accepted values: https://www.npmjs.com/package/proxy-addr?activeTab=readme#proxyaddrreq-trust
#
# > Note: X-Forwarded-* headers are easily spoofed and the detected IP addresses are unreliable.
#useForwardedHeaders = true
# Settings applied to cookies created by xo-server's embedded HTTP server.
#
# See https://www.npmjs.com/package/cookie#options-1

View File

@ -13,6 +13,7 @@ import memoryStoreFactory from 'memorystore'
import merge from 'lodash/merge.js'
import ms from 'ms'
import once from 'lodash/once.js'
import proxyAddr from 'proxy-addr'
import proxyConsole from './proxy-console.mjs'
import pw from 'pw'
import serveStatic from 'serve-static'
@ -584,7 +585,7 @@ const setUpStaticFiles = (express, opts) => {
// ===================================================================
const setUpApi = (webServer, xo, config) => {
const setUpApi = (webServer, xo, config, useForwardedHeaders) => {
const webSocketServer = new WebSocketServer({
...config.apiWebSocketOptions,
@ -593,7 +594,7 @@ const setUpApi = (webServer, xo, config) => {
xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close'))
const onConnection = (socket, upgradeReq) => {
const { remoteAddress } = upgradeReq.socket
const remoteAddress = proxyAddr(upgradeReq, useForwardedHeaders)
// Create the abstract XO object for this connection.
const connection = xo.createApiConnection(remoteAddress)
@ -653,7 +654,7 @@ const setUpApi = (webServer, xo, config) => {
const CONSOLE_PROXY_PATH_RE = /^\/api\/consoles\/(.*)$/
const setUpConsoleProxy = (webServer, xo) => {
const setUpConsoleProxy = (webServer, xo, useForwardedHeaders) => {
const webSocketServer = new WebSocketServer({
noServer: true,
})
@ -678,7 +679,7 @@ const setUpConsoleProxy = (webServer, xo) => {
throw invalidCredentials()
}
const { remoteAddress } = socket
const remoteAddress = proxyAddr(req, useForwardedHeaders)
log.info(`+ Console proxy (${user.name} - ${remoteAddress})`)
const data = {
@ -836,8 +837,20 @@ export default async function main(args) {
// Trigger a clean job.
await xo.hooks.clean()
const useForwardedHeaders = (() => {
// recompile the fonction when the setting change
let useForwardedHeaders
xo.config.watch('http.useForwardedHeaders', val => {
useForwardedHeaders = typeof val === 'boolean' ? () => val : proxyAddr.compile(val)
})
return (...args) => useForwardedHeaders(...args)
})()
express.set('trust proxy', useForwardedHeaders)
// Must be set up before the API.
setUpConsoleProxy(webServer, xo)
setUpConsoleProxy(webServer, xo, useForwardedHeaders)
// Must be set up before the API.
express.use(xo._handleHttpRequest.bind(xo))
@ -847,7 +860,7 @@ export default async function main(args) {
await setUpPassport(express, xo, config)
// Must be set up before the static files.
setUpApi(webServer, xo, config)
setUpApi(webServer, xo, config, useForwardedHeaders)
setUpProxies(express, config.http.proxies, xo)

View File

@ -17225,7 +17225,7 @@ protocol-buffers-encodings@^1.1.0:
signed-varint "^2.0.1"
varint "5.0.0"
proxy-addr@~2.0.7:
proxy-addr@^2.0.7, proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==