T3 Code 非官方中文翻译不是官方产品,也不是 T3 Tools 官方文档。源提交 d2c25950b4d9英文原文GitHub 源文件
Skip to content

T3 Connect ​

T3 Connect 用 Clerk 做云端身份。中继(relay)管理环境(environment)链接、到达环境所需的凭据,以及受管隧道的分配。引导完成之后,客户端(client)经环境的隧道主机名发送应用流量;中继的 Worker 不代理它们的 HTTP 或 WebSocket 会话。唯一的例外是自动化 webhook:中继把 /v1/hooks/:endpointKey/:hookId/:token 转发到环境的隧道,使发送方得到一个稳定的 URL。endpointKey 是受管隧道名称的 16 位十六进制哈希后缀,并不是环境 id。它不把请求体和令牌写进自己的追踪,令牌与签名的校验留给环境(转发器)。

转发器默认什么都不存储。环境可以选择,在自己离线时由中继暂存请求(其链接上的 hold_webhooks_while_offline)。只有这时,中继才会把原始请求存入一个带 SQLite 存储的 Durable Object,路径中的 hook 令牌也一并保存。每个受管链接对应一个对象,以端点键为键:链接该环境的每个账号,都有自己的暂存请求、顺序和上限。该对象从自己的 alarm 出发,把暂存请求经隧道推回,同一个 hook 内按最旧的优先,环境一直不在时就退避。某个 hook 若应答为忙,这一轮剩余时间内会被跳过,于是同一条链接上的其他 hook 可以超过它。隧道重新连上时,环境会要求中继立刻向自己的每一个链接投递。请求在环境作出应答后删除,或在 24 小时后、其链接被解除时、环境关闭暂存时删除;关闭暂存会清空它证明过的每一个链接。两次清空都是尽力而为:解除链接时,清空失败会记一条日志并继续;关闭暂存时,会停在第一个无法清空的链接上,那些请求便等到 24 小时过期。中继始终不检查该令牌;投递走的是同一条环境路由。每一次转发都携带 x-t3-relay-delivery-id,因此一个在超时之前已经到达环境、稍后又被再次投递的请求只会运行一次(收件箱对象)。

这里用 Durable Object,不用 Postgres,也不用 Queues,因为暂存请求是最多 1 MiB、写一次、读一次的请求体,并且需要按链接维持顺序、上限和重试时机。Queues 把消息限制在 128 KB,也不能在环境离开时单独扣住某一个链接的请求。

Clerk、部署和原生认证的搭建,写在Connect 设置运行手册。

中继是受信任的中间人 ​

已经通过认证的云用户,仍然需要一条有效的环境链接。中继要求该环境签发一份一次性引导凭据,绑定到客户端的 DPoP 密钥。客户端直接拿它与环境交换,换取一份环境会话。中继从不收到该会话令牌,只持有引导凭据,也不足以在没有客户端私钥的情况下把它兑换出来。

这次交换的双方都会做认证。环境只接受有界、防重放,并且针对其自身身份、已链接用户和所请求操作的中继证明。经过签名的环境响应把结果绑定到请求的 nonce;签发响应还会把凭据绑定到客户端的证明密钥。中继在返回凭据之前核对这些绑定。这样可以防止隧道背后的另一个进程冒充已链接的环境。这些检查汇合于环境链接服务和中继连接器。

签发请求的签名权在中继手里。DPoP 保护一次诚实的交换,使凭据不能被拿去重用;中继的签名密钥一旦泄露,它并不能让这件事变得无害。修改该协议时,要把这一信任假设写明确。

受管隧道只暴露经过校验的回环 HTTP 源站。链接证明检查会拒绝被转发的 authority 头,中继只根据自己的受管分配解析端点,不采用调用方提供的 URL。健康检查请求和签发请求都不得跟随重定向。这些限制使端点发现不会变成任意的中继出站访问,也不会把环境主机上的另一项服务暴露出去。

CLI 授权、所期望的暴露,以及一个正在运行的连接器,寿命各不相同。服务器处于停止状态时,链接仍然可以记下这一意图。启动时会把该意图调和过来。CLI 登出会移除已存储的云凭据并关闭暴露,同时不卸载该环境的后台服务。

受管分配属于一对用户与环境。配置过程会为外部隧道和 DNS 资源建立检查点(checkpoint),以便重试时把只完成一部分的工作调和齐。由 CLI 管理的链接在正常关闭时会释放隧道,以免继续为空闲资源付费,同时保留主机名预留,供下次启动使用。分配记录也会保留,使环境仍然处于「offline」,而不会变成「not authorized」。

有两种情况必须在关闭之后仍然保留隧道。经客户端安装的链接没有启动时的配置路径,依赖其已存储的连接器令牌。更新交接会立刻启动一台替换服务器,此时更换隧道会给每一次更新都加上路由传播延迟。这些例外属于关闭处理。

释放和解除链接都会先认领分配的世代,然后才删除外部资源。迟来的清理不得删除一条已被并发的重启或重新链接复用的隧道。解除链接会先提交授权撤销,再做外部拆除,因为数据库一旦失败,这条仍然有效的链接必须还能使用。拆除失败会留下足够重试的状态。参见受管端点生命周期。

空闲隧道会被回收,也会被恢复 ​

无论是否连着连接器,Cloudflare 都会为隧道计费,因此一台在环境仍处于链接状态时进入睡眠的笔记本电脑,会留下一条仍在计费的隧道。中继每五分钟一次的维护任务可以回收这些隧道。RELAY_TUNNEL_CLEANUP_MODE 用来选择 off、dry-run 或 enabled,默认是 off。该模式在部署时读取,因此更改它意味着重新部署中继,而不是翻转一个变量就能生效。

