# FR-057 OpenSCAP 掃描工具接入（SSH 連線型態）— 設計文件

> 狀態：設計定案（D1–D7 已拍板）｜建立日期：2026-07-28｜FR-056 檢測工具整合平台續作
> 討論稿（含流程圖）：[`discussion.html`](./discussion.html)

## 變更紀錄

| 日期 | 變更 | 對應 |
|------|------|------|
| 2026-07-28 | 初版設計定案，D1–D7 全數拍板 | FR-057 母案 |
| 2026-07-29 | D3 實作澄清：官方 `oscap-ssh` 腳本只支援「本機 content 推送到遠端」，與 D3「用目標自帶 SSG」矛盾（T-2.2 實作時發現）。定案：connector 自組 SSH 指令直接在目標上執行 oscap 引用目標本地 content（oscap-ssh 本身僅 ~200 行 shell wrapper，自組成本低；取消/逾時本就 connector 自管），report 寫遠端 mktemp 暫存、拉回後清理。D3 核心價值（目標側 content、無平台版本矩陣）不變。 | T-2.2 / D3 |

---

## 1. 需求背景與目標

FR-056 檢測工具整合平台第一版只接了 OpenVAS（API 型）。本案新增第二個工具 **OpenSCAP**——開源組態合規稽核工具（CIS / STIG benchmark），並藉此開出 connector 架構的第三種連線型態 **SSH**（既有 API / CLI 之外）。

### 為什麼是 SSH 遠端掃描（設計前提，已查證定案）

- OpenSCAP（`oscap`）是純 CLI，**無 server API、無 daemon**。組態稽核要讀目標主機系統內部（設定檔 / 套件庫 / sysctl），必須以登入身分在目標 OS 上執行——這是物理限制，全業界一致。
- 官方遠端模式是 **`oscap-ssh`**：發起端 SSH 登入目標 → 推送 content → 在目標上執行目標自己的 `oscap` → 把報告拉回。Red Hat Satellite / Foreman 大規模採用同一模式。
- **Agent 維持 Docker 形態不變**：Agent 是掃描發起端（站點調度器），container 內只需 `openssh-client` + `oscap-ssh` 腳本；真正的掃描引擎（`openscap-scanner` 套件）在各目標主機上。
- Nessus / Qualys 能「零安裝」是因為自造檢查引擎（商業核心資產）；OpenSCAP 開源專案沒有這層，目標裝 `openscap-scanner` 是選用 OpenSCAP 的隨附條件（幾 MB CLI 工具、非常駐、不開 port）。

### 端到端流程

```
租戶設定 OpenSCAP（SSH 稽核帳號 + 金鑰/密碼，加密存放）
  → 任務設「檢測工具執行」選 OpenSCAP + 填 hosts/profile
  → 按「開始執行任務」
  → Agent 心跳領到派工（含解密憑證）
  → Agent 內 oscap-ssh 逐台登入目標主機執行掃描
  → 每台一份 HTML 報告拉回 Agent → 上傳 → 回報 result（多檔）
  → 雲端逐檔取回存 job_evidences（source=DETECTION_TOOL）
  → 通知負責人 → 依完成模式收尾
```

---

## 2. 決策定案（D1–D7）

