FR-039 Cloud ↔︎ Agent 認證設計 v2(自我註冊 + 憑證簽發 + 指紋 + 心跳)

狀態:已實作 + I2 端到端驗證通過(2026-06-19)。身分模型 v3(決策 B)已套用,見 §5(6)。 以 CLIENT_SERVER_REGISTRATION.txt 為準重整;v1(cloud 抓指紋 + ops 預發憑證 + UI 手動註冊)已被取代。

📦 部署一律使用 mTLS(AGENT_AUTH_MODE=full —— none 僅供測試暫時停用認證。 完整部署操作手冊:evidence-agent/deploy/README.md。 資料面 mTLS 的踩雷(httpx 0.28 client 憑證要 SSLContext、下載走檔案自己的 agent、 base_url 必須 https)見 docs/changelog/2026-06-19-fr039-dataplane-mtls-download-i2-fixes.md

已拍板決策

# 決策 定案
1 註冊方式 agent 自我註冊(installer/.env 帶 token + endpoint,agent 主動發第一次心跳註冊)
2 資料面方向 維持 cloud→agent HTTP/blob 沿用,含 SHA-256 對帳);跨外網可達性 = 網路層 tunnel(另案)
3 憑證 雲端 enroll 時自動簽發(agent 送 CSR、雲端 CA 簽 server 憑證回傳);取代 v1 的 ops 預發
4 即時 UI 延後(未來用 SocketIO 推播);現階段管理頁重新整理看狀態
5 註冊 token 每 tenant 一組(可重生/撤銷),installer 內建
6 設備指紋 product_uuid + machine_id + MAC → sha256 = device_uuid;agent 收集後主動送
7 封裝 延後(先 Docker + .env 帶 token/endpoint;之後再包 .msi/.deb,不影響模型)
8 撤銷 status(active/revoked) + JWT 短效 → 標記後幾乎即時失效

1. 架構總覽

            控制面(agent 主動撥出)                    資料面(cloud→agent,沿用)
agent ──register/heartbeat──▶ 雲端                雲端 ──/blob 上傳/下載/刪/預覽──▶ agent
   token + device_uuid          enroll:                (mTLS + JWT + 指紋 + status 檢查)
   + hardware + version         驗 token → upsert
                                → 簽發 agent 憑證回傳
                                → last_seen / online
  • 控制面(註冊 + 心跳):agent 主動撥出 → 永遠通(避防火牆)。
  • 資料面(檔案):維持 cloud→agent(已 ship);跨外網用 tunnel(另案,本設計不含)。
  • 認證:bootstrap 用 token + TLS;穩態用「雲端簽發的 agent 憑證」+ mTLS + JWT + 指紋。

2. 設備指紋 / device_uuid(決策 6)

device_uuid = sha256( product_uuid + "|" + machine_id + "|" + primary_mac )
識別碼 來源 綁定維度
product_uuid /sys/class/dmi/id/product_uuid(SMBIOS) 硬體(主機板);重灌不變、換板才變
machine_id /etc/machine-id(systemd) OS 安裝;重灌就變、換板不變
primary_mac 主網卡 網卡
  • 三者正交(硬體 / OS 安裝 / 網卡)→ 移機、重灌、clone、換網卡 任一情況指紋就變。
  • agent 主動收集後送(附件步驟一);不再由雲端去抓(v1 做法)。
  • Docker 階段:compose 掛 host 識別碼(/sys/.../product_uuid/etc/machine-id 唯讀 + 部署腳本寫 MAC 檔);原生封裝後直接讀,免掛載。
  • 副作用:客戶重灌 OS / 搬機 → 指紋變 → 連線被擋 → 需「重新綁定」(人為明確動作)。

3. 註冊 token(決策 5)

  • 每 tenant 一組 registration token(installer 內建);雲端存 hash
  • 用途:agent 第一次註冊時帶上 → 雲端據此判定歸屬哪個 tenant
  • 可在管理 UI 重新產生 / 撤銷(token 外洩時換一組,舊 installer 失效)。

4. 憑證自動簽發(決策 3,附件步驟三)

① agent ──HTTPS(雲端 server cert) + token──▶ 雲端          ← bootstrap:只靠 token + TLS
② 雲端驗 token → tenant 確認 → agent 產 keypair 送 CSR
   → 雲端 CA 簽 agent server 憑證(SAN=agent host/IP)→ 回傳
③ 之後 agent 用此憑證跑 HTTPS server;資料面走 mTLS + JWT   ← 穩態
  • 雲端側一次性:CA、雲端 client 憑證、JWT 簽章私鑰(RS256)放雲端 secret。
  • 每台 agent:憑證自動簽發(不用 ops 手配);agent 持 server cert+key + CA + JWT 公鑰。
  • 輪替/撤銷:cert 有效期 + status=revoked(雲端拒呼叫)+ JWT 短效。

5. 完整流程

(1) 安裝 / 初始化(附件步驟一)

installer(現階段 Docker + .env)帶:雲端 endpoint + 該 tenant 的 registration token。agent 啟動算 device_uuid。

(2) 第一次心跳 = 註冊(附件步驟二)

