feat(gateway): add trusted-proxy auth mode (#15940)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 279d4b304f
Co-authored-by: nickytonline <833231+nickytonline@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
Nick Taylor
2026-02-14 06:32:17 -05:00
committed by GitHub
parent 3a330e681b
commit 1fb52b4d7b
28 changed files with 1867 additions and 92 deletions

View File

@@ -2,10 +2,108 @@ import os from "node:os";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
isPrivateOrLoopbackAddress,
isTrustedProxyAddress,
pickPrimaryLanIPv4,
resolveGatewayListenHosts,
} from "./net.js";
describe("isTrustedProxyAddress", () => {
describe("exact IP matching", () => {
it("returns true when IP matches exactly", () => {
expect(isTrustedProxyAddress("192.168.1.1", ["192.168.1.1"])).toBe(true);
});
it("returns false when IP does not match", () => {
expect(isTrustedProxyAddress("192.168.1.2", ["192.168.1.1"])).toBe(false);
});
it("returns true when IP matches one of multiple proxies", () => {
expect(isTrustedProxyAddress("10.0.0.5", ["192.168.1.1", "10.0.0.5", "172.16.0.1"])).toBe(
true,
);
});
});
describe("CIDR subnet matching", () => {
it("returns true when IP is within /24 subnet", () => {
expect(isTrustedProxyAddress("10.42.0.59", ["10.42.0.0/24"])).toBe(true);
expect(isTrustedProxyAddress("10.42.0.1", ["10.42.0.0/24"])).toBe(true);
expect(isTrustedProxyAddress("10.42.0.254", ["10.42.0.0/24"])).toBe(true);
});
it("returns false when IP is outside /24 subnet", () => {
expect(isTrustedProxyAddress("10.42.1.1", ["10.42.0.0/24"])).toBe(false);
expect(isTrustedProxyAddress("10.43.0.1", ["10.42.0.0/24"])).toBe(false);
});
it("returns true when IP is within /16 subnet", () => {
expect(isTrustedProxyAddress("172.19.5.100", ["172.19.0.0/16"])).toBe(true);
expect(isTrustedProxyAddress("172.19.255.255", ["172.19.0.0/16"])).toBe(true);
});
it("returns false when IP is outside /16 subnet", () => {
expect(isTrustedProxyAddress("172.20.0.1", ["172.19.0.0/16"])).toBe(false);
});
it("returns true when IP is within /32 subnet (single IP)", () => {
expect(isTrustedProxyAddress("10.42.0.0", ["10.42.0.0/32"])).toBe(true);
});
it("returns false when IP does not match /32 subnet", () => {
expect(isTrustedProxyAddress("10.42.0.1", ["10.42.0.0/32"])).toBe(false);
});
it("handles mixed exact IPs and CIDR notation", () => {
const proxies = ["192.168.1.1", "10.42.0.0/24", "172.19.0.0/16"];
expect(isTrustedProxyAddress("192.168.1.1", proxies)).toBe(true); // exact match
expect(isTrustedProxyAddress("10.42.0.59", proxies)).toBe(true); // CIDR match
expect(isTrustedProxyAddress("172.19.5.100", proxies)).toBe(true); // CIDR match
expect(isTrustedProxyAddress("10.43.0.1", proxies)).toBe(false); // no match
});
});
describe("backward compatibility", () => {
it("preserves exact IP matching behavior (no CIDR notation)", () => {
// Old configs with exact IPs should work exactly as before
expect(isTrustedProxyAddress("192.168.1.1", ["192.168.1.1"])).toBe(true);
expect(isTrustedProxyAddress("192.168.1.2", ["192.168.1.1"])).toBe(false);
expect(isTrustedProxyAddress("10.0.0.5", ["192.168.1.1", "10.0.0.5"])).toBe(true);
});
it("does NOT treat plain IPs as /32 CIDR (exact match only)", () => {
// "10.42.0.1" without /32 should match ONLY that exact IP
expect(isTrustedProxyAddress("10.42.0.1", ["10.42.0.1"])).toBe(true);
expect(isTrustedProxyAddress("10.42.0.2", ["10.42.0.1"])).toBe(false);
expect(isTrustedProxyAddress("10.42.0.59", ["10.42.0.1"])).toBe(false);
});
it("handles IPv4-mapped IPv6 addresses (existing normalizeIp behavior)", () => {
// Existing normalizeIp() behavior should be preserved
expect(isTrustedProxyAddress("::ffff:192.168.1.1", ["192.168.1.1"])).toBe(true);
});
});
describe("edge cases", () => {
it("returns false when IP is undefined", () => {
expect(isTrustedProxyAddress(undefined, ["192.168.1.1"])).toBe(false);
});
it("returns false when trustedProxies is undefined", () => {
expect(isTrustedProxyAddress("192.168.1.1", undefined)).toBe(false);
});
it("returns false when trustedProxies is empty", () => {
expect(isTrustedProxyAddress("192.168.1.1", [])).toBe(false);
});
it("returns false for invalid CIDR notation", () => {
expect(isTrustedProxyAddress("10.42.0.59", ["10.42.0.0/33"])).toBe(false); // invalid prefix
expect(isTrustedProxyAddress("10.42.0.59", ["10.42.0.0/-1"])).toBe(false); // negative prefix
expect(isTrustedProxyAddress("10.42.0.59", ["invalid/24"])).toBe(false); // invalid IP
});
});
});
describe("resolveGatewayListenHosts", () => {
it("returns the input host when not loopback", async () => {
const hosts = await resolveGatewayListenHosts("0.0.0.0", {