mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 15:30:39 +00:00
fix(gateway): require loopback proxy IP for trusted-proxy + bind=loopback (#22082)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 6ff3ca9b5d
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/Auth: require `gateway.trustedProxies` to include a loopback proxy address when `auth.mode="trusted-proxy"` and `bind="loopback"`, preventing same-host proxy misconfiguration from silently blocking auth. (#22082, follow-up to #20097) thanks @mbelinky.
|
||||
- Gateway/Auth: allow trusted-proxy mode with loopback bind for same-host reverse-proxy deployments, while still requiring configured `gateway.trustedProxies`. (#20097) thanks @xinhuagu.
|
||||
- Gateway/Auth: allow authenticated clients across roles/scopes to call `health` while preserving role and scope enforcement for non-health methods. (#19699) thanks @Nachx639.
|
||||
- Gateway/Hooks: include transform export name in hook-transform cache keys so distinct exports from the same module do not reuse the wrong cached transform function. (#13855) thanks @mcaxtr.
|
||||
|
||||
@@ -39,8 +39,8 @@ Use `trusted-proxy` auth mode when:
|
||||
```json5
|
||||
{
|
||||
gateway: {
|
||||
// Must bind to network interface (not loopback)
|
||||
bind: "lan",
|
||||
// Use loopback for same-host proxy setups; use lan/custom for remote proxy hosts
|
||||
bind: "loopback",
|
||||
|
||||
// CRITICAL: Only add your proxy's IP(s) here
|
||||
trustedProxies: ["10.0.0.1", "172.17.0.1"],
|
||||
@@ -62,6 +62,9 @@ Use `trusted-proxy` auth mode when:
|
||||
}
|
||||
```
|
||||
|
||||
If `gateway.bind` is `loopback`, include a loopback proxy address in
|
||||
`gateway.trustedProxies` (`127.0.0.1`, `::1`, or an equivalent loopback CIDR).
|
||||
|
||||
### Configuration Reference
|
||||
|
||||
| Field | Required | Description |
|
||||
|
||||
@@ -51,6 +51,27 @@ describe("resolveGatewayRuntimeConfig", () => {
|
||||
expect(result.bindHost).toBe("127.0.0.1");
|
||||
});
|
||||
|
||||
it("should allow loopback trusted-proxy when trustedProxies includes ::1", async () => {
|
||||
const cfg = {
|
||||
gateway: {
|
||||
bind: "loopback" as const,
|
||||
auth: {
|
||||
mode: "trusted-proxy" as const,
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
trustedProxies: ["::1"],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await resolveGatewayRuntimeConfig({
|
||||
cfg,
|
||||
port: 18789,
|
||||
});
|
||||
expect(result.bindHost).toBe("127.0.0.1");
|
||||
});
|
||||
|
||||
it("should reject loopback trusted-proxy without trustedProxies configured", async () => {
|
||||
const cfg = {
|
||||
gateway: {
|
||||
@@ -75,6 +96,30 @@ describe("resolveGatewayRuntimeConfig", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject loopback trusted-proxy when trustedProxies has no loopback address", async () => {
|
||||
const cfg = {
|
||||
gateway: {
|
||||
bind: "loopback" as const,
|
||||
auth: {
|
||||
mode: "trusted-proxy" as const,
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
trustedProxies: ["10.0.0.1"],
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
resolveGatewayRuntimeConfig({
|
||||
cfg,
|
||||
port: 18789,
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"gateway auth mode=trusted-proxy with bind=loopback requires gateway.trustedProxies to include 127.0.0.1, ::1, or a loopback CIDR",
|
||||
);
|
||||
});
|
||||
|
||||
it("should reject trusted-proxy without trustedProxies configured", async () => {
|
||||
const cfg = {
|
||||
gateway: {
|
||||
|
||||
@@ -11,7 +11,12 @@ import {
|
||||
} from "./auth.js";
|
||||
import { normalizeControlUiBasePath } from "./control-ui-shared.js";
|
||||
import { resolveHooksConfig } from "./hooks.js";
|
||||
import { isLoopbackHost, isValidIPv4, resolveGatewayBindHost } from "./net.js";
|
||||
import {
|
||||
isLoopbackHost,
|
||||
isTrustedProxyAddress,
|
||||
isValidIPv4,
|
||||
resolveGatewayBindHost,
|
||||
} from "./net.js";
|
||||
import { mergeGatewayTailscaleConfig } from "./startup-auth.js";
|
||||
|
||||
export type GatewayRuntimeConfig = {
|
||||
@@ -122,6 +127,16 @@ export async function resolveGatewayRuntimeConfig(params: {
|
||||
"gateway auth mode=trusted-proxy requires gateway.trustedProxies to be configured with at least one proxy IP",
|
||||
);
|
||||
}
|
||||
if (isLoopbackHost(bindHost)) {
|
||||
const hasLoopbackTrustedProxy =
|
||||
isTrustedProxyAddress("127.0.0.1", trustedProxies) ||
|
||||
isTrustedProxyAddress("::1", trustedProxies);
|
||||
if (!hasLoopbackTrustedProxy) {
|
||||
throw new Error(
|
||||
"gateway auth mode=trusted-proxy with bind=loopback requires gateway.trustedProxies to include 127.0.0.1, ::1, or a loopback CIDR",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user