| # | 決策 | 定案 |
|---|------|------|
| **D1** | SSH 憑證放哪層 | **租戶工具設定層一組共用稽核帳號**（方案 A）。業界標準做法——Tenable 官方《Credentialed Checks on Linux》明文：每台目標建同名專用掃描帳號。沿用 `tenant_detection_tool_configs` 加密鏈 + D9 派工下發，零新表零新機制。任務層帶憑證（明文落 DB）排除；host inventory 表（每台各自憑證）列未來，資料模型可平滑升級。 |
| **D2** | 認證方式 | **密碼 / SSH 私鑰二擇一，推薦金鑰**（Tenable 同款建議：金鑰更安全、私鑰只存 scanner 端）。另設 `use_sudo` 布林——完整掃描需 root 級讀取，企業普遍禁 root 遠端登入，「一般帳號 + sudo 提權」是標準模式（`oscap-ssh --sudo`）。 |
| **D3** | SCAP content 來源 | **用目標主機自帶的 SSG**（`scap-security-guide` 套件，`/usr/share/xml/scap/ssg/content/`），天然匹配該機 OS 版本。任務參數 = content 路徑（預設慣例路徑可改）+ profile（常用 CIS/STIG 下拉 + 自由輸入）。平台統一管理 content 列未來（版本矩陣維護成本高、錯配風險）。**實作註（2026-07-29）**：不經 `oscap-ssh` 官方腳本（其語意為本機 content 推送，與本決策矛盾），connector 自組 SSH 指令在目標上執行 oscap 引用目標本地 content，詳見變更紀錄。 |
| **D4** | 證據格式 | **HTML 原樣上傳**（`oscap` 官方人讀格式，無原生 PDF）。summary 從 stdout 規則統計解析（pass/fail/notapplicable）。不轉 PDF（LibreOffice 轉複雜 HTML 排版易爛）；ARF XML 結構化 findings 留未來 FR（同 FR-056 D4 邏輯）。 |
| **D5** | 多目標處理 | **逐台掃、每台一個證據檔**（檔名含主機名），summary 加總。不硬拼單一 HTML。⚠️ 牽動回收鏈：現況 `result_ref.upload_uid` 是單數，需擴成多檔（見 §5 FR-057.3）。 |
| **D6** | 部署前提呈現 | **設定頁內建前提說明 + 一鍵複製目標主機準備腳本**（裝 `openscap-scanner`+`scap-security-guide`、建稽核帳號、佈公鑰、sudoers），加 **probe 實地檢查**（SSH 上去驗 oscap / SSG content 存在性，缺什麼報什麼）。只寫手冊（被動）不採。 |
| **D7** | 不做範圍（第一版） | Push 模式（目標自掃回報，Foreman 式）——與「按開始→即時派工」任務模型衝突；host inventory 表；ARF findings 解析；content 平台管理。 |

---

## 3. 現況接入點盤點（FR-056 已鋪好的軌道）

| 元件 | 現況 | FR-057 動作 |
|------|------|------|
| `config.detection_tools` | OpenVAS(available) / Nessus / SonarQube(coming_soon)；`connection_type` 註解只列 API/CLI | seed 第 4 筆 `openscap`（`connection_type='SSH'`，新型態值）+ `config_field_schema` |
| `config.detection_tool_param_schemas` | OpenVAS 一筆（hosts/timeout_sec/...） | seed OpenSCAP param schema |
| `tenant_detection_tool_configs` + Fernet 加密 | 完備 | 直接沿用（`private_key` 進 credentials 密文） |
| 派工鏈（心跳夾帶 + 憑證解密下發） | 完備（D9） | 直接沿用 |
| Agent `DetectionConnector` base | `run()` / `probe()` / `cancel_event` | 新增 `openscap.py` 實作 |
| Agent factory `_TOOL_ID_TO_CODE` | `1=openvas, 2=nessus, 3=sonarqube` 固定映射 | 加 `4: "openscap"`（seed 順序須對齊） |
| Agent Dockerfile | LibreOffice + curl | 加 `openssh-client` + `openscap-utils`（拿 oscap-ssh） |
| result 回收鏈 | `result_ref.upload_uid` **單數**；`detection_result_handler` 取單檔寫 `job_evidences` | **擴多檔**：`upload_uids` 陣列 + 逐檔迴圈（向下相容單數 key） |
| FE 設定頁 / 任務參數 | schema 驅動動態渲染 | 欄位型態需擴：`textarea`（私鑰）、`select_or_text`（profile）、條件顯示（auth_method 切換）、說明區塊 + 複製腳本 |

---

## 4. 詳細設計

### 4.1 租戶設定欄位（config_field_schema）

