feat(cli): add trusted-proxy auth mode to gateway configuration

- Add 'trusted-proxy' option to gateway auth mode selection
- Prompt for trusted-proxy config: userHeader, requiredHeaders, allowUsers
- Prompt for trustedProxies IP list (required for trusted-proxy mode)
- Update buildGatewayAuthConfig to handle trusted-proxy mode
- Add helpful note explaining trusted-proxy use cases and docs link

Enables CLI configuration of trusted-proxy auth during:
- Initial onboarding (openclaw onboard)
- Gateway configuration (openclaw configure)

Example prompts:
- User identity header: x-forwarded-user (default)
- Required headers: x-forwarded-proto,x-forwarded-host (optional)
- Allowed users: nick@example.com,admin@company.com (optional)
- Trusted proxy IPs: 10.0.1.10,192.168.1.5 (required)

Refs: feat/trusted-proxy auth implementation
This commit is contained in:
Nick Taylor
2026-02-13 14:47:11 +00:00
committed by Peter Steinberger
parent 164c2053ee
commit e1ce11c4b7
2 changed files with 97 additions and 4 deletions

View File

@@ -14,7 +14,7 @@ import {
import { promptCustomApiConfig } from "./onboard-custom.js";
import { randomToken } from "./onboard-helpers.js";
type GatewayAuthChoice = "token" | "password";
type GatewayAuthChoice = "token" | "password" | "trusted-proxy";
/** Reject undefined, empty, and common JS string-coercion artifacts for token auth. */
function sanitizeTokenValue(value: string | undefined): string | undefined {
@@ -40,6 +40,11 @@ export function buildGatewayAuthConfig(params: {
mode: GatewayAuthChoice;
token?: string;
password?: string;
trustedProxy?: {
userHeader: string;
requiredHeaders?: string[];
allowUsers?: string[];
};
}): GatewayAuthConfig | undefined {
const allowTailscale = params.existing?.allowTailscale;
const base: GatewayAuthConfig = {};
@@ -52,8 +57,17 @@ export function buildGatewayAuthConfig(params: {
const token = sanitizeTokenValue(params.token) ?? randomToken();
return { ...base, mode: "token", token };
}
const password = params.password?.trim();
return { ...base, mode: "password", ...(password && { password }) };
if (params.mode === "password") {
const password = params.password?.trim();
return { ...base, mode: "password", ...(password && { password }) };
}
if (params.mode === "trusted-proxy") {
if (!params.trustedProxy) {
throw new Error("trustedProxy config is required when mode is trusted-proxy");
}
return { ...base, mode: "trusted-proxy", trustedProxy: params.trustedProxy };
}
return base;
}
export async function promptAuthConfig(

View File

@@ -12,7 +12,7 @@ import {
validateGatewayPasswordInput,
} from "./onboard-helpers.js";
type GatewayAuthChoice = "token" | "password";
type GatewayAuthChoice = "token" | "password" | "trusted-proxy";
export async function promptGatewayConfig(
cfg: OpenClawConfig,
@@ -103,6 +103,11 @@ export async function promptGatewayConfig(
options: [
{ value: "token", label: "Token", hint: "Recommended default" },
{ value: "password", label: "Password" },
{
value: "trusted-proxy",
label: "Trusted Proxy",
hint: "Behind reverse proxy (Pomerium, Caddy, Traefik, etc.)",
},
],
initialValue: "token",
}),
@@ -177,6 +182,8 @@ export async function promptGatewayConfig(
let gatewayToken: string | undefined;
let gatewayPassword: string | undefined;
let trustedProxyConfig: { userHeader: string; requiredHeaders?: string[]; allowUsers?: string[] } | undefined;
let trustedProxies: string[] | undefined;
let next = cfg;
if (authMode === "token") {
@@ -201,11 +208,82 @@ export async function promptGatewayConfig(
gatewayPassword = String(password ?? "").trim();
}
if (authMode === "trusted-proxy") {
note(
[
"Trusted proxy mode: OpenClaw trusts user identity from a reverse proxy.",
"The proxy must authenticate users and pass identity via headers.",
"Only requests from specified proxy IPs will be trusted.",
"",
"Common use cases: Pomerium, Caddy + OAuth, Traefik + forward auth",
"Docs: https://docs.openclaw.ai/gateway/trusted-proxy",
].join("\n"),
"Trusted Proxy Auth",
);
const userHeader = guardCancel(
await text({
message: "Header containing user identity",
placeholder: "x-forwarded-user",
initialValue: "x-forwarded-user",
validate: (value) => (value?.trim() ? undefined : "User header is required"),
}),
runtime,
);
const requiredHeadersRaw = guardCancel(
await text({
message: "Required headers (comma-separated, optional)",
placeholder: "x-forwarded-proto,x-forwarded-host",
}),
runtime,
);
const requiredHeaders = requiredHeadersRaw
? String(requiredHeadersRaw).split(",").map((h) => h.trim()).filter(Boolean)
: [];
const allowUsersRaw = guardCancel(
await text({
message: "Allowed users (comma-separated, blank = all authenticated users)",
placeholder: "nick@example.com,admin@company.com",
}),
runtime,
);
const allowUsers = allowUsersRaw
? String(allowUsersRaw).split(",").map((u) => u.trim()).filter(Boolean)
: [];
const trustedProxiesRaw = guardCancel(
await text({
message: "Trusted proxy IPs (comma-separated)",
placeholder: "10.0.1.10,192.168.1.5",
validate: (value) => {
if (!value || String(value).trim() === "") {
return "At least one trusted proxy IP is required";
}
return undefined;
},
}),
runtime,
);
trustedProxies = String(trustedProxiesRaw)
.split(",")
.map((ip) => ip.trim())
.filter(Boolean);
trustedProxyConfig = {
userHeader: String(userHeader).trim(),
requiredHeaders: requiredHeaders.length > 0 ? requiredHeaders : undefined,
allowUsers: allowUsers.length > 0 ? allowUsers : undefined,
};
}
const authConfig = buildGatewayAuthConfig({
existing: next.gateway?.auth,
mode: authMode,
token: gatewayToken,
password: gatewayPassword,
trustedProxy: trustedProxyConfig,
});
next = {
@@ -217,6 +295,7 @@ export async function promptGatewayConfig(
bind,
auth: authConfig,
...(customBindHost && { customBindHost }),
...(trustedProxies && { trustedProxies }),
tailscale: {
...next.gateway?.tailscale,
mode: tailscaleMode,