T3 Connect

T3 Connect 使用 Clerk 作為雲端身分。Relay 負責管理 environment 的連結(link)、用來連到 environment 的憑證,以及受管 tunnel 的配置。Bootstrap 完成之後,client 的應用流量是透過 environment 的 tunnel hostname 傳送;relay Worker 並不代理它們的 HTTP 或 WebSocket session。

Clerk、部署和原生驗證的設定,都放在 Connect 設定 runbook。

Relay 是受信任的中介者#

一個已通過驗證的雲端使用者,仍然需要有一個有效的 environment 連結。Relay 會要求那個 environment 簽發一份一次性的 bootstrap 憑證,並綁定到 client 的 DPoP key。Client 直接拿它去和 environment 交換一個 environment session。Relay 永遠不會收到那個 session token,而且光是持有 bootstrap 憑證、沒有 client 的私鑰,也無法兌換它。

這次交換的兩邊都會驗證對方。Environment 只接受有期限、有防重播保護的 relay proof,而且這份 proof 必須是針對它自己的身分、已連結的使用者,以及所要求的操作。Environment 簽章過的回應會把結果綁定到請求的 nonce;簽發憑證的回應還會把憑證綁定到 client 的 proof key。Relay 在回傳憑證之前會驗證這些綁定。這可以防止 tunnel 後面另一個行程冒充已連結的 environment。這些檢查落在 environment 的 cloud handler 和 relay connector 這兩處。

Relay 握有簽發請求的簽章權限。DPoP 保護的是一次誠實的交換不被重複使用憑證;它並不會讓「relay 簽章金鑰外洩」這件事變得無害。修改協定時,要把這個信任假設明確寫出來。

受管 tunnel 只會公開一個經過驗證的 loopback HTTP origin。連結 proof 的檢查會拒絕被轉送的 authority header,而 relay 是從它自己管理的配置來解析 endpoint,不是用呼叫端提供的 URL。健康檢查和簽發請求都不可以跟隨重新導向。這些限制是為了避免 endpoint 探索變成任意的 relay 對外連線,或是把 environment 主機上的另一個服務暴露出去。

CLI 授權、期望的公開狀態,以及執行中的 connector,三者的生命週期各不相同。Server 停止時,連結動作仍然可以先把意圖記錄下來。啟動時再依這份意圖做對帳。CLI 登出會移除已儲存的雲端憑證並停用公開,但不會解除安裝 environment 的背景 service。

受管配置屬於一組「使用者/environment」配對。Provisioning 會為外部的 tunnel 和 DNS 資源留下 checkpoint,讓重試時可以對帳只做到一半的工作。由 CLI 管理的連結在正常關機時會釋放它的 tunnel,避免為閒置的資源付費,但會保留 hostname 的預留,供下次啟動使用。它也會保留配置紀錄,這樣 environment 會維持在「offline」,而不是變成「not authorized」。

有兩種情況必須在關機時保留 tunnel。透過 client 安裝的連結沒有啟動時的 provisioning 路徑,要靠它已儲存的 connector token。更新交接則會立刻啟動一個替代的 server,如果換掉 tunnel,每次更新都會多出路由傳播的延遲。這些例外屬於關機處理。

釋放和解除連結(unlink)會先 claim 配置的 generation,再刪除外部資源。延遲執行的清理,不可以刪掉一個已經被同時發生的重新啟動或重新連結再次使用的 tunnel。Unlink 會先提交授權的撤銷,再拆除外部資源,因為資料庫失敗時必須讓有效的連結仍然可用。拆除失敗時會保留足夠的狀態以便重試。參見受管 endpoint 的生命週期。

閒置 tunnel 的回收與復原#

不論有沒有 connector 接著,Cloudflare 都會對 tunnel 計費,所以一台帶著已連結 environment 進入睡眠的筆電,會留下一條要付費的 tunnel。Relay 每五分鐘執行一次的維護工作可以回收這些 tunnel。RELAY_TUNNEL_CLEANUP_MODE 可以選擇 off、dry-run 或 enabled,預設是 off。這個模式是在部署時讀取的,所以要改它就得重新部署 relay,而不是切一下變數就好。回收的候選對象,是同一個 stage 裡、Cloudflare 回報已經 down 至少五分鐘的 tunnel,或是從未連線過、而且建立至少一小時的 tunnel。給從未連線的 tunnel 較長的寬限期,是為了涵蓋仍在進行中的配對。