候选者是同一阶段(stage)中、被 Cloudflare 报告为 down 至少五分钟的隧道,或者从未连接、并且至少已经存在一小时的隧道。从未连接的隧道使用更长的宽限,以便覆盖仍在进行的配对。

清理只删除其主机已经注册恢复的隧道。没有恢复注册的分配属于那些无法替换被删除隧道的主机,会原样留下。没有记录隧道 ID,或者隧道 ID 不一致的分配会被跳过,因为某次配置可能正占用它们。一条完全没有分配行的隧道计为 skippedOrphan,并且从不删除:没有行可以锁定,一次按名称接管它的重新链接就可能与删除发生竞争。这类隧道请手工清除。每一轮清扫都有界限:最多十次列表请求、100 次删除、两分钟的截止时间,并且一遇到 Cloudflare 速率限制就提前停止。每一轮清扫的起点都沿着候选列表再前进一个预算的距离,因此一整段一直失败的删除不会把排在后面的隧道饿死。参见回收器。

主机在启动时注册恢复,用来自环境密钥的短时签名发送自己的隧道 ID 和回环源站。只有本地主机或端口发生变化时,注册才会访问 Cloudflare;升级之后的第一次注册还会对每个已有分配访问一次,因为当时存储的源站是空的。首次注册会在时间上错开,以免一轮自动更新同时打到中继。主机把已确认源站的标记与连接器配置存在一起,以后的启动只有在该标记与当前配置和端口一致时,才会在注册之前先启动连接器。如果注册持续十分钟无法到达中继,主机仍然会按已存储的配置启动,并在后台继续注册,直到能够把源站调和一致。如果连接器退出,或者 cloudflared 报告隧道被反复拒绝,主机就会向中继请求一个替换,最多每两分钟一次。中继在同一条分配之下进行配置,因此主机名和 DNS 记录都保留下来,客户端也保持原有绑定。对一条分配的每次变更都会把它的 generation 加一,删除则会把该行锁在自己所认领的世代上,因此在清扫进行到一半时重新连上的主机会胜出。

付费的 T3 Chat 方案会提高隧道上限 ​

T3 Chat 使用 WorkOS,不使用 Clerk,因此两个账号是经用户同意之后关联的,并不是因为它们共用同一个身份。中继与 T3 Chat 共用一个密钥(这边是 T3CHAT_LINK_SECRET,那边是 T3CODE_LINK_SECRET)。中继签署一份短时令牌,其中写明 Clerk 用户和电子邮件;用户在已登录 T3 Chat 时确认它,由 T3 Chat 存下这条链接。链接和方案归 T3 Chat 所有,因此中继不保留副本。只有当某个账号为了新建环境而达到正常上限时,中继才会询问 T3 Chat;只要询问失败,就继续使用正常上限。已经占有一个计入名额的环境在重启时不再询问,因此降级或 T3 Chat 中断只会挡住新建的环境。一行手动覆盖会替换新环境所使用的默认值,T3 Chat 的方案也不会提高这一覆盖值(限额)。

中继客户端跟随服务器的钉选 ​

主机运行的是 cloudflared,由 CLOUDFLARED_VERSION 钉选,写在 relayClient.ts 中;在那里把它更新上去(版本、URL 和校验和)就是全部的发布步骤。一台已经链接的主机若用新的钉选启动服务器,会让连接器继续运行在最新的那一份更旧的受管发布上,在后台安装被钉选的那一份,然后在同一条隧道上为它再启动第二个连接器。旧连接器要等到新连接器注册之后才停止,因此这次切换从不会让主机离开中继。如果新的连接器没能启动,旧的继续提供服务,安装稍后重试。主机上如果还没有中继客户端,会先安装一个。cloudflared 出现在 PATH 上,或者一个显式的 T3CODE_CLOUDFLARED_PATH,属于用户自己的选择,从不会被替换,因为建立链接时会先询问,然后才下载。

被钉选的发布一旦注册上一条连接,比该钉选更旧的受管发布都会删除,只留下其中最新的一份。留下的这一份,以及任何更新的发布,可能属于共用同一个 T3 home 的另一台服务器,例如一次失败更新之后的回滚;没有它,那台服务器就无法再次运行。

版本来自运行 cloudflared version 的结果,不来自文件夹名称。--no-autoupdate 之前的连接器会就地替换自己,因此一个受管文件夹里可能存放着另一份发布。钉选版本还在安装时,这样的二进制仍然作为后备运行。每一个二进制都需要 CLOUDFLARED_MIN_VERSION,也就是能够接受连接器启动时所带全部标志的最旧发布。每增加一个新标志,就把它提高。

OAuth 陷阱 ​

交互式客户端与无头 CLI 使用同一个 Clerk 应用,凭据却不同。中继同时接受会话模板 JWT 和 CLI 的 OAuth 令牌;如果要求 CLI 使用 JWT 模板,就会拒绝有效的登录。CLI 是使用 PKCE 的公开 OAuth 客户端,不存储客户端密钥。

回环 CLI 授权从托管的 /connect 页面开始,以便在进入 Clerk 的 authorize 端点之前先完成登录。把一个尚未登录的浏览器直接送到该端点,authorize 参数会在登录重定向过程中丢失。共享流程 会为回环回调保留 PKCE 和 state。

SSH 与无头会话使用 Clerk 的 OAuth 设备授权授予,因为浏览器通常无法到达远程机器上的监听器。用户在 Clerk 托管的设备页面上批准一个短码期间,CLI 直接轮询 Clerk 的令牌端点;托管应用不参与其中,也没有重定向 URI 或 PKCE。必须在 CLI 的 OAuth 应用上启用该授予,否则设备端点会在显示任何提示之前返回错误。

非官方译文 · 源提交 d2c25950b4d9 · 不代表 T3 Tools