Refactor ip range checks and support ipv6

This commit is contained in:
Jesús Ramos 2023-03-30 10:56:46 +00:00
parent c8ef1d6ccf
commit 287c2d1bbc
3 changed files with 41 additions and 44 deletions

View File

@ -18,6 +18,7 @@
"dockerode": "^3.3.4", "dockerode": "^3.3.4",
"follow-redirects": "^1.15.2", "follow-redirects": "^1.15.2",
"i18next": "^21.9.2", "i18next": "^21.9.2",
"ipaddr.js": "^2.0.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-rpc-2.0": "^1.4.1", "json-rpc-2.0": "^1.4.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",

View File

@ -25,6 +25,9 @@ dependencies:
i18next: i18next:
specifier: ^21.9.2 specifier: ^21.9.2
version: 21.10.0 version: 21.10.0
ipaddr.js:
specifier: ^2.0.1
version: 2.0.1
js-yaml: js-yaml:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
@ -1924,6 +1927,11 @@ packages:
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
dev: false dev: false
/ipaddr.js@2.0.1:
resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==}
engines: {node: '>= 10'}
dev: false
/is-arguments@1.1.1: /is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}

View File

@ -2,51 +2,46 @@ import { getClientIp } from "@supercharge/request-ip";
import { getSettings } from "utils/config/config"; import { getSettings } from "utils/config/config";
function checkIPRange(ip, ipSpace) { const ipaddr = require("ipaddr.js");
// Check if the given IP is in the ipSpace address space using
// CIDR notation. If ipSpace is a plain IPv4 we just compare them. function checkSingleIPRange(ip, cidr) {
const ipSpaceParts = ipSpace.split("/"); try {
if (ipSpaceParts.length === 1) { const ipAddr = ipaddr.process(ip);
if (ip === ipSpace) { // If the CIDR is not in the format of x.x.x.x/y, we assume it is a single IP
return true; if (!cidr.includes("/")) {
const cidrIP = ipaddr.process(cidr);
// If both are IPv6, we need to normalize the strings
if (ipAddr.kind() === "ipv6" && cidrIP.kind() === "ipv6") {
return (ipAddr.toNormalizedString() === cidrIP.toNormalizedString());
}
return ipAddr.toString() === cidrIP.toString();
} }
} else if (ipSpaceParts.length === 2) { // Otherwise, we assume it is a CIDR range
const ipParts = ip.split("."); const range = ipaddr.parseCIDR(cidr);
const ipNum = parseInt(ipParts[0], 10) * 256 * 256 * 256 + parseInt(ipParts[1], 10) * 256 * 256 + parseInt(ipParts[2], 10) * 256 + parseInt(ipParts[3], 10); return ipAddr.match(range);
const ipSpaceNum = parseInt(ipSpaceParts[0].split(".")[0], 10) * 256 * 256 * 256 + parseInt(ipSpaceParts[0].split(".")[1], 10) * 256 * 256 + parseInt(ipSpaceParts[0].split(".")[2], 10) * 256 + parseInt(ipSpaceParts[0].split(".")[3], 10); } catch (e) {
const mask = 32 - parseInt(ipSpaceParts[1], 10); return false;
// eslint-disable-next-line no-bitwise }
const maskNum = 0xffffffff << mask; }
// eslint-disable-next-line no-bitwise
if ((ipNum & maskNum) === (ipSpaceNum & maskNum)) { function checkIPRange(ip, range) {
return true; if (typeof range === "string") {
} return checkSingleIPRange(ip, range);
} else { }
throw new Error(`Invalid ipSpace: ${ipSpace}`); if (Array.isArray(range)) {
return range.find((cidr) => checkSingleIPRange(ip, cidr)) !== undefined;
} }
return false; return false;
} }
function isRequestProxied(remoteAddress) { export function isRequestProxied(remoteAddress) {
const settings = getSettings(); const settings = getSettings();
// Check if trustedproxies is set // Check if trustedproxies is set
const trustedProxies = settings?.trustedproxies; const trustedProxies = settings?.trustedproxies;
// If trustedproxies is set, check if the client IP // If trustedproxies is set, check if the client IP
// is in the trustedproxies address space using CIDR notation. // is in the trustedproxies address space.
if (trustedProxies) { return trustedProxies ? checkIPRange(remoteAddress, trustedProxies) : false;
// Get the connection IP and strip IPv6 from the hybrid IPv4-IPv6 socket
const ip = remoteAddress.replace(/^.*:/, '');
for (let i = 0; i < trustedProxies.length; i += 1) {
const proxy = trustedProxies[i].trim();
const inRange = checkIPRange(ip, proxy);
if (inRange) {
return true;
}
}
}
return false;
} }
export function getRealClientIP(req) { export function getRealClientIP(req) {
@ -63,17 +58,10 @@ export function isInLocalScope(req) {
const localScope = settings?.localscope; const localScope = settings?.localscope;
// If localscope is set, check if the client IP // If localscope is set, check if the client IP
// is in the localscope address space using CIDR notation. // is in the localscope address space.
if (localScope) { if (localScope) {
const ip = getRealClientIP(req); const ip = getRealClientIP(req);
return checkIPRange(ip, localScope);
for (let i = 0; i < localScope.length; i += 1) {
const localIP = localScope[i].trim();
const inRange = checkIPRange(ip, localIP)
if (inRange) {
return true;
}
}
} }
return false; return false;
} }