| key | label | type | required | secret | 說明 |
|-----|-------|------|----------|--------|------|
| `username` | 稽核帳號 | text | ✅ | — | 各目標主機同名帳號（業界慣例） |
| `auth_method` | 認證方式 | select(password/private_key) | ✅ | — | 驅動下兩欄條件顯示 |
| `password` | SSH 密碼 | password | 條件 | ✅ | auth_method=password 時必填 |
| `private_key` | SSH 私鑰 | textarea | 條件 | ✅ | auth_method=private_key 時必填（OpenSSH PEM） |
| `ssh_port` | SSH 連接埠 | number | — | — | 預設 22 |
| `use_sudo` | 使用 sudo 提權 | boolean | — | — | 預設 true（完整掃描建議） |

secret 欄位（password/private_key）進 `credentials_encrypted` 密文；其餘進 `field_values`。

### 4.2 任務參數（param_schema）

| key | label | type | required | 說明 |
|-----|-------|------|----------|------|
| `hosts` | 掃描目標 | text | ✅ | 逗號/空白分隔多台（同 OpenVAS 慣例，connector 邊界正規化） |
| `content_path` | SCAP content 路徑 | text | — | 預設 `/usr/share/xml/scap/ssg/content/`（目標主機上的 SSG 慣例路徑），可填完整檔案路徑 |
| `profile` | 掃描 Profile | select_or_text | ✅ | 下拉常用（CIS L1/L2 Server、STIG）+ 自由輸入 xccdf profile id |
| `timeout_sec` | 逾時秒數 | number | — | 預設 3600，逐台計 |

content_path 語意：填目錄時 connector 依目標 OS 推斷 `ssg-<os><ver>-ds.xml`；填檔案路徑時直接用。第一版先實作「必填完整檔案路徑或用預設推斷」中較簡單的一種，plan 階段定案。

### 4.3 Agent connector（`core/task_executor_connectors/openscap.py`）

- **`run()`**：解析 hosts → 逐台自組 SSH 指令執行 `ssh <user>@<host> [sudo] oscap xccdf eval --profile <p> --report <遠端 mktemp 暫存> <目標上的 content 路徑>` → 拉回 HTML 報告 bytes（scp/cat）→ 清理遠端暫存 → 解析 stdout 規則統計 → 回傳多檔 ScanResult（見 4.5）。不經 `oscap-ssh` 官方腳本（其語意為本機 content 推送，與 D3 矛盾，見變更紀錄 2026-07-29）。私鑰認證：憑證中的 private_key 寫入記憶體型暫存（`/dev/shm` 或 tmpfile 0600），掃完即刪，不落地持久化。
- **`probe()`**：SSH 連線 + 認證 → 遠端執行 `command -v oscap` + SSG content 路徑存在性檢查 → 缺什麼在錯誤訊息中明確分類（連不上 / 認證失敗 / 缺 oscap / 缺 content）。與 run() 共用連線參數解析（單一真相，同 OpenVAS 模式）。
- **取消 / 逾時**：SSH 指令是子行程——輪詢 `cancel_event`，取消時 terminate 子行程 raise `ScanCancelledError`；逾時逐台計時 raise TimeoutError。
- **oscap 退出碼**：`0`=全 pass、`2`=有 fail（**掃描成功**，報告有效）、`1`=執行錯誤。2 不可誤判為失敗。

### 4.4 exit code 與 summary

stdout 每條規則一行 `Result: pass/fail/notapplicable/...`，彙總成：

```json
{"findings": <fail數>, "pass": n, "fail": n, "notapplicable": n, "error": n, "hosts_scanned": n}
```

解析失敗不拖垮派工（同 OpenVAS `_parse_summary` 原則：證據到手 summary 可缺）。

### 4.5 多檔證據回收鏈擴充（跨 agent + BE）

- Agent `task_executor`：逐檔上傳 blob → `result_ref` 從 `{"upload_uid": x}` 擴為 `{"upload_uids": [{"uid": x, "filename": f}, ...]}`；**保留單數 key 讀取相容**（OpenVAS 路徑不動也不壞）。
- BE `detection_result_handler`：讀 `upload_uids`（fallback `upload_uid`）逐檔 mTLS GET `/blob/{uid}` → 逐檔寫 `job_evidences`。單檔失敗記 warning 續處理其餘（部分成功優於全失敗）。
- 檔名：`OpenSCAP掃描報告_<host>_<日期>.html`。

