Skip to content

Trusted Proxy 认证

安全敏感功能。 此模式将认证完全委托给你的反向代理。配置错误可能导致你的 Gateway 遭到未授权访问。启用前请仔细阅读本页。

适用场景

以下情况使用 trusted-proxy 认证模式:

  • 你在身份感知代理(Pomerium、Caddy + OAuth、nginx + oauth2-proxy、Traefik + forward auth)后面运行 OpenClaw
  • 你的代理处理所有认证,并通过 Header 传递用户身份
  • 你处于 Kubernetes 或容器环境中,代理是访问 Gateway 的唯一路径
  • 你遇到 WebSocket 1008 unauthorized 错误,因为浏览器无法在 WS 载荷中传递 token

不适用场景

  • 你的代理不认证用户(只是 TLS 终止或负载均衡器)
  • 存在绕过代理直接访问 Gateway 的路径(防火墙漏洞、内网直接访问)
  • 你不确定代理是否正确剥离/覆盖转发 Header
  • 你只需要个人单用户访问(考虑使用 Tailscale Serve + 回环地址,配置更简单)

工作原理

  1. 你的反向代理认证用户(OAuth、OIDC、SAML 等)
  2. 代理添加包含已认证用户身份的 Header(例如 x-forwarded-user: nick@example.com
  3. OpenClaw 验证请求来自受信任的代理 IP(在 gateway.trustedProxies 中配置)
  4. OpenClaw 从配置的 Header 中提取用户身份
  5. 一切检查通过后,请求获得授权

控制台配对行为

gateway.auth.mode = "trusted-proxy" 启用且请求通过受信任代理检查时,Control UI WebSocket 会话可以在没有设备配对身份的情况下连接。

影响说明:

  • 在此模式下,配对不再是 Control UI 访问的主要关卡。
  • 你的反向代理认证策略和 allowUsers 成为实际的访问控制机制。
  • 请将 Gateway 入口锁定为仅受信任的代理 IP(gateway.trustedProxies + 防火墙)。

配置

json5
{
  gateway: {
    // Trusted-proxy 认证期望请求来自非 loopback 的受信任代理源
    bind: "lan",

    // 关键:这里只添加你代理的 IP
    trustedProxies: ["10.0.0.1", "172.17.0.1"],

    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        // 包含已认证用户身份的 Header(必填)
        userHeader: "x-forwarded-user",

        // 可选:必须存在的 Header(代理验证)
        requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],

        // 可选:限制到特定用户(空 = 允许所有已认证用户)
        allowUsers: ["nick@example.com", "admin@company.org"],
      },
    },
  },
}

重要运行时规则:

  • Trusted-proxy 认证会拒绝来自 loopback 源的请求(127.0.0.1::1、loopback CIDR)。
  • 同主机 loopback 反向代理满足 trusted-proxy 认证。
  • 对于同主机 loopback 代理,请改用 token/password 认证,或通过非 loopback 受信任代理地址路由,并在 gateway.trustedProxies 中保留该 IP。
  • 非 loopback 控制 UI 部署仍然需要显式配置 gateway.controlUi.allowedOrigins

配置参考

字段是否必填说明
gateway.trustedProxies受信任的代理 IP 地址数组,来自其他 IP 的请求将被拒绝
gateway.auth.mode必须为 "trusted-proxy"
gateway.auth.trustedProxy.userHeader包含已认证用户身份的 Header 名称
gateway.auth.trustedProxy.requiredHeaders请求受信任所必须存在的额外 Header
gateway.auth.trustedProxy.allowUsers用户身份允许列表,空表示允许所有已认证用户

TLS 终止与 HSTS

使用一个 TLS 终止点,并在该点应用 HSTS。

推荐模式:代理 TLS 终止

当你的反向代理为 https://control.example.com 处理 HTTPS 时,在该域的代理处设置 Strict-Transport-Security

  • 适合面向互联网的部署。
  • 证书 + HTTP 加固策略集中在一处。
  • OpenClaw 可以保持在代理后面的回环 HTTP。

示例 Header 值:

text
Strict-Transport-Security: max-age=31536000; includeSubDomains

Gateway TLS 终止

