mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 23:24:52 +09:00
Use X-Forwarded-For header on trusted proxies
If the controller is behind a proxy then the socket.remoteAddress will be that of the proxy instead of the end client that connected. In this case the X-Forwarded-For header will contain the address of the end client, but this should only be relied upon if the party connecting is a trusted proxy, otherwise a malicious user could spoof the IP address that is logged by providing a forged X-Forwarded-For header. Implement a trusted proxy list consisting of a configured list of IP addresses and/or CIDR blocks and use the last address[1] in the X-Forwarded-For header if the remoteAddress address of the connecting party is in the trusted proxy list. [1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
This commit is contained in:
1 parent
c2a3d4361a
commit
5f25aad273
6 files changed
+191
-9
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type winston from "winston";
|
||||
import type { AddressInfo } from "net";
|
||||
import { BlockList, type AddressInfo, isIPv4, isIPv6 } from "net";
|
||||
import type { ControllerArgs } from "../controller";
|
||||
import express, { type Request, type Response, type NextFunction, type Application } from "express";
|
||||
import type { Static } from "@sinclair/typebox";
|
||||
@@ -74,6 +74,7 @@ export default class Controller {
|
||||
|
||||
/** WebSocket server */
|
||||
wsServer: WsServer;
|
||||
trustedProxies: BlockList;
|
||||
debugEvents: events.EventEmitter = new events.EventEmitter();
|
||||
private _events: events.EventEmitter = new events.EventEmitter();
|
||||
|
||||
@@ -153,6 +154,7 @@ export default class Controller {
|
||||
this.app.locals.controller = this;
|
||||
this.app.locals.streams = new Map();
|
||||
|
||||
this.trustedProxies = this.parseTrustedProxies();
|
||||
this.wsServer = new WsServer(this);
|
||||
|
||||
this.modStore.on("change", mod => {
|
||||
@@ -228,6 +230,8 @@ export default class Controller {
|
||||
this.onAutosaveIntervalChanged();
|
||||
} else if (field === "controller.system_metrics_interval") {
|
||||
this.onSystemMetricsIntervalChanged();
|
||||
} else if (field === "controller.trusted_proxies") {
|
||||
this.trustedProxies = this.parseTrustedProxies();
|
||||
}
|
||||
lib.invokeHook(this.plugins, "onControllerConfigFieldChanged", field, curr, prev);
|
||||
});
|
||||
@@ -404,6 +408,34 @@ export default class Controller {
|
||||
logger.info("Goodbye");
|
||||
}
|
||||
|
||||
parseTrustedProxies() {
|
||||
const trustedProxies = new BlockList();
|
||||
const proxiesString = this.config.get("controller.trusted_proxies");
|
||||
if (proxiesString) {
|
||||
const proxies = proxiesString.split(",").map(s => s.trim());
|
||||
for (const proxy of proxies) {
|
||||
const [ip, prefix] = proxy.split("/");
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const type = isIPv4(ip) ? "ipv4" : isIPv6(ip) ? "ipv6" : undefined;
|
||||
if (!type) {
|
||||
logger.error(`Invalid proxy '${proxy}': not an IP address`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (prefix) {
|
||||
trustedProxies.addSubnet(ip, Number.parseInt(prefix, 10), type);
|
||||
} else {
|
||||
trustedProxies.addAddress(ip, type);
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error(`Invalid proxy '${proxy}': ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trustedProxies;
|
||||
}
|
||||
|
||||
onSystemMetricsIntervalChanged() {
|
||||
if (this.systemMetricsInterval) {
|
||||
clearInterval(this.systemMetricsInterval);
|
||||
|
||||
Reference in new issue
Block a user