diff --git a/config.template.yaml b/config.template.yaml index 06499369..664a3428 100644 --- a/config.template.yaml +++ b/config.template.yaml @@ -70,6 +70,9 @@ http: index: # Maximum number of channels to display on the index page public channel list max-entries: 50 + # Configure trusted proxy addresses to map X-Forwarded-For to the client IP. + # See also: https://github.com/jshttp/proxy-addr + trust-proxies: ['loopback'] # HTTPS server details https: diff --git a/package.json b/package.json index 623372b7..4a4b924a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.39.0", + "version": "3.39.1", "repository": { "url": "http://github.com/calzoneman/sync" }, @@ -32,6 +32,7 @@ "mysql": "^2.9.0", "nodemailer": "^1.4.0", "oauth": "^0.9.12", + "proxy-addr": "^1.1.4", "pug": "^2.0.0-beta3", "q": "^1.4.1", "redis": "^2.4.2", diff --git a/src/config.js b/src/config.js index 7e65949d..dfaf116a 100644 --- a/src/config.js +++ b/src/config.js @@ -43,7 +43,10 @@ var defaults = { "cookie-secret": "change-me", index: { "max-entries": 50 - } + }, + "trust-proxies": [ + "loopback" + ] }, https: { enabled: false, diff --git a/src/configuration/webconfig.js b/src/configuration/webconfig.js index ae1f888d..a9de72be 100644 --- a/src/configuration/webconfig.js +++ b/src/configuration/webconfig.js @@ -1,10 +1,5 @@ import clone from 'clone'; -const DEFAULT_TRUSTED_PROXIES = Object.freeze([ - '127.0.0.1', - '::1' -]); - export default class WebConfiguration { constructor(config) { this.config = config; @@ -15,7 +10,7 @@ export default class WebConfiguration { } getTrustedProxies() { - return DEFAULT_TRUSTED_PROXIES; + return this.config.trustProxies; } getCookieSecret() { @@ -76,5 +71,7 @@ WebConfiguration.fromOldConfig = function (oldConfig) { config.maxIndexEntries = oldConfig.get('http.index.max-entries'); + config.trustProxies = oldConfig.get('http.trust-proxies'); + return new WebConfiguration(config); }; diff --git a/src/io/ioserver.js b/src/io/ioserver.js index 8461e283..86809b4b 100644 --- a/src/io/ioserver.js +++ b/src/io/ioserver.js @@ -19,6 +19,7 @@ import { LoggerFactory } from '@calzoneman/jsli'; const verifySession = Promise.promisify(session.verifySession); const getAliases = Promise.promisify(db.getAliases); import { CachingGlobalBanlist } from './globalban'; +import proxyaddr from 'proxy-addr'; const LOGGER = LoggerFactory.getLogger('ioserver'); @@ -165,35 +166,13 @@ function addTypecheckedFunctions(sock) { } function ipForwardingMiddleware(webConfig) { - function getForwardedIP(socket) { - var req = socket.client.request; - const xForwardedFor = req.headers['x-forwarded-for']; - if (!xForwardedFor) { - return socket.client.conn.remoteAddress; - } - - const ipList = xForwardedFor.split(','); - for (let i = 0; i < ipList.length; i++) { - const ip = ipList[i].trim(); - if (net.isIP(ip)) { - return ip; - } - } - - return socket.client.conn.remoteAddress; - } - - function isTrustedProxy(ip) { - return webConfig.getTrustedProxies().indexOf(ip) >= 0; - } + const trustFn = proxyaddr.compile(webConfig.getTrustedProxies()); return function (socket, accept) { - if (isTrustedProxy(socket.client.conn.remoteAddress)) { - socket._realip = getForwardedIP(socket); - } else { - socket._realip = socket.client.conn.remoteAddress; - } - + LOGGER.debug('ip = %s', socket.client.request.connection.remoteAddress); + //socket.client.request.ip = socket.client.conn.remoteAddress; + socket._realip = proxyaddr(socket.client.request, trustFn); + LOGGER.debug('socket._realip: %s', socket._realip); accept(null, true); } } diff --git a/src/web/middleware/x-forwarded-for.js b/src/web/middleware/x-forwarded-for.js index 3e94b3d1..aeccfaa4 100644 --- a/src/web/middleware/x-forwarded-for.js +++ b/src/web/middleware/x-forwarded-for.js @@ -1,45 +1,29 @@ -import net from 'net'; +import proxyaddr from 'proxy-addr'; -export default function initialize(app, webConfig) { - function isTrustedProxy(ip) { - return webConfig.getTrustedProxies().indexOf(ip) >= 0; - } +export function initialize(app, webConfig) { + const trustFn = proxyaddr.compile(webConfig.getTrustedProxies()); - function getForwardedIP(req) { - const xForwardedFor = req.header('x-forwarded-for'); - if (!xForwardedFor) { - return req.ip; - } - - const ipList = xForwardedFor.split(','); - for (let i = 0; i < ipList.length; i++) { - const ip = ipList[i].trim(); - if (net.isIP(ip)) { - return ip; - } - } - - return req.ip; - } - - function getForwardedProto(req) { - const xForwardedProto = req.header('x-forwarded-proto'); - if (xForwardedProto && xForwardedProto.match(/^https?$/)) { - return xForwardedProto; - } else { - return req.protocol; - } - } - - app.use((req, res, next) => { - if (isTrustedProxy(req.ip)) { - req.realIP = getForwardedIP(req); - req.realProtocol = getForwardedProto(req); - } else { - req.realIP = req.ip; - req.realProtocol = req.protocol; - } - - next(); - }); + app.use(readProxyHeaders.bind(null, trustFn)); +} + +function getForwardedProto(req) { + const xForwardedProto = req.header('x-forwarded-proto'); + if (xForwardedProto && xForwardedProto.match(/^https?$/)) { + return xForwardedProto; + } else { + return req.protocol; + } +} + +function readProxyHeaders(trustFn, req, res, next) { + const forwardedIP = proxyaddr(req, trustFn); + if (forwardedIP !== req.ip) { + req.realIP = forwardedIP; + req.realProtocol = getForwardedProto(req); + } else { + req.realIP = req.ip; + req.realProtocol = req.protocol; + } + + next(); } diff --git a/src/web/webserver.js b/src/web/webserver.js index 379d375c..4a6b8f2c 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -138,7 +138,7 @@ module.exports = { counters.add("http:request", 1); next(); }); - require('./middleware/x-forwarded-for')(app, webConfig); + require('./middleware/x-forwarded-for').initialize(app, webConfig); app.use(bodyParser.urlencoded({ extended: false, limit: '1kb' // No POST data should ever exceed this size under normal usage