FR-039 Agent 認證 v2 — 後端(BE)開發計劃(詳盡版)

repo:compliance-manager-be。依據 design-agent-auth.md v2。 BE 擁有共用契約 + DB migration(A0);FE / Agent 依本文件 §1 契約開發。 全程 AGENT_AUTH_MODE=none 包覆,不破壞現有 demo;全綠才切 full。 已完成:Phase 1 — remote_agentsdevice_fingerprint(=device_uuid) + status(active/revoked)。 📋 進度回填:開工/完成/卡住都更新 TRACKER;動到 §1 契約必在 TRACKER 契約 Log 公告。


§1 共用契約(canonical,FE/Agent 照這個刻)

控制面(agent → cloud)

POST /api/1.0/agents/register(bootstrap:TLS + token,免 mTLS)

// req
{ "token":"<tenant 註冊token明文>", "device_uuid":"<sha256>", "base_url":"http://<agent>:8080",
  "hardware_info":{"hostname":"..","os":"..","ip":".."}, "agent_version":"0.1.0",
  "status":"registering", "csr":"<PEM CSR>" }
// resp
{ "agent_uid":"..", "certificate":"<PEM 簽好的 server cert>", "ca_certificate":"<PEM>",
  "jwt_public_key":"<PEM>", "heartbeat_interval_sec":300 }

POST /api/1.0/agents/heartbeat(mTLS:agent 持註冊時拿到的 cert 當 client cert)

// req { "device_uuid":"..", "status":"online" }   // resp { "status":"ok" }

資料面(cloud → agent,沿用 /blob,加認證)

  • 雲端請求帶 Authorization: Bearer <JWT>;JWT(RS256) claims:iss=compliance-cloud, aud=<agent_uid>, exp≈now+60s, op, tenant_id, bound_fp=<device_uuid>
  • agent 回應帶 X-Agent-Fingerprint: <device_uuid>
  • mTLS:雲端 client cert + 驗 agent server cert(皆對內部 CA)。

Token 管理(cloud,FE 用)

  • GET /api/1.0/agents/enroll-token{exists, label, created_at}(不回明文)
  • POST /api/1.0/agents/enroll-token → 產生/重新產生 → {token:"<明文,僅此次>", endpoint}
  • POST /api/1.0/agents/enroll-token/revoke → 停用

撤銷 / 重新綁定(cloud,FE 用)

  • POST /api/1.0/remote-agents/<uid>/revoke | /unrevoke → 改 status
  • POST /api/1.0/remote-agents/<uid>/rebind → 更新 device_fingerprint(manager;硬體換新後續同一 agent_uid)

§2 任務

A0 · migration + 契約凍結(先做,擋 FE/Agent)

  • migration scripts/sql/2026-06-19-fr039-agent-enroll.sql
    • remote_agentslast_seen_at TIMESTAMP NULLagent_version VARCHAR(50) NULLhardware_info JSONB NULL
    • 新表 compliance.remote_agent_enroll_tokensid, uid, tenant_id, token_hash VARCHAR(64), label VARCHAR(100), enabled BOOLEAN DEFAULT true, created_at/created_user/...;index(tenant_id);GRANT cm_app + sequence;RLS policy 比照 remote_agents(per-tenant);schema_migrations。
    • model/entity/mapper:remote_agents 補三欄;新建 RemoteAgentEnrollToken 全套(model/entity/query/mapper/repo,放 domain|infra/remote_agent/...)。
  • 驗收:migration 套 dev;契約(§1)三 repo 可照刻。

B1 · 憑證 / JWT 基礎 util

  • common/util/agent_auth/ca.py:載入內部 CA(cert+key 路徑來自 config/secret);sign_csr(csr_pem, san) -> cert_pem
  • common/util/agent_auth/jwt_util.pymint(agent_uid, op, tenant_id, bound_fp) -> jwt(RS256,雲端私鑰,exp 60s);(驗證在 agent 端)
  • config:AGENT_AUTH_MODE(none|full)、AGENT_CA_CERT/KEYAGENT_JWT_PRIVATE_KEYAGENT_CLOUD_CLIENT_CERT/KEY 路徑(走 env/secret,不入版控)。
  • 驗收:簽一張測試 cert + mint JWT,單測過。依賴:A0。

B2 · 註冊 token 管理

  • service:generate(tenant)(亂數 token→存 hash→回明文一次)、get_current(tenant)verify(token)->tenant_idrevoke
  • endpoints:§1 token 管理三支。token 存 sha256;明文只在 generate 回傳。
  • 先一組 active UX:generate 時把該 tenant 既有 enabled token 設 false(等於替換);表結構支援多組(未來放寬)。
  • 驗收:產/重生/撤銷;verify 正確回 tenant、停用的 token 驗不過。依賴:A0。

B3 · /agents/register(enrollment)

  • 流程:verify(token)→tenant → 算/收 device_uuid → upsert remote_agents by (tenant_id, device_fingerprint)(寫 base_url/hardware_info/agent_version/last_seen_at=now/status=active) → sign_csr(B1)簽 server cert → 回 §1 resp。
  • before_flush 自動補 tenant_id(已驗過機制)。
  • 驗收:帶有效 token register → DB upsert + 回合法 cert;無效 token → 401。依賴:A0+B1+B2。

B4 · /agents/heartbeat + online/offline

  • heartbeat:mTLS 驗 agent cert → 更新 last_seen_at + status。
  • online/offline helper:is_online(last_seen_at, interval) = now - last_seen < interval * 3(或固定 30 分);清單 / detail 回傳時附 online 布林。
  • 驗收:heartbeat 更新 last_seen;清單算得出 online/offline。依賴:A0。

B5 · 資料面認證(RemoteAgentAdapter)

  • mode=full 時:httpx cert=(client_cert,key) + verify=ca + Authorization: Bearer mint(...) + 讀回應 X-Agent-Fingerprint 比對該 agent device_fingerprint(不符→稽核+擋) + 呼叫前 status != revoked 檢查。
  • mode=none:完全維持現狀。base_url 走 https(mode=full)。
  • 關鍵檔infra/upload_file/remote_agent_adapter.py
  • 驗收:mode=full 對測試 agent 上傳/下載全鏈通;mode=none 不變。依賴:A0+B1。

B6 · 撤銷 / 重新綁定

  • revoke/unrevoke(status)、rebind(更新 device_fingerprint,manager 權限)。
  • 驗收:撤銷後雲端所有呼叫拒;rebind 更新指紋。依賴:A0。

§3 紀律 / 測試

  • DDD:route→app service(@transaction)→domain→repo;不在 route 查 DB。
  • 權限:token 管理 / revoke / rebind 需 manager;register/heartbeat 是 agent 來的(走 token/mTLS,不走 JWT user)。
  • error code:common/code/error_code.py 既有 FILE_AGENT_* 沿用 + 新增 enroll/token 相關(<前綴>_<HTTP><序>)。
  • 憑證/金鑰不入版控;連線資訊文件只寫路徑。
  • 測試在 compliance-manager-test;BE 單測(JWT/CA util、token verify)可加 test/
  • 依賴順序:A0 →(B1∥B2)→ B3;B4/B5/B6 待 A0(+B1 for B5)。