適用對象:原廠客服/技術支援/授權簽發人員(坐在 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 三性質與過期重簽、交付與失敗排查 |
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)。
| 情況 | 典型徵兆 | 處置 |
|---|---|---|
| 誤鎖 | 客戶未做任何主機層操作;或磁碟/檔案系統異常、部署過程中斷、image 版本與 manifest 版本不匹配 | 簽發 token,並排查根因避免再犯 |
| 真竄改 | 客戶確認有人以 SSH/docker exec 動過產物;或不符檔案的 mtime 落在可疑時間點 |
依商務/法務流程處理後,視決策簽發 |
兩者走同一條簽發路徑(D6 定案)——技術上不做區分,因為機器端無從自證。差別在紀錄:簽發表單的「研判備註」欄必須寫清楚 ①客戶回報管道與時間 ②判定為誤鎖或真竄改 ③判定依據。這欄會與 token 一起落進 License Center 的 unlock_token_issuance 稽核帳(append-only),日後「誰為什麼簽了什麼」要答得出來。
產品沒有、也不可能有「是誰改了檔案」這個答案——竄改動作走 SSH/docker exec 等主機層管道,不經過任何業務邏輯(design.md §4.4.3)。
系統蒐集到的是檔案證據(不符檔案的 mtime/ctime/owner/size)與若剛好有業務 session 在跑時的旁證,不是身分溯源。對客戶溝通時:
契約層面綁的是「完整性驗證失敗即構成違約事由」這個客觀事實,不以查明具體操作人員為前提(措辭見 design.md §4.4.3)。
要客戶提供(或自行從回報事件查):
| 材料 | 來源 |
|---|---|
machine_fingerprint(64 hex) |
客戶端鎖定畫面/伺服器啟動 log 的 machine_fingerprint: 那一行 |
tamper_event_id(evt- 開頭) |
同上,tamper_event_id: 那一行 |
| 不符檔案清單與 metadata | 客戶端 docker logs <容器> 的 [FATAL] 區塊;或 License Center 收到的 D9 回報事件(有網環境才有) |
| 客戶端最近的操作紀錄 | 客戶自述(部署、升級、手動改檔) |
D9 在線回報:客戶端偵測到竄改時會 best-effort 把事件 POST 給 License Center(
POST /api/internal/report-tamper,存於tamper_events表)。發得出就發、發不出不重試——所以「LC 這邊查不到事件」不代表客戶沒被鎖定,離線環境本來就送不出來。
License Center 後台 → 導覽列「解鎖 token」(路徑 /unlock),需登入。
服務位址:http://<LC 主機>:5062(STG 在 188 /opt/license_center,systemd unit license-center.service)。
| 欄位 | 必填 | 說明 |
|---|---|---|
| **機器指紋(machine_fingerprint) | ✅ | 客戶回報的 64 個十六進位字元。須完全一致**——這是 token 綁機的依據 |
| 竄改事件編號(tamper_event_id) | ✅ | evt- 開頭。token 只解得開這一次事件 |
| 效期(小時) | ✅ | 預設 72 小時,允許範圍 1~336(14 天) |
| 簽發人 | ✅ | 自己的名字/工號,落稽核帳 |
| 研判備註 | — | 見 §2.1,強烈建議填 |
| 私鑰 passphrase | 視部署 | 僅在該 LC 站台未於環境設定中配置 passphrase 時才出現此欄;已配置時系統自動解鎖私鑰,表單不顯示這一欄。passphrase 值請查該站台的部署設定,本文不記載 |
服務端守門(三個入口共用,抄漏一段會在這裡就被擋下):
evt- 開頭這些守門刻意放在服務層而非表單層:抄漏一段的值若放行簽出,客戶端拒收的原因會顯示成「這是為另一台機器簽發的 token」,現場查不出真因。
送出成功會跳到結果頁,顯示 token_id、有效期限與完整 payload。按「下載」取得檔案:
unlock.token(客戶下載完直接丟進目錄,不必改名)GUIDANT LICENSE,套上去會讓客戶誤以為是授權照而拿去「授權管理」頁上傳下載動作本身也落 log(誰拿走了哪一張)。
簽發站的 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/)並重啟服務。
| 性質 | 機制 | 對客戶的意義 |
|---|---|---|
| 一次性 | payload 內含 64 hex 隨機 nonce;核銷成功後記進客戶端的 used-nonces.json,同一張再用會被拒 |
用過就不能留著下次用 |
| 綁機 | payload 內含 machine_fingerprint,核銷時與本機重算值比對 |
拿到另一台機器上無效 |
| 短效 | payload 內含 expires_at,逾期核銷一律拒 |
預設 72 小時內要用掉 |
再加上綁事件:tamper_event_id 必須等於客戶端目前標記檔內的事件編號——每一次鎖定都要原廠重新研判簽發,舊 token 不能沿用。
token 過期、客戶弄丟、或「解鎖後檔案沒還原導致立刻再次鎖定」時:
tamper_event_id,舊的那張 token 對它無效)unlock.token 檔docs/user-manual/system-integrity-lockdown-guide.md)docker restart,不可 docker rm 後重建容器——重建可能改變機器指紋,token 隨即作廢[INTEGRITY] 區塊(失敗訊息會明確指出是四關中的哪一關)客戶端核銷會印出明確的失敗原因([INTEGRITY] ⛔ 解鎖 token 核銷未通過)。對照處置:
| 客戶端訊息要點 | 真因 | 處置 |
|---|---|---|
| 簽章驗證失敗 / 不是解鎖 token | 檔案被文字編輯器改動過、傳輸損毀,或客戶放錯檔(例如放了授權照) | 重新交付原始下載檔,勿用編輯器開啟 |
| 已於 … 過期 | 超過 expires_at |
重簽一張(§5) |
| 機器指紋與本機不符(訊息會印出本機指紋) | 指紋抄錯,或客戶重建了容器導致指紋改變 | 用訊息印出的本機指紋重簽 |
| 事件編號與本機目前的鎖定不符(訊息會印出本機編號) | 拿舊事件的 token 解新一次鎖定 | 用訊息印出的本機事件編號重簽 |
| 已經使用過,不可重複使用 | 同一張用第二次 | 重簽一張 |
| 鎖定紀錄檔已毀損,無法核對事件編號 | 客戶端標記檔損壞 | 一律不放行解鎖(防繞法);需原廠協助人工處理 |
| 驗證通過但無法寫入已用紀錄(防重放) | 標記目錄不可寫(掛載或權限問題) | token 尚未作廢——請客戶修正 /opt/guidant/pki 掛載為可寫的持久 volume 後,同一張再試 |
四種失敗訊息刻意各不相同且含處置建議——含糊的訊息會讓現場把「指紋抄錯」誤判成「原廠簽錯了」,來回數輪查不出來。
| 名詞 | 意思 |
|---|---|
| 完整性驗證 | 比對產物檔案 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 留存(發不出不重試) |