# 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`](../../../../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`](../../../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/register`、`POST /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，另案。