如果 OpenClaw 自身直接提供 HTTPS(没有 TLS 终止代理),设置:

json5
{
  gateway: {
    tls: { enabled: true },
    http: {
      securityHeaders: {
        strictTransportSecurity: "max-age=31536000; includeSubDomains",
      },
    },
  },
}

strictTransportSecurity 接受字符串 Header 值,或 false 显式禁用。

推出指南

  • 先从短 max-age 开始(例如 max-age=300),在验证流量期间使用。
  • 确认无误后才增加到长期值(例如 max-age=31536000)。
  • 只有每个子域都已准备好 HTTPS 时才添加 includeSubDomains
  • 只有在你有意满足整个域集合的预加载要求时才使用 preload。
  • 仅限回环的本地开发不需要 HSTS。

代理配置示例

Pomerium

Pomerium 通过 x-pomerium-claim-email(或其他声明 Header)传递身份,并在 x-pomerium-jwt-assertion 中传递 JWT。

json5
{
  gateway: {
    bind: "lan",
    trustedProxies: ["10.0.0.1"], // Pomerium 的 IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-pomerium-claim-email",
        requiredHeaders: ["x-pomerium-jwt-assertion"],
      },
    },
  },
}

Pomerium 配置片段:

yaml
routes:
  - from: https://openclaw.example.com
    to: http://openclaw-gateway:18789
    policy:
      - allow:
          or:
            - email:
                is: nick@example.com
    pass_identity_headers: true

Caddy + OAuth

caddy-security 插件的 Caddy 可以认证用户并传递身份 Header。

json5
{
  gateway: {
    bind: "lan",
    trustedProxies: ["10.0.0.1"], // Caddy/sidecar 代理 IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-forwarded-user",
      },
    },
  },
}

Caddyfile 片段:

openclaw.example.com {
    authenticate with oauth2_provider
    authorize with policy1

    reverse_proxy openclaw:18789 {
        header_up X-Forwarded-User {http.auth.user.email}
    }
}

nginx + oauth2-proxy

oauth2-proxy 认证用户并在 x-auth-request-email 中传递身份。

json5
{
  gateway: {
    bind: "lan",
    trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy 的 IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-auth-request-email",
      },
    },
  },
}

nginx 配置片段:

nginx
location / {
    auth_request /oauth2/auth;
    auth_request_set $user $upstream_http_x_auth_request_email;

    proxy_pass http://openclaw:18789;
    proxy_set_header X-Auth-Request-Email $user;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Traefik + Forward Auth

json5
{
  gateway: {
    bind: "lan",
    trustedProxies: ["172.17.0.1"], // Traefik 容器 IP
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "x-forwarded-user",
      },
    },
  },
}

混合 Token 配置

gateway.auth.token(或 OPENCLAW_GATEWAY_TOKEN)与 trusted-proxy 模式同时启用时,OpenClaw 会拒绝该配置,因为混合 token 配置可能导致 loopback 请求走错认证路径而被静默认证。

如果启动时看到 mixed_trusted_proxy_token 错误:

  • 使用 trusted-proxy 模式时删除共享 token,或
  • 如果确实想用 token 认证,请将 gateway.auth.mode 改为 "token"

Loopback trusted-proxy 认证同样采用失败关闭策略:同主机调用方必须通过受信任代理传递配置的身份 Header,而非被静默认证。

Operator 作用域 Header

Trusted-proxy 认证是一种携带身份的 HTTP 模式,因此调用方可以选择通过 x-openclaw-scopes 声明 operator 作用域。

示例:

  • x-openclaw-scopes: operator.read
  • x-openclaw-scopes: operator.read,operator.write
  • x-openclaw-scopes: operator.admin,operator.write

行为说明:

  • 当 Header 存在时,OpenClaw 遵守声明的作用域集。
  • 当 Header 存在但为空时,请求声明 operator 作用域。
  • 当 Header 不存在时,普通携带身份的 HTTP API 回退到标准 operator 默认作用域集。
  • Gateway 认证插件 HTTP 路由默认更窄:x-openclaw-scopes 不存在时,运行时作用域回退到 operator.write
  • 浏览器 origin HTTP 请求即使 trusted-proxy 认证通过后,仍然需要通过 gateway.controlUi.allowedOrigins(或有意为之的 Host-header 回退模式)。