### 4.6 部署前提（設定頁說明 + 準備腳本）

`detection_tools` 加 `setup_guide` 欄位（TEXT，markdown），FE 設定頁渲染 + 腳本區塊一鍵複製。腳本內容（範例，deb/rpm 雙版）：

```bash
# 每台目標主機執行一次（Ubuntu/Debian）
sudo apt install -y openscap-scanner ssg-base ssg-debderived   # RHEL 系: openscap-scanner scap-security-guide
sudo useradd -m -s /bin/bash audit-scan
echo '<公鑰>' | sudo tee /home/audit-scan/.ssh/authorized_keys  # 目錄權限步驟略，見完整腳本
echo 'audit-scan ALL=(root) NOPASSWD: /usr/bin/oscap' | sudo tee /etc/sudoers.d/audit-scan
```

---

## 5. 拆分（3 子需求 × 8 子任務）

依賴鏈：**FR-057.1（BE 地基）→ FR-057.2（Agent connector）∥ FR-057.3（多檔回收鏈）**，.2/.3 可並行（.3 不依賴 openscap connector 本身，用假多檔即可測）。

### FR-057.1 BE：SSH 型態 + OpenSCAP 目錄與設定頁 — 依賴：無

| # | 子任務 | 驗收 | 依賴 | Repo |
|---|--------|------|------|------|
| T-1.1 | migration：seed openscap（connection_type='SSH' + config_field_schema）+ param_schema seed + `detection_tools.setup_guide` 欄位與內容 | 三環境可套；設定頁清單出現 OpenSCAP(available) | — | BE |
| T-1.2 | FE 設定頁欄位型態擴充：textarea / 條件顯示（auth_method 切換）/ setup_guide 渲染 + 腳本複製 | OpenSCAP 設定頁可完整填寫送出，憑證密文落庫 | T-1.1 | FE |
| T-1.3 | FE 任務參數型態擴充：select_or_text（profile 下拉+自由輸入） | 任務可選 OpenSCAP 並填參數發佈 | T-1.1 | FE |

### FR-057.2 Agent：OpenSCAP connector — 依賴：FR-057.1

| # | 子任務 | 驗收 | 依賴 | Repo |
|---|--------|------|------|------|
| T-2.1 | Dockerfile（openssh-client + openscap-utils）+ factory 註冊 + connector 骨架（SSH 參數解析、私鑰暫存管理） | image build 過；unit test 綠 | T-1.1 | evidence-agent |
| T-2.2 | `run()`：逐台 oscap-ssh + HTML 報告收取 + stdout summary + exit code 2 正確處理 + 取消/逾時 | 手塞派工 → 掃真實主機 → 報告回收全鏈通 | T-2.1 | evidence-agent |
| T-2.3 | `probe()`：連線/認證/oscap/SSG 四段檢查與錯誤分類 | 測試連線在四種缺件情境回清楚訊息 | T-2.1 | evidence-agent |

### FR-057.3 多檔證據回收鏈 — 依賴：FR-057.1（可與 .2 並行）

| # | 子任務 | 驗收 | 依賴 | Repo |
|---|--------|------|------|------|
| T-3.1 | Agent：task_executor 多檔上傳 + result_ref.upload_uids（向下相容單數） | 多檔派工回報結構正確；OpenVAS 單檔路徑迴歸綠 | — | evidence-agent |
| T-3.2 | BE：detection_result_handler 多檔迴圈取回 + 逐檔寫 job_evidences + 部分失敗續處理 | 多檔證據逐檔入池；單檔 fallback 迴歸綠 | T-3.1 | BE |

---

## 6. 驗收（端到端）

1. 管理員在檢測工具管理頁設定 OpenSCAP（金鑰認證）→ 測試連線通過（含前提檢查）
2. 任務選 OpenSCAP、填 2 台 hosts + CIS profile → 開始執行 → 每台各一份 HTML 報告進證據池
3. 任一台缺 openscap-scanner → probe/掃描回明確錯誤訊息（非神祕失敗）
4. OpenVAS 既有全鏈迴歸不受影響（factory / result 單檔路徑 / FE 動態渲染）