agent ──HTTPS POST /api/1.0/agents/register──▶ 雲端
  payload: { token, device_uuid, base_url, hardware_info, agent_version, status:"registering" }

(3) 雲端 enroll + 綁定 + 簽憑證(附件步驟三)

驗 token → 對應 tenant
Upsert by (tenant_id, device_uuid):有則更新、無則新增一列進 remote_agents
回傳:簽好的 agent 憑證(+ CA);UI 上該設備自動出現(重新整理可見)

(4) 後續心跳(附件步驟五)

agent ──每 N 分鐘 POST /api/1.0/agents/heartbeat { device_uuid, status }──▶ 雲端
雲端更新 last_seen_at;超過門檻(如 30 分)未收到 → 視為 offline

即時 UI 延後:online/offline 由 last_seen_at 推算,管理頁重新整理時顯示。

(5) 資料面(沿用,cloud→agent)

雲端用 agent 回報的 base_url/blob*:mTLS(雲端 client cert + 驗 agent cert)+ JWT(帶 bound device_uuid)+ agent 驗指紋 + status != revoked

(6) 重新綁定 / 撤銷

⚠️ v3 修訂(2026-06-19,決策 B):身分主鍵改為伺服器發的 agent_uid,rebind 已退場。 詳見 docs/analysis/2026-06-19-fr039-agent-identity-v3-uid-vs-fingerprint.md

  • 身分 = agent 持久化的 agent_uid(雲端首次發);device_fingerprint 降為可更新屬性(attestation)。
  • 換硬體 → register/heartbeat 依 agent_uid 自動更新指紋 + 記稽核(FILE_AGENT_FINGERPRINT_CHANGED),不冒新列、不需人工 rebind
  • 平台刪掉該 agent → 心跳回 404 → agent 自動重新 enroll(換新 uid)。對齊 osquery/Teleport/SPIRE 主流。
  • rebind endpoint 留 dormant(FE 不放鈕);下方原 v2 敘述保留供脈絡。
  • 重灌/搬機 → device_uuid 變 → 連線/心跳對不上 → admin 確認後「重新綁定」(manager)。(v3 退場,改自動更新)
  • 設備可疑 → 「撤銷」設 status=revoked → 雲端所有呼叫即拒(JWT 短效,幾乎即時)。(不變)

6. DB Table 異動

compliance.remote_agents 加欄(Phase 1 已加 device_fingerprint/status;本版再加)

欄位 型別 說明
device_fingerprint VARCHAR(64) = device_uuid(已加,Phase 1)
status VARCHAR(20) active / revoked(已加,Phase 1)
last_seen_at TIMESTAMP NULL 最後心跳時間;online/offline 由此推算
agent_version VARCHAR(50) NULL agent 回報版本
hardware_info JSONB NULL agent 回報硬體細節(稽核用)

base_url 改為 agent 註冊時回報(不再 UI 手打);uid 仍是 registry 內部 id,upsert 鍵 = (tenant_id, device_fingerprint)

新表 compliance.remote_agent_enroll_tokens(per-tenant 註冊 token)

欄位 型別 說明
id / uid PK / 識別
tenant_id INTEGER 歸屬租戶
token_hash VARCHAR(64) token 的 sha256(不存明文)
label VARCHAR(100) 備註
enabled BOOLEAN 是否有效(撤銷=false)
created_at/user 審計

agent 端 DB(fileagent.upload_files)不變。


7. 各 repo 改動範圍

內容
♻️ 重用 資料面 /blob(上傳/下載/刪/預覽/SHA-256 對帳)、remote_agents registry、storage-config、Phase 1 的 device_fingerprint/status
🔧 註冊:UI 手打 base_url → agent 自我註冊 + upsert;憑證:ops 預發 → 雲端 enroll 簽發;指紋:雲端抓 → agent 送
新增(cloud BE) POST /agents/registerPOST /agents/heartbeat、token 管理(表 + CRUD + 重生/撤銷)、CA 簽發 util(CSR→cert)、JWT util(RS256)、last_seen/online 推算、撤銷/重新綁定 endpoint
新增(evidence-agent) 開機算 device_uuid、自我註冊 + 心跳迴圈、存放雲端回傳的憑證、資料面驗 JWT + 指紋 self-check、nginx/TLS
➕ **新增(FE) 設備清單(自動出現/狀態欄 online-offline-active-revoked)、token 管理頁、重新綁定/撤銷鈕(manager)—— 加在現有管理頁旁,不取代**
⏸️ **退場(先保留,不做) UI 手動新增 agent、現有「測試連線 / 對帳」全部留著**;自我註冊與其並存,未來再決定是否退場

8. 安全上限(誠實)

  • 指紋為 agent 軟體自報 → 擋一般憑證盜用(複製到別台跑原廠 agent,product_uuid/machine_id 不符);擋不住完全控制機器 + 改 agent 原始碼偽造指紋。要更強需 TPM(未來)。

9. 延後項

  • 即時 UI(SocketIO 推播):未來有需求再做,現階段重新整理。
  • 封裝(.msi/.deb installer):最後做,只影響「指紋讀取 / TLS 終結」末端細節,不影響模型。
  • 資料面跨外網:網路層 tunnel,另案。