逐操作存取
以 builder 政策 department-worker-project-v2.8 或更新版本建置的 release(截至後端 5.22.0,每次新提交都以 department-worker-project-v2.10 建置)上,部門管理者可以針對每個 manifest operationId 覆寫存取模式與 IP 允許清單,也能讓每個操作擁有自己的 API 金鑰。Worker 層級政策仍是各操作繼承的預設值。逐操作存取首次隨後端 v5.13.0 推出。上線 release 早於 v2.8 的 Worker 只有一份 Worker 層級政策;在重新上傳專案並部署之前,操作設定路由一律回 409 operation_settings_require_supported_release(各政策版本的能力對照表在提交管線)。操作步驟見管理逐操作存取。
一個 Worker 服務不同對象時就用它:public 的讀取操作旁邊放需要金鑰的寫入操作、只允許發送端 CIDR 的 webhook 接收端、維持 teamsync_internal 的操作,或每個整合各發一把金鑰,撤銷其中一把不影響其他整合。
設定模型
僅限部門管理者的 GET/PUT /worker/operation-settings 路由讀取與替換每個 release 的一張表:
| 欄位 | 意義 |
|---|---|
operations[].operation_id | manifest 的 operationId(1–64 字元,^[A-Za-z][A-Za-z0-9_]{0,63}$) |
operations[].access | null = 繼承 Worker 層級政策;或覆寫值 {mode, ip_allowlist},使用同樣五種模式 |
operations[].service_account_id | 此操作 context.teamsync.authHeaders() 的出站身分 — 見服務帳號 |
default_service_account_id | 沒有自身綁定的操作所用的出站身分 |
revision | 此 release generation 那張表的 CAS 權杖 |
state | legacy、reconciled 或 pending(見下表) |
| 狀態 | 意義 | 公開通道 |
|---|---|---|
legacy | Durable Object 從未替任何 release 儲存過操作設定;revision 為 0 | 每個操作都用 Worker 層級政策(v2.8 之前的行為) |
reconciled | 此 release generation 已有設定表 | 各操作的有效政策 |
pending | 其他 release generation 有設定,這一個卻沒有;revision 為 0 | 失敗即關閉:403 operation_settings_pending |
檢視對 manifest 的每個操作各回一筆,依 operation_id 排序,含 method、path、儲存的 access 覆寫值,以及 effective_access {mode, ip_allowlist_configured, api_key_configured, source}。操作有一列覆寫了模式、或該操作現有或曾有自己的金鑰時,source 為 operation,否則為 worker。已知的後端不一致:沒有列、但曾擁有金鑰的操作,在這個檢視中讀到 source: "worker",在生成的 OpenAPI 中卻是 "operation"。操作金鑰的明文、摘要與後四碼提示永遠不會被回傳。
依 release generation 儲存
設定存在部門 Worker 的 Durable Object 裡、與 Worker 層級政策並列,以 release generation(由版本的專案摘要、依賴摘要與 builder 政策推導)為鍵。每個 generation 的表有自己的 CAS revision:PUT 以 base_revision 送出目前的值,成功時恰為 base_revision + 1。
- 部署會在新 release 開始服務之前,先為目標 generation 暫存設定表,並把上線中的列帶過去(規則見管理逐操作存取)。暫存的表從自己的 revision 起算,所以每次部署後、下一次
PUT之前都要重新讀取設定。 PUT與操作金鑰輪替只保留上線中 generation 的表,其他 generation 的表都會刪除;部署暫存則保留上線中與目標 generation 的表,其餘刪除。部署票證處於pending、leased或uncertain時,PUT與輪替都會被拒絕,所以暫存的表不會在部署途中被丟棄。- 撤銷操作金鑰會在每個已儲存的 generation 中清除該操作的金鑰。
有效政策
在公開通道上,生成的 runtime 針對每個比對到的操作,計算正在服務的 release generation 的政策:
| 情況 | 模式與 IP 允許清單 | 檢查的金鑰 |
|---|---|---|
狀態 legacy | Worker 層級 | Worker 層級金鑰 |
reconciled,該操作沒有列 | Worker 層級 | Worker 層級金鑰 — 若該操作曾擁有自己的金鑰則不接受任何金鑰 |
列的 access: null | Worker 層級的模式與 CIDR | 操作現有或曾有自己的金鑰時用它自己的,否則用 Worker 層級金鑰 |
| 列帶覆寫值 | 覆寫值的模式與它自己的 CIDR(不與 Worker 層級清單合併) | 操作現有或曾有自己的金鑰、或覆寫為 api_key/api_key_and_ip 時用它自己的,否則用 Worker 層級金鑰 |
- 金鑰擁有權是永久的。 操作一旦擁有過金鑰,即使撤銷後也不會再退回 Worker 層級金鑰;有效模式為
api_key或api_key_and_ip時,在輪替出新的操作金鑰之前不接受任何金鑰。 api_key或api_key_and_ip覆寫若沒有自己的金鑰,在為該操作輪替金鑰之前會拒絕所有公開呼叫。- CIDR 處理與 Worker 層級政策相同:項目會正規化(
with_prefixlen)、不可重複並排序;ip_allowlist與api_key_and_ip至少要一條 CIDR,其他模式與繼承的列都不帶 CIDR。 - 各模式的拒絕狀態碼不變:
api_key→ 401access_denied(Unauthorized);ip_allowlist、api_key_and_ip與teamsync_internal→ 403access_denied(Forbidden)。
操作 API 金鑰
POST /worker/operation-settings/{operation_id}/api-key產生tsw_+ 43 個 URL-safe 字元(secrets.token_urlsafe(32),共 47 字元),並且只回傳一次,附header_name: "X-TeamSync-Worker-Key"— 與 Worker 層級金鑰用同一個 header。送到 Durable Object 的只有 SHA-256 摘要與後四碼。- 任何管理回應與 OpenAPI 文件都不會顯示操作金鑰的後四碼提示,只有
effective_access.api_key_configured。請自行記錄哪把金鑰交給了哪個整合。 - 輪替要求上線中的表為
reconciled(legacy的 Worker 要先寫一次設定),會立即取代該操作先前的金鑰、把 revision 推進 1,且不改變模式:只有在操作的有效模式為金鑰模式時才會檢查金鑰。 - 撤銷會在每個已儲存的 generation 中清除金鑰並推進 revision;重複撤銷回
revoked: false,且不推進 revision。
與 Worker 層級政策的組合
/worker/access-policy上的 Worker 層級政策仍適用於legacy的 Worker、沒有覆寫值的操作,以及比對不到的路徑。- 上線 release 以政策 v2.8 或更新版本建置時,Worker 層級金鑰輪替僅限舊版:
POST /worker/access-policy/api-key回 409{"code":"worker_wide_key_legacy_only","message":"Rotate an operation key instead"},且只要存在任何操作設定表,Durable Object 也會拒絕。撤銷 Worker 層級金鑰在每個 release 上都仍可用(管理存取政策)。 - 在舊版 release 下產生的 Worker 層級金鑰會跨部署保留,仍可讓從未擁有金鑰的繼承操作通過。第一次部署就是 v2.8 或更新版本的 Worker 永遠無法產生 Worker 層級金鑰,因此 Worker 層級的
api_key或api_key_and_ip模式會拒絕所有沒有自己金鑰的繼承操作。
公開通道的回應
驗證完 dispatch context 並載入呼叫狀態後,依序檢查:
- 退役鎖定(兩條通道)→ 403
worker_retired(Forbidden)— 見退役。 - 路由比對。公開通道上路徑比對不到或方法不符時:狀態
pending→ 403operation_settings_pending;Worker 層級准入 → 401/403access_denied;接著才是 404operation_not_found或 405method_not_allowed。 - 公開通道上比對到操作:狀態
pending→ 403operation_settings_pending(Forbidden);否則以該操作的有效政策准入 → 401/403access_denied。 - 執行 handler。
簽章通道(/worker/invoke 與其他 TeamSync 呼叫端)從不依模式或操作設定准入;它只受退役鎖定影響。
生成的 OpenAPI
- 根層級的
x-teamsync-operation-settings-state{state, revision}一律存在(v2.8 之前的 release 為legacy、0)。根層級的security、servers[0].url與x-teamsync-access-policy仍描述 Worker 層級政策。 - 只有狀態為
reconciled時:每個有設定列或曾擁有金鑰的操作,都帶自己的x-teamsync-access-policy{mode, ipAllowlistConfigured, apiKeyConfigured, source}。若其有效模式與 Worker 層級模式不同、或source為operation,還會帶操作層級的security(teamsync_internal為TeamSyncBearer、金鑰模式為TeamSyncWorkerApiKey、public/ip_allowlist為[]),且當其 base URL 與根層級不同時,帶操作層級的servers(teamsync_internal為後端 invoke base,其他為公開 URL)。每個操作也都帶x-teamsync-service-account(未綁定帳號時為null)。 components.securitySchemes最多兩個機制:Worker 層級機制與各操作機制的聯集。TeamSyncWorkerApiKeyheader 帶操作自己的金鑰;沒有自己金鑰的繼承操作則帶 Worker 層級金鑰。- 設定變更時
ETag就會改變,不需重新部署。產生用戶端的工具必須遵循操作層級的security與servers。
限制
| 限制 | 值 |
|---|---|
| 每張設定表/每個請求的操作數 | 64 |
| 每個操作覆寫值的 CIDR 數 | 64 |
| 所有操作加總的 CIDR 數 | 256 |
任何 /worker/operation-settings 路由的請求本體 | 64 KiB(65 536 B)→ 413 Worker operation settings request exceeds limit |
base_revision | 0 – 9 007 199 254 740 990 |
| 部門層級的設定鎖 | 600 秒後過期 |
稽核
PUT 寫入 operation_settings.replaced、部署暫存寫入 operation_settings.staged、輪替寫入 operation_settings.api_key.rotated、撤銷寫入 operation_settings.api_key.revoked 或 operation_settings.api_key.revoke_noop — resource type 為 edge_worker_operation_settings,只記摘要(輪替記錄金鑰的 SHA-256,絕不記錄金鑰本身)。這些都走耐久的控制面稽核 outbox;見稽核。