Skip to Content
概念存取模式

存取模式

每個 V2 部署都把五種存取模式全部編譯進去 — 切換模式不需要重新部署、也沒有 feature flag。部門 Worker 的第一次部署起始為 teamsync_internal(Durable Object 的播種列,revision 0):在部門管理者選擇其他模式之前,公開通道一律回 403。播種用 INSERT OR IGNORE,已存有政策的 Durable Object 在後續部署中保留原政策。以 builder 政策 department-worker-project-v2.5 或更新版本建置的 bundle 都播種 teamsync_internal,目前的 department-worker-project-v2.10 也一樣;以 v2.4 或更早政策建置的 bundle 播種的是 public,只有當這種修訂版剛好是部門的第一次部署時才有影響。簽章的 TeamSync 通道在任何模式下都可用。

下表是 Worker 層級政策。以 builder 政策 v2.8 或更新版本建置的 release 上,管理者還能為每個 manifest 操作個別設定五種模式之一,每個操作也能擁有自己的 API 金鑰 — 見逐操作存取。無論模式來自 Worker 層級政策還是某個操作,拒絕狀態碼都相同。

模式公開 dispatcher 准入拒絕狀態碼
public任何滿足有界 HTTP 契約的請求—
api_key需要與儲存摘要吻合的 X-TeamSync-Worker-Key401 access_denied
ip_allowlist用戶端 IP(cf-connecting-ip,由 Cloudflare 設定,地端則由 edge host 設定)必須落在管理者擁有的 IPv4/IPv6 CIDR403 access_denied
api_key_and_ip邏輯 AND — 兩個檢查都要過;絕非 fallback/OR403 access_denied(是的 — 加了允許清單後,壞金鑰的狀態碼從 401 變成 403)
teamsync_internal公開通道整條關閉;只剩簽章的後端通道403 access_denied

在哪裡強制執行

不在 dispatcher。公開通道上 dispatcher 只轉發證據 — 呼叫者的金鑰(若符合 ^tsw_[A-Za-z0-9_-]{43,124}$)轉成 x-teamsync-public-api-key,cf-connecting-ip 轉成 x-teamsync-client-ip。生成的 runtime 讀取自己 Durable Object 的呼叫狀態後決定:

  • 以政策 v2.8 或更新版本建置的修訂版(後端 v5.13.0 起建置的每個修訂版):退役鎖定的 Worker 先回 403 worker_retired;接著 runtime 比對路由,再以比對到之操作的有效政策准入 — 除非操作設定覆寫,否則就是 Worker 層級政策。比對不到的路徑與不符的方法,會在回 404/405 之前先以 Worker 層級政策准入。正在服務之 release 的操作設定為 pending 時,公開通道改回 403 operation_settings_pending。
  • 較舊的修訂版保留建置時的 runtime:只有一份 Worker 層級政策,在選路之前准入。
  • 金鑰檢查:以常數時間比較(crypto.subtle.timingSafeEqual)拿提交金鑰的 SHA-256 對適用的儲存摘要(操作自己的金鑰或 Worker 層級金鑰)— 先做比較,摘要未設定時用全零的 32 位元組替身,所以未設定的金鑰永遠不會誤配,時間也洩漏不了任何資訊。
  • IP 檢查:嚴格的手寫 IPv4/IPv6/CIDR 解析(不接受 zone id、不接受 IPv6 內嵌點分 IPv4),逐位元組的前綴包含判定。

模式↔允許清單的配對在每一層都驗證:ip_allowlist/api_key_and_ip 需要 ≥1 條 CIDR(最多 64;逐操作覆寫每個也最多 64、合計最多 256)、其他模式必須恰好 0 條 — 後端 schema 驗一次、Durable Object 再驗一次,退化的組合根本存不進去。

API 金鑰機制

  • 輪替(POST /worker/access-policy/api-key)生成 tsw_ + secrets.token_urlsafe(32)(47 字元)。明文只回傳一次;只儲存 SHA-256 摘要與後四碼,送到 Durable Object 的也只有摘要+後四碼。明文金鑰在結構上就進不了控制 outbox。
  • Worker 層級輪替僅限舊版:上線 release 以政策 v2.8 或更新版本建置時,它回 409 {"code":"worker_wide_key_legacy_only","message":"Rotate an operation key instead"}。請改為輪替操作金鑰 — 格式相同、header 相同,明文同樣只顯示一次(管理逐操作存取)。
  • 撤銷是冪等的:對未設定金鑰做撤銷回 revoked: false 且不推進 revision(稽核為 access_policy.api_key.revoke_noop)。撤銷 Worker 層級金鑰在每個 release 上都可用。
  • 金鑰自己的前綴 tsw_ 就在提交憑證篩查清單裡 — 你甚至無法把長得像金鑰的字串放進 manifest example。

CAS 管理

所有政策讀寫都是 /worker/access-policy 上的部門管理者操作,且要求 Worker 已部署且 active(否則 409)。寫入帶 base_revision;Durable Object 做 compare-and-swap(revision == base),過期的 base 回 409 Access policy revision is stale。政策物件整體替換 — 永不合併。操作設定則依 release generation 各有獨立的 CAS revision(逐操作存取)。

信任但驗證:每次變更後,後端斷言 provider 回傳的 revision 恰為 base_revision + 1(撤銷 no-op:不變),且 payload 鍵集合恰為 {api_key_configured, api_key_last_four, ip_allowlist, mode, revision, updated_at} — 任何漂移都是 503,不是 409。呼叫期間 release 被換掉時,回帶代碼的 503 {"code":"worker_release_unavailable","message":"Worker release changed; retry this request"}。變更走耐久的控制面稽核 outbox,所以每次改動最終都成為回執背書的稽核事實,回應帶 Cache-Control: no-store。

其他地方的效果

  • 生成的 OpenAPI 即時跟隨模式(每次請求 openapi.json 都重新讀取):金鑰模式 → 安全機制 TeamSyncWorkerApiKey(header X-TeamSync-Worker-Key);teamsync_internal → TeamSyncBearer;public/ip_allowlist → 無。servers[0].url 在公開 dispatcher URL({origin}/public/{script_name})與後端 invoke base(teamsync_internal 時)之間切換。根層級的 x-teamsync-access-policy 擴充欄位只露出 mode 加兩個布林 — 永遠不露 CIDR。操作設定為 reconciled 時,各操作還會帶自己的 security、servers 與 x-teamsync-access-policy(外加 source),components.securitySchemes 也可能同時含兩種機制(逐操作存取)。
  • 公開通道的 CORS 無條件是 access-control-allow-origin: *,且 access-control-allow-credentials 會被主動剝除 — 公開 Worker 永遠做不了帶憑證的 CORS。
  • 公開通道不帶 release generation,所以公開呼叫者永遠打到當下上線的修訂版,也永遠不會看到 release_generation_mismatch。
Last updated on