实用规则:

  • 当你希望 trusted-proxy 请求比默认值更窄,或 gateway 认证插件路由需要比 write 作用域更强的权限时,显式传递 x-openclaw-scopes

安全检查清单

启用 trusted-proxy 认证前,确认:

  • [ ] 代理是唯一路径:Gateway 端口对代理以外的所有访问都有防火墙保护
  • [ ] trustedProxies 最小化:只填写你实际的代理 IP,而不是整个子网
  • [ ] 没有 loopback 代理源:trusted-proxy 认证对 loopback 源请求采用失败关闭策略
  • [ ] 代理剥离 Header:你的代理会覆盖(而非追加)客户端的 x-forwarded-* Header
  • [ ] TLS 终止:你的代理处理 TLS,用户通过 HTTPS 连接
  • [ ] allowedOrigins 显式配置:非 loopback 控制 UI 使用显式的 gateway.controlUi.allowedOrigins
  • [ ] 建议设置 allowUsers:限制到已知用户,而非允许任何已认证的人
  • [ ] 没有混合 token 配置:不要同时设置 gateway.auth.tokengateway.auth.mode: "trusted-proxy"

安全审计

openclaw security audit 会将 trusted-proxy 认证标记为严重等级发现。这是故意的——提醒你正在将安全委托给你的代理配置。

审计会检查:

  • 基础 gateway.trusted_proxy_auth 警告/严重提醒
  • 缺少 trustedProxies 配置
  • 缺少 userHeader 配置
  • 空的 allowUsers(允许任何已认证用户)
  • 暴露的控制 UI 入口上的通配符或缺失的浏览器 origin 策略

故障排查

"trusted_proxy_untrusted_source"

请求不是来自 gateway.trustedProxies 中的 IP。检查:

  • 代理 IP 是否正确?(Docker 容器 IP 可能会变化)
  • 你的代理前面是否有负载均衡器?
  • 使用 docker inspectkubectl get pods -o wide 找到实际 IP

"trusted_proxy_loopback_source"

OpenClaw 拒绝了来自 loopback 源的 trusted-proxy 请求。

检查:

  • 代理是否从 127.0.0.1 / ::1 发起连接?
  • 你是否在尝试将 trusted-proxy 认证用于同主机 loopback 反向代理?

修复方案:

  • 对同主机 loopback 代理使用 token/password 认证,或
  • 通过非 loopback 受信任代理地址路由,并在 gateway.trustedProxies 中保留该 IP。

"trusted_proxy_user_missing"

用户 Header 为空或缺失。检查:

  • 你的代理是否配置为传递身份 Header?
  • Header 名称是否正确?(大小写不敏感,但拼写很重要)
  • 用户是否真的在代理处完成了认证?

"trusted_proxy_missing_header_*"

必需的 Header 不存在。检查:

  • 你代理对这些特定 Header 的配置
  • 链路中某处是否在剥离 Header

"trusted_proxy_user_not_allowed"

用户已认证但不在 allowUsers 中。要么添加该用户,要么删除允许列表。

"trusted_proxy_origin_not_allowed"

Trusted-proxy 认证通过,但浏览器 Origin Header 未通过控制 UI origin 检查。

检查:

  • gateway.controlUi.allowedOrigins 是否包含确切的浏览器 origin
  • 你是否在依赖通配符 origin,还是确实有意允许全部
  • 如果你有意使用 Host-header 回退模式,是否显式设置了 gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true

WebSocket 仍然失败

确保你的代理:

  • 支持 WebSocket 升级(Upgrade: websocketConnection: upgrade
  • 在 WebSocket 升级请求上也传递身份 Header(不只是 HTTP)
  • 对 WebSocket 连接没有单独的认证路径

从 Token 认证迁移

如果你正从 token 认证迁移到 trusted-proxy:

  1. 配置你的代理认证用户并传递 Header
  2. 独立测试代理配置(使用 curl 带 Header)
  3. 用 trusted-proxy 认证更新 OpenClaw 配置
  4. 重启 Gateway
  5. 从 Control UI 测试 WebSocket 连接
  6. 运行 openclaw security audit 并审查结果

相关文档