清理只會刪除 host 已經註冊過復原(recovery)的 tunnel。沒有復原註冊的配置,屬於無法替換被刪除 tunnel 的 host,這些會被放著不動。沒有記錄 tunnel ID 的配置,或是 tunnel ID 不同的配置,都會被跳過,因為可能有一次 provision 正擁有它們。完全沒有配置資料列的 tunnel 會被計為 skippedOrphan,而且絕對不會被刪除:沒有資料列可以上鎖,所以一次用名稱把它接收過去的重新連結,可能會和刪除動作互相競爭。這類 tunnel 要手動清除。每次清掃都有上限:最多十次 list 請求、100 次刪除、兩分鐘的期限,遇到 Cloudflare 的 rate limit 就提早停止。每次清掃會從候選清單往後移一個預算的位置開始,所以一整批一直刪除失敗的項目,不會讓排在它們後面的 tunnel 永遠輪不到。參見 reaper。

Host 在啟動時註冊復原:它送出自己的 tunnel ID 和 loopback origin,並附上一個由 environment key 產生的短效簽章。註冊只有在本機的 host 或 port 改變時才會動到 Cloudflare;另外,升級之後的第一次註冊,每個既有的配置也會各動到一次,因為當時儲存的 origin 是空的。第一次註冊會加上 jitter,這樣一波自動更新不會同時打到 relay。Host 會把一個「已確認 origin」的標記和 connector 設定存在一起;之後開機時,只有在這個標記和目前的設定與 port 相符的情況下,才會在註冊之前先啟動 connector。如果註冊有十分鐘都連不上 relay,host 還是會先用已儲存的設定啟動,並在背景持續註冊,直到能夠把 origin 對帳完成為止。如果 connector 結束了,或是 cloudflared 回報 tunnel 一再被拒絕,host 會向 relay 要求一條替代的 tunnel,最多每兩分鐘一次。Relay 會在同一個配置底下做 provisioning,所以 hostname 和 DNS 紀錄都會保留,client 也能保有它們的綁定。對一個配置的每一次變更都會讓它的 generation 加一,而刪除時會把資料列鎖在它 claim 的那個 generation,所以在清掃途中重新連上的 host 會勝出。

OAuth 的陷阱#

互動式 client 和 headless 的 CLI 使用同一個 Clerk 應用程式,但使用不同的憑證。Relay 同時接受 session-template JWT 和 CLI 的 OAuth token;如果要求 CLI 也使用 JWT template,會把有效的登入擋掉。CLI 是使用 PKCE 的公開 OAuth client,不儲存 client secret。

Loopback 的 CLI 授權是從 hosted 的 /connect 頁面開始,這樣登入會在進入 Clerk 的 authorize endpoint 之前完成。把一個尚未登入的瀏覽器直接送到那個 endpoint,會在登入重新導向的過程中遺失 authorize 參數。共用流程會為 loopback callback 保留 PKCE 和 state。

SSH 和 headless 的 session 使用 Clerk 的 OAuth device authorization grant,因為瀏覽器通常連不到遠端機器上的 listener。使用者在 Clerk 的 hosted 裝置頁面上核准一組短代碼的同時,CLI 會直接輪詢 Clerk 的 token endpoint;hosted app 完全沒有參與,也沒有 redirect URI 或 PKCE。這個 grant 必須在 CLI 的 OAuth 應用程式上啟用,否則 device endpoint 會在顯示任何提示之前就回傳錯誤。

本頁譯自 docs/internals/t3-connect.md(英文原文,版本 367d911)。標示「本站補充」的區塊不在原文裡。

非官方翻譯,與 T3 Code 的維護者無關。內容由 AI 翻譯並補充,未經逐頁人工校對,可能有錯誤或已經過時,請以英文原文為準。

原文 © T3 Tools Inc.,以 MIT 授權釋出;本站為其官方 repository 中 docs/ 的翻譯。本站原始碼與問題回報