系統完整性鎖定 → 解鎖 SOP(原廠側)

系統完整性鎖定 → 解鎖 SOP(原廠內部作業文件)

適用對象:原廠客服/技術支援/授權簽發人員(坐在 License Center 後台前受理客戶回報的人)。 本文用途:客戶回報系統鎖定後,從研判到簽出 unlock token、交付客戶的完整作業流程。 不在本文範圍:防竄改機制的設計理由與決策(見同資料夾 design.md §2 D5/D6/D11、§4.2–4.4);客戶端操作(見 docs/user-manual/system-integrity-lockdown-guide.md)。 ⚠️ 本文不含任何密碼、passphrase、token 值。需要 passphrase 時一律查部署文件或 .env

變更紀錄

日期 內容
2026-08-16 首版(CM-1239):研判流程、LC 後台頁與 CLI 兩種簽發路徑、token 三性質與過期重簽、交付與失敗排查

§1

一、這件事的全貌

Guidant AI 落地部署版在啟動時與運行期間會比對產物檔案的 SHA-256 與原廠簽章 manifest。比對失敗即進入鎖定:正常服務不啟動,改由一個極簡的 lockdown 殼回應所有請求(HTTP 503),客戶端看到一頁「系統已鎖定」。

解鎖的唯一途徑,是原廠用 License Center 私鑰簽出的一次性 unlock token。沒有其他後門、沒有旁路開關——這是刻意的設計(design.md D6/D8)。

客戶端偵測到完整性驗證失敗
        │
        ▼
服務鎖定(lockdown 殼回 503,畫面顯示事件編號)
        │
        ▼
客戶透過人工通道(電話/郵件)回報 ── 提供 machine_fingerprint + tamper_event_id
        │
        ▼
【原廠】研判(誤鎖 or 真竄改)→ 兩者都走同一條簽發路徑,但研判紀錄要留
        │
        ▼
【原廠】License Center 簽發 unlock token(後台頁 或 CLI)
        │
        ▼
交付客戶(token 檔)
        │
        ▼
客戶把檔案放進主機 pki 目錄 → docker restart → 啟動時核銷四關 → 解鎖

🔴 解鎖不豁免當次的完整性驗證。 核銷成功後啟動流程仍會往下跑完整 manifest 比對——被改動的檔案若沒還原,會立刻再次鎖定(新的事件編號、新的 token)。因此交付 token 前必須先確認客戶已把產物還原(或已重新部署原廠 image)。


§2

二、前置研判

2.1 兩種情況、同一條路徑

情況 典型徵兆 處置
誤鎖 客戶未做任何主機層操作;或磁碟/檔案系統異常、部署過程中斷、image 版本與 manifest 版本不匹配 簽發 token,並排查根因避免再犯
真竄改 客戶確認有人以 SSH/docker exec 動過產物;或不符檔案的 mtime 落在可疑時間點 依商務/法務流程處理後,視決策簽發

兩者走同一條簽發路徑(D6 定案)——技術上不做區分,因為機器端無從自證。差別在紀錄:簽發表單的「研判備註」欄必須寫清楚 ①客戶回報管道與時間 ②判定為誤鎖或真竄改 ③判定依據。這欄會與 token 一起落進 License Center 的 unlock_token_issuance 稽核帳(append-only),日後「誰為什麼簽了什麼」要答得出來。

2.2 邊界:不要承諾查明「是誰改的」

產品沒有、也不可能有「是誰改了檔案」這個答案——竄改動作走 SSH/docker exec 等主機層管道,不經過任何業務邏輯(design.md §4.4.3)。

系統蒐集到的是檔案證據(不符檔案的 mtimectimeownersize)與若剛好有業務 session 在跑時的旁證,不是身分溯源。對客戶溝通時:

  • ✅ 可以說:「產品完整性驗證失敗,事件編號 evt-…,不符檔案 N 個,最近修改時間為 …」
  • ❌ 不要說:「我們查到是誰改的」「這是某某帳號做的」

契約層面綁的是「完整性驗證失敗即構成違約事由」這個客觀事實,不以查明具體操作人員為前提(措辭見 design.md §4.4.3)。

