| 項目 | 內容 |
|---|---|
| 緣由 | FR-058 分批出貨。第一批(ID 解耦 + 任務層敏感參數 + ZAP,CM-958〜970)實作已完成、agent 已 bump 0.2.13。本文是第三批(Nmap)實作 session 的派工單 |
| Branch | BE:feature/FR-058(不切 branch);evidence-agent:當下 branch(第一批 HEAD 8185893 bump 0.2.13) |
| 本棒角色 | 實作者。 完成 FR-058.4(Nmap),依 T-4.1 → T-4.2 → T-4.3 順序做,每個子任務完成即 commit + 回寫 Notion 卡 |
| 與第二批的關係 | 完全獨立、可並行。 第二批(InSpec/CINC → GCB,CM-971〜979)與本批無任何依賴:不碰 inspec.py、不碰第二批的 seed 檔,各自新增檔案不衝突。但 evidence-agent 有三處共用檔會撞(factory __init__.py / Dockerfile / 版號),處理方式見 §7 |
| ⚠️ 改案(2026-07-30,本文已全份更新) | D9 改案:Nmap 由 connection_type='CLI'(agent 本機執行)改為 'SSH'——nmap 由客戶安裝在自己指定的主機上,agent 透過 SSH 登入該主機執行,與 OpenSCAP 完全同構。觸發點是「客戶自備」在容器化部署下無法成立(agent 在容器內看不到主機的 nmap),查證 NPSL v0.95 §3 後確認不能 bundle 且同條款末段的自留出口讓 SSH 型成立。連帶:Nmap 需要 SSH 憑證、T-4.1 零憑證平台能力保留但 Nmap 不再是其使用者、connection_type='CLI' 至今仍無實際使用者。完整推導見 design.md §2 D9,本文摘要見 §2.1〜§2.3 |
| 接手前必讀 | 本文全份 + design.md §2(D9 含 2026-07-30 改案段,務必讀原文)+ §4.5(Nmap 詳細設計,已改為 SSH 型)+ §5.7(子任務表)+ §3.2 元件盤點的 connection_type / start_execution() 兩列 |
| 上游文件 | docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-30-fr058-planning-complete-handoff.md(全案大圖與需求演變;本文已摘實作所需部分,卡住時才回頭讀) |
design.md §4.5(FR-058.4 Nmap 詳細設計)全讀 + §5.7(T-4.1〜T-4.3 子任務表與驗收)design.md §2 的 D9 定案原文,含 2026-07-30 改案段(本文 §2.1〜§2.3 是摘要,原文含 NPSL 條款原文、改案的完整推導與三個被排除方案的理由)design.md §3.2 元件盤點表的兩列:config.detection_tools(「加是否需要憑證宣告欄位」)與 start_execution() 憑證檢查(「放行零憑證工具」)——這兩列是 T-4.1 的範圍界線第三批 = FR-058.4(Nmap),共 3 個子任務,序列執行(T-4.2 依賴 T-4.1 的欄位、T-4.3 依賴 T-4.2 的 seed)。
| # | 子任務 | Repo | 一句話 |
|---|---|---|---|
| T-4.1 | detection_tools 加「是否需要憑證」宣告欄位(schema 異動 + ORM model + migration);start_execution() 依該欄位放行零憑證工具(D9);三環境套用 |
BE | 本批唯一動到平台本體的一棒,迴歸風險最高。改案後 Nmap 已不是此能力的使用者,但這一棒照做——它是平台級能力(見 §2.2) |
| T-4.2 | seed Nmap(connection_type='SSH'、requires_credentials=TRUE)+ config_field_schema(SSH 憑證,對照 OpenSCAP)+ param_schema(掃描目標 / 掃描類型 / 埠範圍)+ setup_guide(客戶在自己指定的主機安裝 nmap、NPSL 授權說明、SSH 前提、主動掃描授權警語);三環境套用 |
BE | migration |
| T-4.3 | nmap.py:SSH 登入執行主機 → nmap -oX → XML 解析 summary + 報告上傳 + probe()(SSH 連線 / 認證 / 該主機 nmap 可執行 / 權限四段分類)+ cancel_event / timeout;不 bundle 進 image;factory 依 code 註冊 |
evidence-agent | connector(主要對照 openscap.py) |
驗收(design.md §5.7 原文):
DETECTION_TOOL_CONFIG_NOT_FOUND;需憑證的既有工具檢查行為不變(← 迴歸紅線,見 §2.2)probe() 回明確訊息(且可與「連不上」「認證失敗」區分) (其中「手塞派工掃真實網段」屬端到端驗收,依 §5 測試紀律延後到全案最後)https://app.notion.com/p/3ad346da4cd081859d2ed8b21ca2697c)| 卡 | 對應 | Notion URL |
|---|---|---|
| CM-980(FR-058.4 母卡) | Nmap | https://app.notion.com/p/3ad346da4cd081159daae65d4b08c80d |
| CM-981 | T-4.1 | 從母卡 CM-980 子卡清單進入 |
| CM-982 | T-4.2 | 同上 |
| CM-983 | T-4.3 | 同上 |
⚠️ 2026-07-30 D9 改案(本節已依此改寫):Nmap 由原定的
connection_type='CLI'(agent 本機執行)改為connection_type='SSH'——nmap 由客戶安裝在自己指定的主機上,agent 透過 SSH 登入該主機執行,與 OpenSCAP 完全同構。改案的完整推導、NPSL 條款原文與三個被排除方案見design.md§2 的 D9(本節 §2.1〜§2.3 是摘要,動手前務必讀 D9 原文)。
Nmap 在四個工具裡「技術上最單純」(呼叫 CLI + XML 解析)。改案前它是「唯一一個逼平台改自己的工具」(零憑證),改案後它變成 OpenSCAP 的同構複製,實質工作量從「改平台」轉為「照抄 SSH 型範本」。本節四點動手前逐點讀完。
CLI 型態至今仍無使用者)detection_tools.connection_type 在 FR-056 就定義了三種型態(API / CLI / SSH),至今只有 API(OpenVAS、ZAP)與 SSH(OpenSCAP)被真正用過。原本 Nmap 要當 CLI 的首個實際使用者,改案後這件事沒有發生——CLI 這一格從 FR-056 定義出來到現在仍然沒有任何工具走過,本批不會改變這個事實。
這對本批的意義是好消息:走 SSH 就是走已被 OpenSCAP 實戰驗證過的路徑,BE 派工鏈、設定頁憑證渲染、測試連線端點、agent 的 SSH 連線處理全部有現成可照抄的形狀,原本「未經驗證路徑可能藏著缺口」的風險不復存在。
因此實作方向是「照抄 OpenSCAP」而不是「開新路」:
| 對照點 | 照誰寫 |
|---|---|
detection_tools seed(connection_type / config_field_schema / requires_credentials) |
OpenSCAP 那一列 + scripts/sql/2026-07-28-fr057-1-openscap-seed.sql |
| 設定頁 SSH 憑證欄位(host / port / 帳號 / 密碼或私鑰) | OpenSCAP 的 config_field_schema,不需要新欄位型態 |
agent connector 的 SSH 連線 / 私鑰暫存 / 多目標逐台 / host_failures |
openscap.py(見 §4.3) |
測試連線(probe())的分段錯誤分類 |
openscap.py 的 probe,再加一段「該主機有沒有 nmap」(§2.3) |
唯一要留意的新事實:Nmap 現在需要 SSH 憑證(requires_credentials=TRUE),設定頁必須先設定憑證才能執行——與 OpenSCAP 行為一致,不再是「無需填憑證」。
發現與 OpenSCAP 形狀不一致而需要偏離時的處置:先停下回報再決定——這屬 D1–D12 沒涵蓋的新決策,依 §6 規範不自行開新方向。
⚠️ 改案後的定位(先讀這段再往下):Nmap 改為 SSH 型後需要 SSH 憑證,它自己不再是零憑證工具,也就不會被
start_execution()的憑證檢查誤擋。
但 T-4.1 這一棒照做、範圍不變——「是否需要憑證」宣告欄位 + 放行邏輯是平台級能力(design.md §1.2 已把它列為平台結構性補強),日後真正零憑證的工具會用到,只是第一個使用者不再是 Nmap。
對本批的具體差異只有兩點:① T-4.2 seed Nmap 時requires_credentials要填 TRUE(不是 FALSE);② T-4.1 的驗收要另找方式驗(見本節末的「改案後怎麼驗 T-4.1」)。下面的現況分析與紅線全部仍然適用。
現況(已查證,app/detection_tools/service/detection_orchestration_service.py:105-110):
creds = self._resolve_credentials(
binding.tenant_config_id, tenant_id, binding.detection_tool_id
)
if not creds:
# 空憑證派下去 agent 必炸(缺 host/帳密),且錯在雲端卻只能從 agent log 看到
# → 提前擋下並講清楚要去設定工具憑證。
raise BadRequestError(DetectionToolsErrorCode.DETECTION_TOOL_CONFIG_NOT_FOUND)(改案前的問題敘述:Nmap 不需要任何帳號密碼,走到這裡必被誤擋。改案後 Nmap 有 SSH 憑證,不會走到這個死路——但下面的平台能力仍要補上,理由見本節開頭的框。)
D9 定案做法:在 detection_tools 增加「是否需要憑證」的宣告欄位,讓平台知道零憑證是一種合法狀態,start_execution() 依該宣告放行。
D9 明確排除的做法:為 Nmap 建立一筆空的租戶設定當佔位——不要這樣做。理由:會留下一筆語意不明的資料,日後看到的人無法判斷那是刻意還是遺漏。
這一棒的風險與紅線:
NOT NULL DEFAULT TRUE 之類),migration 不可讓既有四列變成零憑證。驗收的第二句「需憑證的既有工具檢查行為不變」就是在講這件事。config.detection_tools 加欄位(migration,三環境都套)infra/detection_tools/model/detection_tool.py(32 行,加 Mapped[bool] 欄位 + comment=;本專案 ORM comment= 是 DB COMMENT 的單一來源)domain/detection_tools/entity/detection_tool_entity.py + infra/detection_tools/mapper/detection_tool_mapper.py(欄位要能傳到 service 層才判斷得了)start_execution() 的 if not creds 改為「若該工具宣告需要憑證才擋」start_execution() 目前手上只有 binding.detection_tool_id,需要取到該工具的宣告欄位。依 DDD 層級規範,走 detection tool 的 domain service 取,不要在 app service 直接 import ORM model 查(既有 self._jedt_domain / self._exec_domain 的注入形狀可照抄;缺 domain service 就在 __init__ 加參數並到 di_containers/ 對應 container wiring)。requires_credentials(BOOLEAN NOT NULL DEFAULT TRUE)——語意正向、預設安全(新工具沒宣告就是需要憑證,往嚴的方向失敗)。若採別的命名,理由寫進 migration 檔頭註解。requires_credentials=false 的工具應該不顯示憑證表單 / 不要求「先設定才能執行」。若 FE 需要這個欄位,API response 要一併帶出(detection_tools 列表端點的 schema)——這點在 T-4.1 就要決定,不要留到 T-4.2 才發現前端拿不到。requires_credentials=true,走的是既有的「顯示憑證表單」路徑,與 OpenSCAP 一致,因此本批不會實地踩到 false 分支的 FE 行為。仍建議在 T-4.1 把 false 分支的 FE 行為想清楚並寫進 commit message,避免日後第一個真正零憑證的工具進來時才發現前端沒處理。)改案後怎麼驗 T-4.1(原驗收假設「用 Nmap 驗零憑證放行」已不成立):
requires_credentials 設 false 跑一次(DEV 上驗完改回),或在 BE 加一個針對 start_execution() 憑證分支的單元測試(mock 工具宣告的兩種值各跑一次)——後者較乾淨且可留下迴歸保護,優先選它。Nmap 採 NPSL(Nmap Public Source License,基於 GPLv2 但加了額外限制),不可打包進我們發布的 agent image。
法律依據(2026-07-30 查證,比原記載的「NPSL 授權」四個字強得多)——NPSL v0.95 §3(出處 https://svn.nmap.org/nmap/LICENSE)列舉的「衍生作品」定義明文包含這一條:
Is designed specifically to execute Covered Software and parse the results
(as opposed to typical shell or execution-menu apps, which will execute
anything you tell them to).
我們的 connector 正是「專門呼叫 nmap 並解析其 XML 輸出」,正中此條。因此「只用子行程呼叫、不連結函式庫」這個 GPL 慣用的免責理由在 NPSL 底下不成立——該條款是特意加來堵掉這個論點的。GPLv2 的 mere aggregation 條款也救不了(它只適用於「not based on the Program」的作品,而 §3 已把我們定義為 based on)。官方 Legal Notices(https://nmap.org/book/man-legal.html)另明說免費授權「doesn't allow Nmap to be used and redistributed within commercial software or hardware products」(明列 appliances / virtual machines / traditional applications),並為此販售 Nmap OEM Edition(https://nmap.org/oem/)。
但 §3 最後一段留了出口:軟體若執行的是「使用者早已安裝在自己系統上的 nmap」,授權方不主張控制(Licensor does not purport to control through this license any software which does not require the rights granted herein)。SSH 型正好落在這個描述裡——nmap 是客戶裝在自己主機上的,我們只是遠端下指令。
原 CLI 型為何撐不住:實作前發現「客戶自備」在容器化部署下無法成立——agent 跑在 Docker container 內,看不到主機上安裝的 nmap。原設計隱含假設 nmap 在 agent 可執行範圍內,但「不 bundle」又「不能裝進容器」互相矛盾。被評估並排除的三個方案(掛載主機 binary 進容器 / 客戶自建一層 image / 購買 Nmap OEM 授權)各自的理由見 design.md §2 D9 的改案段——若你在實作中想走其中任何一條,先讀那段再停下回報。
對實作的四個具體要求:
Dockerfile 不加 nmap。(對照組:第二批要在 Dockerfile 加 CINC Auditor,本批不動 Dockerfile 的套件安裝——若你發現自己在改 Dockerfile 裝 nmap,停下來重讀這段。也不可用 volume 掛載主機 binary 的方式繞過,那是 D9 明確排除的方案之一。)probe() 必須能在「執行主機」未安裝 nmap 時回明確訊息,且要與「SSH 連不上」「認證失敗」明確區分(四段式分類,見 §4.3)。這是驗收條件之一,不是選配。遠端 command -v nmap / nmap --version 失敗時的訊息要讓管理員一看就知道「要去那台執行主機裝 nmap」,而不是丟一個 exit code 127 的原始輸出。setup_guide 要寫清楚「裝在哪裡、怎麼裝」:在客戶自己指定的執行主機上安裝(各主流發行版的安裝指令 + 驗證指令),無任何 docker 操作;SSH 連線與帳號權限前提(部分掃描類型需要提權);並註明「本平台不隨附 nmap,需由貴公司在指定主機自行安裝」與 NPSL 授權說明。tenant_detection_tool_configs,走既有 Fernet 鏈,對照 OpenSCAP 的 config_field_schema),不放任務參數、不涉及 FR-058.0 的任務層敏感參數機制。setup_guide 需比照 ZAP 加授權警語埠掃描會對目標送出真實探測流量——不是被動觀察,而是主動連線嘗試。在客戶網路環境中,這可能觸發 IDS/IPS 告警、被誤判為攻擊行為,或違反目標方的使用條款。
要求:setup_guide 比照 ZAP 的寫法加上授權警語。ZAP seed 的原句可直接照抄改寫:
⚠️ 主動掃描會對目標網站送出真實的攻擊測試流量。 請務必確認已取得目標方授權,並優先在測試環境執行。
Nmap 版的措辭要對上埠掃描的實際行為(送出探測封包、可能觸發資安設備告警),並提醒「掃描網段前確認該網段屬於受稽核範圍且已獲授權」。
param_schema 的掃描類型欄位若含侵入性較高的選項(例如版本偵測 -sV、OS 偵測 -O、腳本掃描 -sC),預設值要選保守的那一個——這條沿用 D2 的判斷原則:預設值選錯的代價不對等,預設保守最多是掃得淺,預設侵入則可能在使用者未察覺下對正式環境送出大量探測流量。
f17a311,D11)get_connector() 現在優先吃 detection_tool_code,detection_tool_id 降為 fallback(走 fallback 記 warning log)。core/task_executor_connectors/__init__.py 加一個 _build_nmap() builder(import 留在 builder 內部,維持 lazy import 慣例——某支 connector 的第三方相依缺失時只有該工具不可用,不會讓整個 factory import 失敗),並在 _CONNECTOR_BUILDERS dict 加 "nmap": _build_nmap(實際 code 以 T-4.2 seed 的 detection_tools.code 為準)。_LEGACY_TOOL_ID_TO_CODE——檔頭註解已明訂該表只涵蓋 FR-056/057 已出貨四筆({1: openvas, 2: nessus, 3: sonarqube, 4: openscap}),新工具加入等於把剛移除的脆弱性種回去。ZAP 已照此模式(參考 _build_zap)。test/test_connector_factory.py,新 code 註冊後補對應 case。detection_tool_code(BE commit 5151fb2c + 082a02d8,T-X.1 已上線)_collect_pending_tasks() 已加欄位;082a02d8 補齊了心跳以外的第二條派工路徑(測試連線 probe payload)。第三批不需要動 BE 派工鏈的 code 夾帶部分——seed 進 detection_tools 後派工自然帶 code,agent 端照 §3.1 註冊即可接上。
(注意:T-4.1 仍要動 start_execution(),但那是憑證檢查的分支,與 code 夾帶無關。)
5151fb2c,T-0.1〜0.3)param_schema 欄位標 secret: true 即自動走 Fernet 加密落庫 / 派工解密下發 / FE 與稽核 log 剝除(實作在 common/util/detection_secret_params.py)。
Nmap 本批預設用不到(埠掃描不需要憑證,這正是本批要解的問題)。但若日後 Nmap 要做認證掃描(例如需要目標主機帳密的服務探測),直接在 param_schema 標 secret: true 即可,不需自建機制——這條在 design.md §1.2 已被列為此能力的預期受益者之一。
第一批已在 FE 補上任務參數的必填驗證(FE commit 82208529,helper ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/utils/detectionFieldValidation.js):任務存檔時會擋 condition-aware 的必填欄位(condition 不成立的欄位不渲染也不驗證)。
已知邊界(helper 檔頭註解已載明):空值判斷刻意沿用 falsy(!value),與 DetectionConfigField.showError 同源。當時的前提是「有 required 的欄位型態不含 number / boolean」,所以 falsy 判斷不會誤殺 0 或 false。
對 Nmap 的直接影響:若 Nmap 的 param_schema 出現 type: "number" 或 type: "boolean" 且 required: true 的欄位(很可能——例如埠號範圍起點、逾時秒數、某個布林開關),使用者填 0 或 false 時會被誤判為缺漏而擋下存檔。
處置(依序):
required: false + 給 default(timeout 類本來就該這樣,ZAP 的 timeout_sec 即是 required:false + default:3600),或改用 text / select 型態(埠範圍本來就常寫成 "1-1024" 這種字串)。value === undefined || value === null || value === ''),不要自己在 seed 裡繞過或默默改 FE。ZAP 原定用 reports.generate 產 PDF,實作時發現該 API 只把檔案寫進 ZAP daemon 主機的磁碟、回傳路徑字串,官方沒有回傳檔案內容的端點(zaproxy issue #7821)——遠端部署模型下 agent 拿不到那個路徑的檔案,被迫改案(改 core.htmlreport 取 bytes)。
通則:任何「工具端產檔」的設計,動手前都必須先驗證取得管道回的是內容本體而不是「對方主機上的路徑」。
Nmap 原則上沒有此問題,但改案為 SSH 型後這條要多留意一層——nmap -oX 在遠端執行主機上跑,輸出必須真的傳回 agent。最直接的做法是 -oX - 讓 XML 走 stdout,SSH 直接收到 bytes(省掉遠端暫存檔的清理問題);若改用遠端暫存檔,就必須自己把檔案抓回來並清掉遠端殘檔。寫 run() 前確認一次:確定拿到的是 XML bytes 而不是「遠端主機上的路徑字串」、確定遠端沒留下暫存檔。openscap.py 已經解過同一題,照它的形狀。
第一批已出到 0.2.13(DEV 已部署;HEAD 8185893,含 ZAP 主動掃描修正)。第三批出貨版 bump 0.2.14——但兩批可能同時進行,若第二批先出 0.2.14 就順延到 0.2.15。commit 前先 git log --oneline -5 + 看 pyproject.toml 的 version 確認目前版號,不要拿本文的數字當現況。
bump pattern 照 evidence-agent commit c775fc8——四處版號同步:pyproject.toml / config/config.py(AGENT_VERSION 預設值)/ deploy/docker-compose.yml(AGENT_IMAGE + AGENT_VERSION 兩處)/ rebuild.sh(註解範例)。依 §5 測試紀律,開發期間不重建 image、不部署,bump commit 做完留著等全案端到端才出貨。
scripts/sql/2026-07-30-fr058-1-zap-seed.sql(見 §4.1)——檔頭把決策背景寫成註解、ON CONFLICT (code) DO NOTHING、用 nextval 自然拿 id 且註明「code 派工後 id 不影響正確性」、schema_migrations 收尾。test/test_zap_connector.py(485 行,47 項全 mock + 九輪突變測試驗斷言真的會咬)——Nmap connector 測試照此水準寫 test/test_nmap_connector.py。Nmap 的 XML 解析特別適合這種寫法:準備幾份假 XML fixture(正常多埠 / 全關閉 / 主機不可達 / 格式殘缺),對解析邏輯跑純 mock 測試,完全不需要真的掃任何東西。| 檔案 | 用法 |
|---|---|
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-30-fr058-1-zap-seed.sql |
SQL 寫法的最新範本——nextval 拿 id + 「code 派工後 id 不影響正確性」註解、ON CONFLICT (code) DO NOTHING、param_schema 的 condition / secret / default 寫法、setup_guide 的 E'' 多行 markdown 寫法(含表格與程式碼區塊)、detection_tool_param_schemas 的 is_current 寫法、schema_migrations 收尾 |
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-28-fr057-1-openscap-seed.sql |
⭐ 內容形狀的主要對照(2026-07-30 改案後)——Nmap 與 OpenSCAP 同為 SSH 型,connection_type='SSH' 與 config_field_schema 的 SSH 憑證欄位(host / port / 帳號 / 密碼或私鑰)直接照它的形狀寫,不要自創欄位。SQL 寫法本身仍以上面的 ZAP 檔為準(較新) |
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-30-fr058-2-fix-zap-login-required-flags.sql |
既有資料補正檔的範本——T-4.1 的欄位新增若要同時回填既有四列,形狀照它(改既有列 + 檔頭寫清楚為何要補、新環境走 seed 檔不必再跑此檔) |
T-4.1 的 SQL 特別注意:本批是 FR-058 唯一有 ALTER TABLE 的一棒(其餘都是 INSERT seed)。CLAUDE.md SQL 鐵則照舊(檔頭 -- Date:、每語句加日期註解、收尾 INSERT public.schema_migrations、psql --single-transaction -v ON_ERROR_STOP=1);本批只加欄位不建表,故不涉及 GRANT ... TO cm_app(既有表的授權不變)。
| 檔案 | 用途 |
|---|---|
app/detection_tools/service/detection_orchestration_service.py → start_execution()(憑證檢查在 105-110 行附近) |
D9 放行邏輯的落點;同檔可看到 self._jedt_domain / self._exec_domain / self._agent_task_domain 的 DI 注入形狀 |
infra/detection_tools/model/detection_tool.py(32 行) |
ORM model 加欄位(Mapped[bool] + comment=;comment= 是 DB COMMENT 單一來源,見 memory project_erd_tooling_convergence) |
domain/detection_tools/entity/detection_tool_entity.py + infra/detection_tools/mapper/detection_tool_mapper.py |
entity 欄位 + mapper 對映,缺一則 service 層拿不到值 |
infra/detection_tools/repository/detection_tool_repo_impl.py |
若需依新欄位查詢時看這裡(預期不必改) |
common/code/detection_tools_error_code.py(DETECTION_TOOL_CONFIG_NOT_FOUND = DETECTION_TOOLS_404002) |
放行後這個 error code 對零憑證工具不再觸發;不要刪除它(既有工具仍在用) |
| 檔案 | 用法 |
|---|---|
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/openscap.py(578 行) |
⭐ 主要對照範本(2026-07-30 改案後)——Nmap 與 OpenSCAP 同為 SSH 型,形狀幾乎可整套照搬:SSH 連線建立與認證、私鑰暫存與用完清除、遠端指令執行與輸出取回、多目標主機逐台執行、host_failures 逐台失敗回報、遠端子行程的取消與清理、probe() 的分段錯誤分類。改案前這裡只列為「多目標處理的參考」,現在它是第一順位 |
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/zap.py(631 行,第一批最新出爐) |
次要對照(通用紀律):cancel_event / timeout 輪詢寫法、_check_cancelled() 的「停止呼叫失敗不可蓋掉取消語意」處理、_probe_failure_message() 錯誤訊息組裝、回傳值驗證(錯誤碼偽裝成正常回傳的防護)、summary 解析容錯、log 紀律(憑證絕不進 log)——這些與連線型態無關的形狀照抄 |
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/base.py(63 行) |
DetectionConnector ABC、ScanResult(content bytes / filename / content_type / summary)、ScanCancelledError、注入點 cancel_event / host_failures / content_mismatch_hosts——介面不動 |
Nmap 特有的實作要點(design.md §4.5,已依 2026-07-30 D9 改案更新):
run():SSH 登入客戶指定的執行主機 → 執行 nmap -oX → 取回 XML → 解析產出 summary(開放埠數、服務清單等)→ XML 或轉出的人可讀報告上傳當證據。多台執行主機時逐台一份證據,某台失敗以 host_failures 明確回報(不靜默假成功)——形狀照 openscap.pyprobe():四段式錯誤分類,各段回各自明確的訊息——① SSH 連得上嗎(網路 / port)→ ② 認證過嗎(帳密 / 私鑰)→ ③ 該主機上有沒有 nmap 且可執行(遠端 command -v nmap + nmap --version)→ ④ 執行權限夠不夠(部分掃描類型需要提權)。第 ③ 段的訊息是驗收條件(§2.3 第 2 點)cancel_event 與 timeout:取消時要中止遠端執行中的 nmap 並清理 SSH channel,raise ScanCancelledError。照 openscap.py 的遠端子行程取消形狀處理,不要留下對端的孤兒行程。(注意:這與改案前規劃的「本機 subprocess terminate」不同,也與 ZAP 的 HTTP 輪詢不同)~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/__init__.py(82 行)——f17a311 之後改依 code 註冊(見 §3.1)。照 _build_zap 的形狀加 builder + _CONNECTOR_BUILDERS 一列,不動 _LEGACY_TOOL_ID_TO_CODE。
⚠️ 此檔第二批也會改(加 _build_inspec),是 §7 三個衝突點之一。
| 檔案 | 用途 |
|---|---|
~/Projects/Billows/Audit-Manager/evidence-agent/Dockerfile |
本批不加任何套件(§2.3 第 1 點);只有第二批會動它 |
~/Projects/Billows/Audit-Manager/evidence-agent/test/(跑法 PYTHONPATH=. poetry run pytest test/ -q,repo 缺 conftest.py 必帶 PYTHONPATH) |
單元測試落點 |
~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/utils/detectionFieldValidation.js |
§3.4 的必填驗證 helper(設計 param_schema 前先讀檔頭註解) |
~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/components/.../DetectionConfigField.vue |
FE 七種欄位型態 + condition 條件顯示;設計 param_schema 時確認型態在支援清單內 |
決策者 2026-07-30 裁示,取代原「批次結束測一次」。與第二批派工單完全一致。
端到端驗收延後到全案最後一次做(與第一批、第二批合併驗收)。開發期間:
版號 bump 的 commit 照做(§3.6),但不出貨。
| 檢查 | 做法 |
|---|---|
| import / 語法檢查 | agent:PYTHONPATH=. poetry run python -c "from core.task_executor_connectors.nmap import NmapConnector";BE:改完 model / entity / service 後起服務或跑既有測試確認 import 鏈完整 |
| mock 單元測試 | 全 mock、不碰網路與真實主機(SSH client 一併 mock)。Nmap 特別適合:準備假 XML fixture(正常多埠 / 全部關閉 / 主機不可達 / 格式殘缺)+ 假的遠端執行結果,對解析邏輯與 probe() 四段錯誤分類(SSH 連不上 / 認證失敗 / 該主機沒裝 nmap / 權限不足,四種要能各自回不同訊息)跑測試。水準對齊第一批 test/test_zap_connector.py(47 項全 mock + 突變測試驗斷言真咬——刻意改壞被測邏輯確認測試會紅,避免寫出永遠綠的假測試)。openscap.py 若已有對應測試檔,mock SSH 的形狀照它 |
| factory 迴歸 | test/test_connector_factory.py 補 nmap code 的 case,確認既有四個工具取 connector 行為不變 |
| **T-4.1 的 BE 迴歸(本批特有,不可省) | 放行邏輯改完後,確認需憑證的既有工具(openvas / openscap / zap)在憑證為空時仍被擋**——這是驗收明文要求。用既有 BE 測試或手動觸發皆可,但要有實際執行紀錄,不能只是「看程式碼覺得沒問題」 |
| SQL 套 DEV 後 SELECT 驗證 | T-4.1:SELECT id, code, connection_type, requires_credentials, status FROM config.detection_tools ORDER BY id(確認既有四列仍是「需要憑證」);T-4.2:同一查詢確認 Nmap 進去且 connection_type='SSH'、status='available'、requires_credentials=true(← 2026-07-30 改案,原為 CLI + false),再查 config.detection_tool_param_schemas 確認 schema 進去且 is_current 正確,並確認 config_field_schema 有 SSH 憑證欄位(對照 OpenSCAP 那一列) |
DEV → STG → POC 三環境(指令照 CLAUDE.md「DB 環境清單」段;帳號 cmmgr,密碼請查 .env 的 DB_SECRET 或部署文件,禁入任何版控檔案):
# DEV
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
--single-transaction -v ON_ERROR_STOP=1 -f scripts/sql/<本批 SQL 檔>.sql
# STG(同台不同 DB)
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_stg \
--single-transaction -v ON_ERROR_STOP=1 -f <同上>
# POC(另一台)
psql -h 192.168.50.189 -p 25432 -U cmmgr -d guidant_ai_poc \
--single-transaction -v ON_ERROR_STOP=1 -f <同上>⚠️ T-4.1 是 ALTER TABLE,三環境不同步的後果比 seed 嚴重(BE 程式碼會讀新欄位,某環境沒套就整個 detection tools 查詢炸掉)。T-4.1 套完三環境確認無誤,才開始 T-4.2。
SQL 撰寫鐵則:檔頭 -- Date:、每個語句加日期註解、收尾 INSERT public.schema_migrations。
git checkout <branch> / git switch <branch> 一律不執行。BE 永遠在 feature/FR-058 工作;發現 branch 不對停下回報,不自己 fix(「為了在正確 branch commit 而切 branch」也不允許)-am——避免誤 add 其他 arc 留下的 untracked / modified 檔案(BE working tree 現有一批與 FR-058 無關的既有檔案,不歸本批管、不要清理).env 或部署文件」__init__ 加參數並到 di_containers/ 對應 container wiringtail -200 log/app.log | grep -A 30 -i 'Traceback\|ERROR',log 沒線索才回報第二批(InSpec/CINC → GCB,CM-971〜979,派工單 2026-07-30-fr058-batch2-dispatch.md)與本批無功能依賴、可同時進行,但兩批都會動 evidence-agent 這個共用 repo。
| 項目 | 為何不衝突 |
|---|---|
| connector 檔本體 | 第二批新增 inspec.py,本批新增 nmap.py——不同檔案 |
| 單元測試檔 | test_inspec_connector.py vs test_nmap_connector.py——不同檔案 |
| BE seed SQL | 各自新檔(檔名帶 fr058-<序>-<工具>),內容互不相干 |
| BE 平台邏輯 | 本批動 start_execution() 憑證檢查與 detection_tools schema;第二批只 seed 不動 BE 程式碼 |
| 檔案 | 兩批各自要做什麼 | 處置 |
|---|---|---|
core/task_executor_connectors/__init__.py |
第二批加 _build_inspec + "inspec" 一列;本批加 _build_nmap + "nmap" 一列 |
都是「加一個 builder 函式 + dict 加一列」,語意上不衝突,但文字上會撞同一個區塊。commit 前先 git pull / git log --oneline -5 看對方有沒有先動;若已被改,把自己的那一列加上去即可(不要覆蓋對方的) |
Dockerfile |
第二批加 CINC Auditor;本批不動(Nmap 不 bundle,§2.3) | 本批不碰即無衝突。若你發現自己在改 Dockerfile,先確認自己不是在裝 nmap |
| 版號(四處同步) | 兩批各自要 bump 一版 | 先 git log + 看 pyproject.toml 的 version 確認目前版號再決定自己 bump 到多少(第一批已到 0.2.13;先出貨者拿 0.2.14,後者順延 0.2.15)。不要拿本文的數字當現況 |
git log --oneline -5(agent repo),確認對方的 commit 有沒有進來git add <檔名>,絕不 -am——-am 會把對方正在改的檔案一起帶走feature/FR-058 branch,但改動範圍不重疊(本批動 detection_tools schema + orchestration service;第二批只加 SQL 檔),一樣照顯式 git add 原則接手 FR-058 第三批(Nmap)的實作工作。
完整讀序入口:
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-30-fr058-batch3-nmap-dispatch.md
先讀它全份(特別是 §2 Nmap 的特殊性與 §3 第一批新事實),再照它的 §0 讀序開
design.md §4.5 + §5.7 + §2 的 D9。
範圍:T-4.1〜T-4.3(CM-981〜983,母卡 CM-980),序列執行。
與第二批(InSpec/CINC → GCB)完全獨立、可並行,但 evidence-agent 有三處共用檔會撞
(factory __init__.py / Dockerfile / 版號),處理方式見文件 §7。
硬紅線速記:Nmap 走 connection_type='SSH'(2026-07-30 D9 改案,原定 CLI 已作廢)——nmap 由
客戶裝在自己指定的主機上,agent 走 SSH 登入該主機執行,與 OpenSCAP 完全同構,connector 主要
對照 openscap.py 而非本機子行程模式;Nmap 需要 SSH 憑證,seed 時 requires_credentials=TRUE。
Nmap 不 bundle 進 agent image(NPSL v0.95 §3 的衍生作品條款明文涵蓋「專門執行本軟體並解析其
結果」的程式,正中我們的 connector),也不可用 volume 掛載主機 binary 繞過;probe() 要四段式
分類(SSH 連線 / 認證 / 該主機 nmap 可執行 / 權限),第三段訊息要能讓管理員知道「去那台主機裝
nmap」;setup_guide 要寫在哪台主機裝、怎麼裝(無 docker 操作)+ 埠掃描的授權警語。
T-4.1 的零憑證放行照做但 Nmap 已不是它的使用者——那是平台級能力,既有工具(含 Nmap)都必須
維持「憑證為空即擋」,新欄位預設往嚴的方向;不建空的租戶設定當佔位。
param_schema 避免出現必填的 number/boolean 欄位(FE 驗證用 falsy 判斷,0/false 會被誤判為
缺漏,詳見文件 §3.4);新 connector 依 code 註冊,絕不加進 _LEGACY_TOOL_ID_TO_CODE。
注意 connection_type='CLI' 至今仍無任何實際使用者(改案後 Nmap 不再是它的首個使用者),
本批不會改變這個事實;走 SSH 是已被 OpenSCAP 實戰驗證過的路徑,照抄即可(文件 §2.1)。
測試紀律:端到端驗收整批延後到全案最後,開發期間不重建 image、不部署、不跑真實掃描;
只做 import 檢查 + 全 mock 單元測試(假 XML fixture)+ SQL 套 DEV 後 SELECT 驗證;
T-4.1 額外必做「既有需憑證工具仍被擋」的迴歸驗證;migration 三環境都要套,
且 T-4.1 是 ALTER TABLE,三環境套完確認無誤才開始 T-4.2。
BE 在 feature/FR-058 工作,不切 branch、不 push、顯式 git add 禁 -am;每個子任務完成即
commit 並回寫 Notion 卡(修正待驗證 + 白話說明 + commit hash + 執行紀錄);收尾動作等我下令。
📌 寫完自檢:下個 session 只看這一份 + design.md §4.5 / §5.7,能不能不回頭問任何人就開工?——本文所有路徑 / 卡號 / commit hash / SQL 指令皆可直接使用;密碼類資訊一律指向
.env。