開發者第一次連線至雲端 Mac 時,終端機通常會詢問是否接受未知主機。手動操作時輸入 yes 很快,搬到 CI 後改用 StrictHostKeyChecking=no 看似更省事,但兩種做法都略過了身分驗證。只要 DNS、位址設定或網路路徑遭到錯誤導向,憑證與建置資料就可能被送往錯誤的機器。正確做法是先透過可信任的終端機取得主機金鑰指紋,再限制開發機與自動化工作只接受這把金鑰。
先建立獨立的信任基準
主機金鑰用來證明「目前連線的確實是預期中的那台 Mac」,與使用者登入時使用的私密金鑰並不相同。以 Ed25519 主機金鑰為例,可以透過 XcodeVM 控制台提供的 Web VNC 開啟本機終端機,直接在目標機器上讀取公開金鑰指紋:
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256
請記錄完整的 SHA256: 值,並一併記下執行個體識別碼、連線位址與讀取方式。不要只透過尚待驗證的 SSH 連線取得這個值,否則驗證流程會形成封閉迴圈。
接著在開發機上讀取遠端目前提供的公開金鑰:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -T 5 -t ed25519 "$MAC_HOST" > ~/.ssh/xcodevm_known_hosts.new
ssh-keygen -lf ~/.ssh/xcodevm_known_hosts.new -E sha256
逐字元比對兩邊的指紋。確認完全相符後,再安裝檔案:
install -m 600 ~/.ssh/xcodevm_known_hosts.new ~/.ssh/xcodevm_known_hosts
rm -f ~/.ssh/xcodevm_known_hosts.new
ssh-keyscan解決的是「讀取公開金鑰」,不是「證明公開金鑰可信」。若沒有獨立取得的指紋作為基準,掃描結果不能直接寫入正式環境的信任檔案。
將 known_hosts 與專案隔離
全域 ~/.ssh/known_hosts 適合日常互動式連線,但團隊專案最好使用獨立檔案。如此一來,所有變更都能明確審查,也不會因個人清理紀錄而影響自動化工作。
在 ~/.ssh/config 中建立別名:
Host xcodevm-build
HostName 192.0.2.10
User builder
IdentityFile ~/.ssh/xcodevm_build_ed25519
IdentitiesOnly yes
UserKnownHostsFile ~/.ssh/xcodevm_known_hosts
StrictHostKeyChecking yes
HashKnownHosts yes
ServerAliveInterval 30
ServerAliveCountMax 3
IdentitiesOnly yes 可避免用戶端依序嘗試過多身分金鑰檔案;StrictHostKeyChecking yes 則可確保遇到未知或已變更的金鑰時立即失敗。連線前應先檢查展開後的設定,避免別名遭其他規則覆寫:
ssh -G xcodevm-build |
grep -E '^(hostname|user|identityfile|userknownhostsfile|stricthostkeychecking) '
如果同一個專案使用多台機器,應為每台機器保留獨立項目,並將執行個體識別碼寫入內部變更紀錄。不要用一個過於寬泛的別名指向可能隨機變動的位址。
在 CI 中固定指紋
CI 不應長期沿用某位開發者的個人信任檔案。更穩妥的做法是將預期的 SHA256 指紋儲存為受保護變數,讓每次工作先掃描並比對,再建立暫時的 known_hosts。
set -euo pipefail
: "${MAC_HOST:?MAC_HOST is required}"
: "${EXPECTED_ED25519_SHA256:?fingerprint is required}"
scan_file="$(mktemp)"
known_hosts_file="$(mktemp)"
trap 'rm -f "$scan_file" "$known_hosts_file"' EXIT
ssh-keyscan -T 5 -t ed25519 "$MAC_HOST" > "$scan_file" 2>/dev/null
actual="$(ssh-keygen -lf "$scan_file" -E sha256 | awk '{print $2}')"
if [ "$actual" != "$EXPECTED_ED25519_SHA256" ]; then
printf '%s
' "SSH host key verification failed" >&2
exit 1
fi
install -m 600 "$scan_file" "$known_hosts_file"
ssh \
-o UserKnownHostsFile="$known_hosts_file" \
-o StrictHostKeyChecking=yes \
-o IdentitiesOnly=yes \
"$MAC_USER@$MAC_HOST" \
'uname -m && sw_vers -productVersion'
私密金鑰、預期指紋與主機位址應分開管理。日誌可以輸出實際指紋以協助除錯,但不得列印私密金鑰內容、連線密碼,或未經遮蔽處理的環境變數。
正確處理金鑰變更
主機金鑰變更可能源自系統重設、重新產生主機金鑰或重複使用連線位址,也可能表示連線遭到導向錯誤目標。看到 REMOTE HOST IDENTIFICATION HAS CHANGED 時,應先停止自動化工作,不能直接刪除紀錄後重試。
安全輪替應依照以下順序進行:
- 在控制台確認執行個體識別碼、連線位址與近期操作是否符合預期。
- 透過 Web VNC 的本機終端機重新讀取 Ed25519 公開金鑰指紋。
- 由另一位維護者複核新舊指紋與變更原因。
- 掃描遠端公開金鑰並驗證新指紋,產生新的信任檔案。
- 先更新測試工作,完成一次唯讀連線與環境檢查。
- 再更新正式工作,並撤銷舊指紋。
若使用雜湊化紀錄,可以先找出舊項目:
ssh-keygen -F "$MAC_HOST" -f ~/.ssh/xcodevm_known_hosts
ssh-keygen -R "$MAC_HOST" -f ~/.ssh/xcodevm_known_hosts
第二個指令只能在新指紋已透過獨立管道確認後執行。刪除舊紀錄只是輪替步驟,不是驗證程序。
使用檢查清單完成驗收
上線前至少應檢查以下項目:
| 檢查項目 | 合格條件 |
|---|---|
| 指紋來源 | 從目標機器的本機終端機讀取 |
| 金鑰演算法 | 明確固定為 Ed25519,不接受任意演算法 |
| 檔案權限 | .ssh 為 700,信任檔案為 600 |
| 用戶端策略 | StrictHostKeyChecking=yes |
| CI 隔離 | 每個工作使用獨立的暫存檔案 |
| 變更處理 | 先停止工作、再核驗、最後替換 |
| 日誌內容 | 保留指紋與失敗階段,不洩漏憑證 |
此外還應執行一次負向測試:暫時將預期指紋改成錯誤值,確認工作會在真正執行遠端指令前結束。接著恢復正確值,再驗證唯讀指令、儲存庫存取與建置進入點。如此才能證明保護措施確實有參與執行,而不只是存在於設定檔中。
SSH 信任鏈不需要複雜的系統,關鍵在於把「首次接受」與「變更後重新連線」從臨時點選確認,轉換成可供審查的流程。可信任的終端機提供基準,專案檔案保存固定關係,CI 在每次連線前進行驗證,任何變更都必須先停下來核對。完成這些步驟後,遠端開發與自動建置才能共用同一套明確的主機身分邊界。
常見問題
ssh-keyscan 取得的主機金鑰可以直接信任嗎?
不可以。ssh-keyscan 只會讀取遠端當下提供的公鑰,仍須與透過本機終端等獨立可信任路徑取得的 SHA256 指紋比對,確認一致後才能加入信任。
主機金鑰改變後可以刪除 known_hosts 記錄再重新連線嗎?
不應把刪除記錄視為驗證。應先停止連線並確認位址與系統狀態,再從可信任終端重新讀取指紋,核對一致後才替換舊記錄。
CI 應該共用開發者個人的 known_hosts 嗎?
不建議。CI 應建立權限為 600 的工作專用檔案,明確啟用 StrictHostKeyChecking,並在工作結束後刪除暫存信任資料。
XcodeVM 雲端 Mac
選擇適合目前工作負載的獨享實體主機
核對記憶體、儲存空間、節點與計費週期後,即可進入下單設定。所有節點全年正常運作,實際可用性以控制台即時回傳的結果為準。