2.3 蒐集研判材料

要客戶提供(或自行從回報事件查):

材料 來源
machine_fingerprint(64 hex) 客戶端鎖定畫面/伺服器啟動 log 的 machine_fingerprint: 那一行
tamper_event_idevt- 開頭) 同上,tamper_event_id: 那一行
不符檔案清單與 metadata 客戶端 docker logs <容器>[FATAL] 區塊;或 License Center 收到的 D9 回報事件(有網環境才有)
客戶端最近的操作紀錄 客戶自述(部署、升級、手動改檔)

D9 在線回報:客戶端偵測到竄改時會 best-effort 把事件 POST 給 License Center(POST /api/internal/report-tamper,存於 tamper_events 表)。發得出就發、發不出不重試——所以「LC 這邊查不到事件」不代表客戶沒被鎖定,離線環境本來就送不出來。


§3

三、簽發 unlock token(路徑 A:LC 後台頁,常用)

3.1 進入位置

License Center 後台 → 導覽列「解鎖 token」(路徑 /unlock),需登入。

服務位址:http://<LC 主機>:5062(STG 在 188 /opt/license_center,systemd unit license-center.service)。

3.2 填寫欄位

欄位 必填 說明
**機器指紋(machine_fingerprint) 客戶回報的 64 個十六進位字元。須完全一致**——這是 token 綁機的依據
竄改事件編號(tamper_event_id) evt- 開頭。token 只解得開這一次事件
效期(小時) 預設 72 小時,允許範圍 1~336(14 天)
簽發人 自己的名字/工號,落稽核帳
研判備註 見 §2.1,強烈建議填
私鑰 passphrase 視部署 僅在該 LC 站台未於環境設定中配置 passphrase 時才出現此欄;已配置時系統自動解鎖私鑰,表單不顯示這一欄。passphrase 值請查該站台的部署設定,本文不記載

服務端守門(三個入口共用,抄漏一段會在這裡就被擋下):

  • 機器指紋必須是 64 個十六進位字元
  • 事件編號必須以 evt- 開頭
  • 效期必須在 1~336 小時之間

這些守門刻意放在服務層而非表單層:抄漏一段的值若放行簽出,客戶端拒收的原因會顯示成「這是為另一台機器簽發的 token」,現場查不出真因。

3.3 簽發後

送出成功會跳到結果頁,顯示 token_id、有效期限與完整 payload。按「下載」取得檔案:

  • 檔名固定 unlock.token(客戶下載完直接丟進目錄,不必改名)
  • 內容是 v2 信封裸 JSON,刻意不套 v3 armor——armor 標頭寫的是 GUIDANT LICENSE,套上去會讓客戶誤以為是授權照而拿去「授權管理」頁上傳

下載動作本身也落 log(誰拿走了哪一張)。


§4

四、簽發 unlock token(路徑 B:CLI,air-gapped 保底)

簽發站的 Web 後台不可達,或作業環境完全離線時使用。與後台頁同一條 code path、同一把私鑰、同樣寫稽核帳

在 LC 部署目錄(如 /opt/license_center)執行:

poetry run python -m license_center.cli.main issue-unlock-token \
  --machine-fingerprint <客戶回報的 64 hex> \
  --tamper-event-id evt-xxxxxxxx \
  --issuer <簽發人> \
  --valid-hours 72 \
  --remark "客戶電話回報;判定為部署中斷造成的誤鎖" \
  --output /tmp/unlock.token
參數 說明
--machine-fingerprint 必填,64 hex
--tamper-event-id 必填,evt- 前綴
--issuer 必填
--valid-hours 選填,預設同後台頁(72)
--remark 選填,研判備註
--private-key-path 選填,預設值即標準私鑰路徑
--output 選填;未指定則把 token 印到 stdout

執行時會互動式要求輸入私鑰 passphrase(不接受命令列參數帶入,避免落進 shell history)。

⚠️ 簽出 token 等於解除客戶端該次鎖定——請先完成研判再執行。

指定 --output 時,CLI 會提示:交付客戶後請其將檔案更名為 unlock.token 放進竄改標記目錄(預設 /opt/guidant/pki/)並重啟服務。


§5

五、token 的三個性質(跟客戶說明時的重點)

性質 機制 對客戶的意義
一次性 payload 內含 64 hex 隨機 nonce;核銷成功後記進客戶端的 used-nonces.json,同一張再用會被拒 用過就不能留著下次用
綁機 payload 內含 machine_fingerprint,核銷時與本機重算值比對 拿到另一台機器上無效
短效 payload 內含 expires_at,逾期核銷一律拒 預設 72 小時內要用掉

再加上綁事件tamper_event_id 必須等於客戶端目前標記檔內的事件編號——每一次鎖定都要原廠重新研判簽發,舊 token 不能沿用

過期或作廢後的重簽流程

token 過期、客戶弄丟、或「解鎖後檔案沒還原導致立刻再次鎖定」時:

  1. 請客戶重新回報當前畫面上的事件編號(再次鎖定會產生新的 tamper_event_id,舊的那張 token 對它無效)
  2. 機器指紋通常不變(同一台機器),但仍請客戶一併確認
  3. 依 §3 或 §4 重新簽一張,研判備註註明是重簽及原因

§6

六、交付客戶

  1. 透過既有安全管道(郵件/客戶入口)交付 unlock.token
  2. 同時提醒客戶三件事
    • 先把被改動的檔案還原(或重新部署原廠 image),否則解鎖後會立刻再次鎖定
    • 放檔位置與重啟方式見客戶手冊(docs/user-manual/system-integrity-lockdown-guide.md
    • 🔴 重啟必須用 docker restart,不可 docker rm 後重建容器——重建可能改變機器指紋,token 隨即作廢
  3. 指引客戶完成後回報結果;若失敗,請其提供啟動 log 的 [INTEGRITY] 區塊(失敗訊息會明確指出是四關中的哪一關)

§7

七、客戶端核銷失敗的排查對照

客戶端核銷會印出明確的失敗原因([INTEGRITY] ⛔ 解鎖 token 核銷未通過)。對照處置:

客戶端訊息要點 真因 處置
簽章驗證失敗 / 不是解鎖 token 檔案被文字編輯器改動過、傳輸損毀,或客戶放錯檔(例如放了授權照) 重新交付原始下載檔,勿用編輯器開啟
已於 … 過期 超過 expires_at 重簽一張(§5)
機器指紋與本機不符(訊息會印出本機指紋) 指紋抄錯,或客戶重建了容器導致指紋改變 用訊息印出的本機指紋重簽
事件編號與本機目前的鎖定不符(訊息會印出本機編號) 拿舊事件的 token 解新一次鎖定 用訊息印出的本機事件編號重簽
已經使用過,不可重複使用 同一張用第二次 重簽一張
鎖定紀錄檔已毀損,無法核對事件編號 客戶端標記檔損壞 一律不放行解鎖(防繞法);需原廠協助人工處理
驗證通過但無法寫入已用紀錄(防重放) 標記目錄不可寫(掛載或權限問題) token 尚未作廢——請客戶修正 /opt/guidant/pki 掛載為可寫的持久 volume 後,同一張再試

四種失敗訊息刻意各不相同且含處置建議——含糊的訊息會讓現場把「指紋抄錯」誤判成「原廠簽錯了」,來回數輪查不出來。


§8

附錄:名詞對照

名詞 意思
完整性驗證 比對產物檔案 SHA-256 與原廠簽章 manifest
lockdown 殼 驗證失敗時取代正常服務的極簡 HTTP responder,任何請求都回 503 鎖定資訊,零業務功能、零寫入面
machine_fingerprint 本機識別碼(sha256 hex,64 字元),由 /etc/machine-id 推導
tamper_event_id 單次鎖定事件編號,evt- 開頭
unlock token 原廠簽發的一次性、綁機、短效解鎖憑證,檔名 unlock.token
nonce token 內的一次性隨機值,客戶端記帳防重放
D9 回報 客戶端 best-effort 把 tamper 事件送回 License Center 留存(發不出不重試)