Use `proxy-addr` for parsing x-forwarded-for

Closes #683 by providing functionality to trust proxies other than
localhost.
This commit is contained in:
Calvin Montgomery 2017-06-27 23:37:18 -07:00
parent 9cffd7dde8
commit 76e0d1b7ec
7 changed files with 45 additions and 78 deletions

View File

@ -70,6 +70,9 @@ http:
index: index:
# Maximum number of channels to display on the index page public channel list # Maximum number of channels to display on the index page public channel list
max-entries: 50 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 server details
https: https:

View File

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.39.0", "version": "3.39.1",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },
@ -32,6 +32,7 @@
"mysql": "^2.9.0", "mysql": "^2.9.0",
"nodemailer": "^1.4.0", "nodemailer": "^1.4.0",
"oauth": "^0.9.12", "oauth": "^0.9.12",
"proxy-addr": "^1.1.4",
"pug": "^2.0.0-beta3", "pug": "^2.0.0-beta3",
"q": "^1.4.1", "q": "^1.4.1",
"redis": "^2.4.2", "redis": "^2.4.2",

View File

@ -43,7 +43,10 @@ var defaults = {
"cookie-secret": "change-me", "cookie-secret": "change-me",
index: { index: {
"max-entries": 50 "max-entries": 50
} },
"trust-proxies": [
"loopback"
]
}, },
https: { https: {
enabled: false, enabled: false,

View File

@ -1,10 +1,5 @@
import clone from 'clone'; import clone from 'clone';
const DEFAULT_TRUSTED_PROXIES = Object.freeze([
'127.0.0.1',
'::1'
]);
export default class WebConfiguration { export default class WebConfiguration {
constructor(config) { constructor(config) {
this.config = config; this.config = config;
@ -15,7 +10,7 @@ export default class WebConfiguration {
} }
getTrustedProxies() { getTrustedProxies() {
return DEFAULT_TRUSTED_PROXIES; return this.config.trustProxies;
} }
getCookieSecret() { getCookieSecret() {
@ -76,5 +71,7 @@ WebConfiguration.fromOldConfig = function (oldConfig) {
config.maxIndexEntries = oldConfig.get('http.index.max-entries'); config.maxIndexEntries = oldConfig.get('http.index.max-entries');
config.trustProxies = oldConfig.get('http.trust-proxies');
return new WebConfiguration(config); return new WebConfiguration(config);
}; };

View File

@ -19,6 +19,7 @@ import { LoggerFactory } from '@calzoneman/jsli';
const verifySession = Promise.promisify(session.verifySession); const verifySession = Promise.promisify(session.verifySession);
const getAliases = Promise.promisify(db.getAliases); const getAliases = Promise.promisify(db.getAliases);
import { CachingGlobalBanlist } from './globalban'; import { CachingGlobalBanlist } from './globalban';
import proxyaddr from 'proxy-addr';
const LOGGER = LoggerFactory.getLogger('ioserver'); const LOGGER = LoggerFactory.getLogger('ioserver');
@ -165,35 +166,13 @@ function addTypecheckedFunctions(sock) {
} }
function ipForwardingMiddleware(webConfig) { function ipForwardingMiddleware(webConfig) {
function getForwardedIP(socket) { const trustFn = proxyaddr.compile(webConfig.getTrustedProxies());
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;
}
return function (socket, accept) { return function (socket, accept) {
if (isTrustedProxy(socket.client.conn.remoteAddress)) { LOGGER.debug('ip = %s', socket.client.request.connection.remoteAddress);
socket._realip = getForwardedIP(socket); //socket.client.request.ip = socket.client.conn.remoteAddress;
} else { socket._realip = proxyaddr(socket.client.request, trustFn);
socket._realip = socket.client.conn.remoteAddress; LOGGER.debug('socket._realip: %s', socket._realip);
}
accept(null, true); accept(null, true);
} }
} }

View File

@ -1,45 +1,29 @@
import net from 'net'; import proxyaddr from 'proxy-addr';
export default function initialize(app, webConfig) { export function initialize(app, webConfig) {
function isTrustedProxy(ip) { const trustFn = proxyaddr.compile(webConfig.getTrustedProxies());
return webConfig.getTrustedProxies().indexOf(ip) >= 0;
}
function getForwardedIP(req) { app.use(readProxyHeaders.bind(null, trustFn));
const xForwardedFor = req.header('x-forwarded-for'); }
if (!xForwardedFor) {
return req.ip; function getForwardedProto(req) {
} const xForwardedProto = req.header('x-forwarded-proto');
if (xForwardedProto && xForwardedProto.match(/^https?$/)) {
const ipList = xForwardedFor.split(','); return xForwardedProto;
for (let i = 0; i < ipList.length; i++) { } else {
const ip = ipList[i].trim(); return req.protocol;
if (net.isIP(ip)) { }
return ip; }
}
} function readProxyHeaders(trustFn, req, res, next) {
const forwardedIP = proxyaddr(req, trustFn);
return req.ip; if (forwardedIP !== req.ip) {
} req.realIP = forwardedIP;
req.realProtocol = getForwardedProto(req);
function getForwardedProto(req) { } else {
const xForwardedProto = req.header('x-forwarded-proto'); req.realIP = req.ip;
if (xForwardedProto && xForwardedProto.match(/^https?$/)) { req.realProtocol = req.protocol;
return xForwardedProto; }
} else {
return req.protocol; next();
}
}
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();
});
} }

View File

@ -138,7 +138,7 @@ module.exports = {
counters.add("http:request", 1); counters.add("http:request", 1);
next(); next();
}); });
require('./middleware/x-forwarded-for')(app, webConfig); require('./middleware/x-forwarded-for').initialize(app, webConfig);
app.use(bodyParser.urlencoded({ app.use(bodyParser.urlencoded({
extended: false, extended: false,
limit: '1kb' // No POST data should ever exceed this size under normal usage limit: '1kb' // No POST data should ever exceed this size under normal usage