Cloudflare Tunnel 与 Access
在 loopback 上运行 Gateway,通过 Cloudflare Tunnel 发布它,并让 Cloudflare Access 在每个请求到达 OpenClaw 之前对其进行身份验证。Gateway 保持 gateway.bind: "loopback",因此不会暴露任何端口,也无需入站防火墙规则;cloudflared 从主机发起出站连接。
这是除 Tailscale 和 SSH 隧道 之外,又一种受支持的远程访问拓扑。当你希望在 Control UI 前获得稳定的公共 HTTPS URL 和身份提供商 SSO 时,可以选择这种拓扑。
开始之前¶
- 一个 Cloudflare 账户,包含你主机名的区域,并已启用 Cloudflare Zero Trust。
- 在 Gateway 主机上以及任何将使用 CLI 的机器上安装
cloudflared。 - 一个运行中的 Gateway,位于
127.0.0.1:18789,并带有gateway.bind: "loopback"。 - 熟悉本拓扑所使用的 受信代理认证。
各部分如何协同¶
Access 对请求进行身份验证并注入身份标头。Gateway 不会重新验证用户身份,也不会验证 Access JWT 签名;它会检查受信代理来源和已配置的标头是否存在,然后信任用户标头。由于 allowLoopback 也允许其他本地进程提供这些标头,因此请将 Gateway 端口保持为主机私有,并且只在该主机上运行受信任的工作负载。
步骤 1:将隧道路由到 loopback¶
添加一条 ingress 规则,将你的主机名映射到 Gateway 端口,然后在 Gateway 主机上将 cloudflared 作为服务运行:
tunnel: <tunnel-id>
credentials-file: /root/.cloudflared/<tunnel-id>.json
ingress:
- hostname: gateway.example
service: http://localhost:18789
- service: http_status:404
请参阅 Cloudflare 官方文档,了解如何创建隧道和 DNS 记录。
步骤 2:使用 Access 保护主机名¶
为 gateway.example 创建一个 Access 应用程序,并设置一个允许你用户访问的策略。请注意 Access 在已验证请求上添加的两个标头,因为 Gateway 会在下一步中使用它们:
cf-access-authenticated-user-email— 已验证的身份。cf-access-jwt-assertion— Access 的签名断言。OpenClaw 仅检查该标头是否存在且非空;它不会验证 JWT 签名。
OIDC 登录与现有人员¶
对于 OIDC 登录,请配置一个 Access 策略,通过该身份提供商允许目标用户访问。例如,使用提供商维护的签名角色声明。GitHub 组织策略适用于 GitHub 身份提供商;它不会通过单独的 OIDC 提供商授予访问权限。
OpenClaw 通过 Cloudflare Access 的身份端点验证 OIDC 身份,并要求其电子邮件与已验证的用户标头匹配。然后,它通过现有的人员资料来解析该电子邮件。使用相同的电子邮件会保留该人员的资料和角色;使用不同的电子邮件则需要借助现有的关联别名才能解析到该人员。GitHub 登录会继续验证不可变的 GitHub 账户 ID。身份验证失败时不会回退到电子邮件匹配。
让身份提供商负责验证电子邮件的所有权。创建 OpenClaw 人员资料并不会授予通过 Cloudflare Access 访问的权限。
通过 OIDC 验证 GitHub 署名¶
OIDC 提供商可以提供已验证的 GitHub 账户,而无需更改登录电子邮件或用于发布拉取请求的账户。这是可选的,并且在你明确信任某个 Access 签发者、身份提供商 ID 和声明名称之前,该功能处于禁用状态:
{
gateway: {
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "cf-access-authenticated-user-email",
requiredHeaders: ["cf-access-jwt-assertion"],
cloudflareAccessOidc: {
issuer: "https://example.cloudflareaccess.com",
providerId: "your-access-identity-provider-id",
githubAccountIdClaim: "https://openclaw.ai/github-account-id",
},
},
},
},
}
issuer 是 Access 团队源,末尾不带斜杠。providerId 是 Access 中所选集成的 ID,而不是其名称或 OIDC 用户主体。提供商必须验证 GitHub 账户的所有权,并将其绑定到已验证的登录电子邮件。其 ID 令牌必须包含一个规范的、正数十进制字符串形式的账户 ID,例如 "12345",且处于 JavaScript 的安全整数范围内。配置 Access 以转发该精确的 自定义 OIDC 声明。OpenClaw 从已验证的 /cdn-cgi/access/get-identity 响应中的 oidc_fields 读取该声明;如果 oidc_fields 不存在,则从 custom 中读取,然后通过 GitHub 验证数字账户,以获取其当前的公开登录名。如果存在 oidc_fields 容器,则以它为准:如果首选容器缺少该声明或其值无效,OpenClaw 不会重试 custom。身份提供商的 Test 预览使用 oidc_fields;请在重新登录后检查已认证的身份端点,以验证 OpenClaw 所消费的响应。
如果可选声明缺失或格式错误,或者签发者/提供商未被选择,则保持普通的仅电子邮件解析。格式错误的声明绝不会发送到 GitHub,也不会用于创建关联或公开署名。如果可选的 GitHub 账户查找失败,OpenClaw 将通过正常的资料和访问策略检查来解析已认证的电子邮件。现有的已授权资料或有效的电子邮件邀请仍然可以登录。仅限 GitHub 的访问要求不能通过电子邮件回退来满足。
查找失败不会创建 GitHub 身份或公开署名。现有的已验证匹配关联和已保存的共同作者偏好保持不变;后续成功的查找可以完成或刷新该关联。此行为同时适用于 oidc_fields 和 custom。无效的 Access 身份验证、主体不匹配、有效账户声明的资料绑定冲突,以及已过期或已撤销的访问权限,仍然无法通过其常规检查。
现有邮箱配置文件会保留其身份、角色以及已保存的合著者偏好。存在冲突的 GitHub 账户不会自动合并配置文件或移动邮箱;管理员必须通过现有的 users.linkEmail 操作来解决。这也适用于首次使用某个邮箱来声明一个已属于其他配置文件的账户:必须先显式关联该邮箱,然后它才能继承该配置文件的角色。显式关联的次要账户会保留配置文件的主账户用于公开署名。参见 Gateway 配置文件与 GitHub 署名。
步骤 3:在 Gateway 中信任这些请求头¶
将 gateway.auth.mode 设置为 trusted-proxy,并指定 Access 请求头。此处必须设置 allowLoopback:cloudflared 从 127.0.0.1 连接,而 trusted-proxy 认证否则要求非回环代理。
{
gateway: {
bind: "loopback",
publicOrigin: "https://gateway.example",
trustedProxies: ["127.0.0.1", "::1"],
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "cf-access-authenticated-user-email",
requiredHeaders: ["cf-access-jwt-assertion"],
allowLoopback: true,
},
},
},
}
要求 cf-access-jwt-assertion 只是增加第二项存在性检查,而不是密码学验证。能够连接到 Gateway 的本地进程可以提交这两个请求头,因此不要将此设置视为对不受信任本地代码的防御。安全边界是受控的回环端口,加上 Cloudflare Access 和隧道作为外部流量的唯一路径。
publicOrigin 提供外部会话链接、代理链接说明,以及在省略 controlUi.allowedOrigins 时的浏览器来源默认值。只有当需要不同的浏览器策略时,才需要显式允许列表;该列表会替换默认值,而不是追加到默认值。有关配置文件、角色、GitHub 和运维设置,请参见 部署团队服务器。
步骤 4:决定节点和工作进程如何接入¶
Access 保护主机名上的每个路由,包括节点使用的路由。节点可以在其所需的每一段路径上向 Access 进行身份验证——加入请求、主 Gateway WebSocket、工作进程套接字以及工作进程传输——因此推荐路径不会公开暴露任何内容。
推荐:为节点提供 Access 服务令牌。 在应用中添加 Service Auth 策略,然后在节点主机上:
export CF_ACCESS_CLIENT_ID="<client-id>"
export CF_ACCESS_CLIENT_SECRET="<client-secret>"
openclaw connect https://gateway.example/j/<code> --service
openclaw connect 会将这些值作为环境变量 SecretRef 持久化到 gateway.cloudflareAccess.clientId / clientSecret 下;参见 节点 CLI。唯一的代价是节点在加入命令之前需要这两个值,因此加入链接本身不再能直接粘贴即用。
替代方案:豁免自认证路由。 允许 /j/* 和 /__openclaw__/worker 无需 Access 身份,并在工作进程路由上保持启用 WebSocket 升级。两者都强制使用各自的短期凭据——加入码是一次性的,具有 TTL,按 IP 限流,并以不透明的 404 响应失败;工作进程准入携带自己的过期凭据。这使加入链接保持粘贴即用,代价是使这两个路由可公开访问。除非你需要该引导流程,否则优先使用服务令牌。参见 节点。
如果两者都不做,即使浏览器可以工作,openclaw connect 也会针对隧道失败,因为加入请求会被重定向到 Access 登录页面。
步骤 5:连接每个客户端¶
Control UI。 打开 https://gateway.example 并通过 Access 登录。在 trusted-proxy 认证下,Gateway 会将你的 Access 身份映射为操作员会话。
如果 Access 在聊天打开期间过期,聊天连接可能保持活动,而新的图像和文件请求需要重新获取网站访问权限。Control UI 首先尝试通过隐藏的沙盒浏览器导航自动续期。如果你的全局 Cloudflare Access 会话仍然有效,并且浏览器允许其 Cookie,Access 可以在无需再次登录的情况下颁发新的应用 Cookie。OpenClaw 会在重试失败的附件之前验证访问权限,从而保持你的对话和未发送草稿处于打开状态。
如果续期仍需要登录,Control UI 会打开一个 登录以继续加载内容 对话框。过期的全局会话、身份提供商质询或被阻止的第三方 Cookie 可能需要此手动步骤。自动续期不会延长在 Cloudflare Access 中配置的会话时长。选择 登录,在新标签页中完成身份验证,然后返回对话。可见的失败附件会在访问权限验证后重试;原始对话和未发送草稿保持打开。再次检查 会重复访问检查,暂不 会关闭提示而不中断聊天。普通网络故障和文件缺失不会触发此对话框。
CLI 和 TUI。 它们不携带浏览器 Cookie,因此会在 WebSocket 升级时出示 Access 令牌。按照 远程访问 中的说明配置 gateway.remote.edgeAuth,然后运行一次 cloudflared access login https://gateway.example 以缓存令牌。
节点。 遵循步骤 4 中做出的选择。
验证¶
预期 TUI 能够到达 wss://gateway.example 并显示 connected。首次连接可能会报告 device pairing required;在 Control UI 的“设置 → 设备”下批准它,或在 Gateway 主机上运行 openclaw devices approve --latest 以预览请求,然后重新运行它打印的批准命令。
能够到达 Gateway 自身的配对提示本身即证明 Access 已满足——未认证请求无法到达那里。
生产就绪¶
- 保持
gateway.bind: "loopback"。绑定到更宽范围会在隧道旁边重新暴露 Gateway,并完全绕过 Access。 - 将
trustedProxies限制为回环地址。它是 Gateway 将信任其身份请求头的地址列表。 trustedProxy.deviceAutoApprove可以为通过 Access 身份验证的身份自动配对设备。它省去手动批准步骤;只有当你接受任何通过 Access 的人都会获得具有你所列范围的已配对设备时,才启用它。- Access 令牌会按应用的会话时长过期。预期 CLI 用户在令牌失效时重新运行
cloudflared access login。
故障排除¶
| 症状 | 原因及解决方法 |
|---|---|
来自 CLI 或 TUI 的 gateway rejected websocket upgrade (HTTP 302) |
Access 拦截了升级请求。请配置 gateway.remote.edgeAuth;参见远程访问。 |
浏览器可用,但 openclaw connect 失败 |
Node 路由仍位于 Access 之后。请应用步骤 4 中的任一选项。 |
Exec provider ... exited with code 1 |
exec 密钥提供程序在清理后的环境中运行;cloudflared 需要 passEnv: ["HOME"] 才能读取其缓存的 token。 |
secrets.providers.*.command must not be a symlink |
将 command 指向已解析的二进制文件,而不是包管理器符号链接。 |
| 网关启动但每个请求都是匿名的 | allowLoopback 未设置,因此来自本地 cloudflared 的标头被忽略。 |
相关¶
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw