資安指南

SSH 金鑰的最小權限:別把正式環境的 root 金鑰放進可能被入侵的臨時環境

你是否從 GPU 雲的 pod、CI runner 這類可能被入侵的臨時環境,向正式環境登記了 SSH 金鑰?一旦外洩,別人就能以 root 進入正式環境。本文說明如何用非 root 使用者,加上限制為單一操作的 command 限制金鑰,把金鑰做成最小權限。

發布於 2026-06-11 更新於 2026-10-06 閱讀時間 5 分鐘
本頁內容

適用對象:從 GPU 雲的 pod、CI runner、一次性 VM 這類臨時環境透過 SSH 連到正式環境的人。這裡不講攻擊手法,只講 把金鑰做成最小權限、讓外洩造成的損害更小。金鑰分離的基本做法,請參考 安全底線清單。

本站的看法:把臨時環境視為不可信任

用完即丟的運算環境常常省略加固,是可能被入侵的地方。把正式環境的 root 金鑰放在那裡,該環境被入侵就等於正式環境被入侵。本站每台主機使用不同的金鑰,每把金鑰只限一個用途,不用就立刻刪除(→ 分開金鑰以減少損害)。不把金鑰當作「方便的工具」,而是當作「一旦外洩就能進入所有伺服器的最重要資產」,終究是最安全的做法。

✗ 臨時環境上的 root 金鑰

環境被入侵 → 用那把金鑰取得正式環境的 root → 檔案、機密、其他服務,全部都能觸及。

○ 限制指令的非 root 金鑰

即使同一把金鑰外洩,也只能執行指定的那一條指令。沒有 shell、沒有轉發,損害被限制在單一操作。

從一開始就讓金鑰能做的事保持最小。外洩時的損害會因此不同。

為什麼臨時環境上的 root 金鑰很危險

臨時環境常因「反正很快就刪」而省略加固,因此容易成為攻擊目標,也方便被當作攻擊正式環境的跳板。正式環境的 root 金鑰放在那裡,會讓損害變得最大。

root→全部
外洩的金鑰讓人完全掌控正式環境
用完即丟
臨時環境常省略加固
重複使用
一次外洩讓每台主機都曝光
最小權限
把金鑰限制為單一操作,損害也隨之受限

假設你為了運算開了一個 pod,為了從那裡處理正式環境,把金鑰登記到 root 的 authorized_keys。很方便,但如果那個 pod 被入侵,攻擊者就會用那把金鑰以 root 進入正式環境。無論攻擊是從 RCE 還是金鑰外洩開始,最後的結果都一樣:正式環境被偷來的金鑰拿下(相關:金鑰經由 RCE 被盜並遭惡意刷費)。

把金鑰做成最小權限的 3 個步驟

從「因為方便就給全部權限」,改成「把能做的事限制到最小」。

1

刪除臨時環境的 root 金鑰

從正式環境的 ~/.ssh/authorized_keys 刪除不再使用的金鑰。如果沒在用,刪除不會有任何影響。首先,不要留下沒在用的金鑰。
2

即使再次需要,也不要回到 root

為了正式環境的作業重新登記時,建立一個專用的非 root 使用者,把金鑰登記給它。即使外洩,影響範圍也只限於該使用者的權限。
3

把金鑰限制為單一操作

在 authorized_keys 的金鑰那一行加上 command="..." 和 restrict。即使登入成功,也只能執行指定的那一條指令,連接埠轉發和 shell 都被停用。外洩時損害被限制在那一個操作。
# Register a key restricted to ONE operation in a non-root user's authorized_keys.
# Logging in with it can run only the given command; forwarding and PTY are off.
command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci

restrict 一次停用轉發、agent 轉發、PTY、X11 等功能;command="..." 把可執行的操作固定為一個。基本做法是按用途建立只限單一用途的金鑰,例如只拉取部署、只執行特定腳本。

常見的做法(危險)

  • 從 pod/CI 把金鑰登記到正式環境的 root
  • 用完的金鑰留著不管
  • 一把金鑰在多台主機上重複使用
  • 到處散布能取得完整 shell 的金鑰

最小權限的做法

  • 只以非 root 使用者進入正式環境
  • 金鑰限制為單一操作(command 限制)
  • 按主機和用途分開金鑰
  • 一不再使用,就從 authorized_keys 刪除

把重複使用的金鑰當作最重要的資產

把金鑰當作「方便的工具」,就會重複使用、忘記刪除。相反地,要把它當作一旦外洩就能進入所有伺服器的最重要資產。按主機和用途分開,不讓正式環境的金鑰出現在臨時環境,用完一定刪除。光是這樣,就能防止一次外洩擴散到全部。

金鑰那一行可以加上的主要限制

在 OpenSSH 中,寫在 authorized_keys 金鑰那一行開頭的選項,可以縮小該金鑰能做的事。常用的有:

選項限制的內容使用時機
command="..."用該金鑰登入時只執行指定的指令;用戶端送來的指令會被忽略只做一件固定工作的金鑰,例如拉取部署或備份
restrict一次停用連接埠、agent 和 X11 轉發、PTY 分配,以及執行 ~/.ssh/rc(OpenSSH 7.2 以上)幾乎所有自動化用的金鑰;只把需要的功能(例如 pty)重新打開
from="..."除了金鑰之外,用戶端的主機名稱或 IP 位址也必須符合清單來源 IP 固定的 CI 或作業伺服器
expiry-time="..."超過指定日期時間後不再接受該金鑰(OpenSSH 7.7 以上)只供臨時作業使用的金鑰
PermitRootLogin(sshd_config 中)是否允許直接以 root 登入;forced-commands-only 只允許帶有 command= 的金鑰以 root 登入除非真的需要直接以 root 存取,否則設為 no

OpenSSH 的版本說明把 restrict 描述為包含「目前及未來所有的金鑰限制」。與其逐一列出 no-pty、no-port-forwarding 等選項,不如加上 restrict,再只打開需要的功能,這樣將來新增功能時也不會有漏洞。

# Also narrowing the source and the lifetime (the IP is a documentation address)
from="192.0.2.10",expiry-time="20261231",command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci-2026-10

如果要管理許多金鑰的有效期限,也可以使用 OpenSSH 憑證(用 ssh-keygen -s 簽署,用 -V 設定有效期間,用 force-command 或 source-address 加以限制)。規模不大的話,從金鑰那一行的選項開始就足夠了。

如何檢查自己的環境

以下檢查只讀取你所管理伺服器的設定和記錄。變更設定時,保持一個 SSH 連線開著,從另一個終端機測試新的連線,確認自己沒有被鎖在外面之後,再關閉舊的連線。

1. 列出已登記的金鑰

# Show the fingerprint and comment of each key registered for root and each user
sudo ssh-keygen -lf /root/.ssh/authorized_keys
sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keys

每把金鑰會印出一行,例如 256 SHA256:... deploy@ci (ED25519)。用註解(最後的 deploy@ci 部分)找出來源不明的金鑰、已用完的臨時環境的金鑰,以及登記給 root 的金鑰。舊的檔名 authorized_keys2 預設也會被讀取,如果存在也用同樣方式檢查。

2. 確認 sshd 實際使用的設定

# Check the syntax, then print the settings actually in effect
sudo sshd -t
sudo sshd -T | grep -iE 'permitrootlogin|authorizedkeysfile|pubkeyauthentication|passwordauthentication'

sshd -T 會印出讀取設定檔之後實際生效的值。如果 permitrootlogin 是 yes 或 prohibit-password,只要有金鑰就能直接以 root 登入。如果 authorizedkeysfile 指向預設以外的位置,把那個位置也加進步驟 1。

3. 查看哪些金鑰實際被使用

用金鑰登入成功後,sshd 通常會記錄像 Accepted publickey for deploy from 192.0.2.10 port 50022 ssh2: ED25519 SHA256:... 這樣的一行。在 Debian 和 Ubuntu 上看 /var/log/auth.log,在 Red Hat 系統上看 /var/log/secure,在 systemd 主機上則用 journalctl -u ssh(Red Hat 系統為 -u sshd)。

把這些指紋與步驟 1 的清單對照,就能看出哪些金鑰沒有被使用。NIST 關於 SSH 金鑰管理的報告(NISTIR 7966)建議,在刪除未使用的金鑰之前觀察記錄約 6 到 12 個月,以免誤刪很少使用的金鑰。確定已用完的金鑰,例如臨時環境的金鑰,可以立即刪除。

4. 檢查臨時環境那一側

  • CI 的 secrets 或 GPU pod 的 ~/.ssh 裡,是否有能連到正式環境的私鑰?
  • 是否用 ssh -A(agent 轉發)連到臨時環境,再從那裡前往正式環境?
  • 是否有一把金鑰在多個 CI 系統或伺服器之間共用?

常見錯誤與修正

  • 只刪除臨時環境上的私鑰就結束:如果私鑰已經被複製,只要公鑰還留在伺服器上,它就繼續有效。一定要從正式環境的 authorized_keys 刪除對應的那一行。
  • 在 command= 呼叫的腳本中直接執行用戶端的指令(環境變數 SSH_ORIGINAL_COMMAND):這等於沒有限制。只有在值與固定選項之一完全一致時才使用它。
  • 改用非 root 使用者,卻允許不受限的 sudo:如果該使用者可以不輸入密碼就 sudo 任何指令,實際上就等於 root。如果需要 sudo,在 sudoers 中限制為一條指令。
  • 用 ssh -A 進入臨時環境:在那裡擁有足夠權限的人,可以透過轉發的連線使用你本機 agent 中載入的金鑰(OpenSSH 手冊對此有警告)。如果需要經過某台主機,使用 ssh -J(經由跳板主機連線)。
  • 金鑰沒有註解:檢查時無法分辨來源。建立時就加上用途和日期,例如 ssh-keygen -C "deploy@ci-2026-10"。
  • 多人或多個環境共用一把金鑰:NISTIR 7966 也要求禁止共用私鑰或把私鑰複製到其他系統。這樣無法追蹤誰在哪裡使用了它,而且一次外洩就必須在所有地方更換。

實際案例:遭入侵的 CI 工具蒐集的資料中包含 SSH 金鑰

2026 年 3 月,開源漏洞掃描工具 Trivy 出現了惡意版本,其 GitHub Actions 的標籤也被改寫。據開發商 Aqua Security 表示,惡意程式碼從 CI runner 蒐集憑證並傳送出去,目標包括 ~/.ssh 中的私鑰、authorized_keys、known_hosts 以及 SSH 主機金鑰。Aqua 要求所有可能執行過受影響版本的人,把該管線能讀取的所有機密(包括 SSH 金鑰)視為已曝光,並立即輪換(詳情:Trivy 供應鏈入侵事件(2026 年 3 月),英文)。

在 2021 年的 Codecov 事件中,CI 中執行的腳本也在來源端被竄改,客戶 CI 環境中的機密被傳送出去長達約兩個月(→ Codecov 供應鏈攻擊)。

兩者都顯示,即使你自己的程式碼沒有問題,CI 裡的金鑰也可能被拿走。如果被拿走的金鑰登記在正式環境的 root,正式環境在那一刻就被交出去了。如果使用專用的非 root 使用者、command= 加上 restrict,並用 from= 縮小來源,同一把外洩的金鑰就只能做你定義好的那一件工作,從其他地方的連線也會被拒絕。

本站是怎麼做的

本站每台伺服器使用專用的金鑰,從不重複使用。部署落在一個非 root、專門用途的使用者上,正式環境是單向的:「只接收,不主動外出取得」(→ 正式環境只接收)。本站不會在臨時運算環境留下通往正式環境的常駐金鑰;只在需要時、以最小權限連線。理由正是本文所說的:事先讓每把金鑰能做的事保持最小,讓一次入侵無法擴散到全部。

接下來讀

出處

  • OpenBSD manual「sshd(8)」AUTHORIZED_KEYS FILE FORMAT(command、restrict、from、expiry-time、SSH_ORIGINAL_COMMAND):man.openbsd.org
  • OpenBSD manual「sshd_config(5)」PermitRootLogin 與 AuthorizedKeysFile:man.openbsd.org
  • OpenBSD manual「ssh(1)」-A(agent 轉發的注意事項)與 -J:man.openbsd.org
  • OpenBSD manual「ssh-keygen(1)」-l、-C 與憑證選項:man.openbsd.org
  • OpenSSH 7.2 版本說明(新增 restrict):openssh.org
  • OpenSSH 7.7 版本說明(新增 expiry-time):openssh.org
  • NIST「NISTIR 7966: Security of Interactive and Automated Access Management Using Secure Shell (SSH)」(2015 年 10 月):csrc.nist.gov
  • Wikibooks「OpenSSH/Logging and Troubleshooting」(登入成功記錄中的金鑰指紋):en.wikibooks.org
  • Aqua Security「Trivy Supply Chain Attack: What You Need to Know」(2026):aquasec.com
  • Codecov「Bash Uploader Security Update」(2021):about.codecov.io

FAQ

Q為什麼把 root 金鑰放進臨時環境很危險?
A

GPU 雲的 pod、CI runner 這類臨時環境用完即丟,常常省略加固,因此可能被入侵。在那裡放上通往正式環境的 root 金鑰,該環境一旦被入侵,攻擊者就連同金鑰取得正式環境的 root。把臨時環境視為不可信任,不要在其中放正式環境的 root 金鑰。

Q如果還是需要從臨時環境連到正式環境呢?
A

不要回到 root。建立一個專用的非 root 使用者,登記一把只能做單一操作的金鑰。在 authorized_keys 中加上 command 限制和 restrict 旗標,即使登入成功,也只能執行那一條指令。外洩時損害就被限制在那一個操作。

Q金鑰管理最重要的原則是什麼?
A

把重複使用的金鑰當作最重要的資產,絕不要建立一把外洩就全部曝光的架構。按主機、按用途分開金鑰,不再需要時就從 authorized_keys 刪除。這樣可以防止一次外洩擴散到全部。

Q怎麼確認伺服器上登記了哪些 SSH 金鑰?
A

對每個使用者的 ~/.ssh/authorized_keys 執行 ssh-keygen -lf,就能列出每把已登記金鑰的指紋(SHA256:...)和註解。包括 root 在內的所有帳號都要檢查,找出說不出來源的金鑰,以及已不再使用的臨時環境的金鑰。要知道哪些金鑰實際被使用,就把這些指紋與 sshd 寫入記錄的登入成功紀錄對照。

QPermitRootLogin 應該設成什麼?
A

OpenSSH 的預設值是 prohibit-password:root 不能用密碼登入,但只要有金鑰就能直接以 root 登入。如果不需要直接以 root 登入,就設為 no,從非 root 使用者進行作業。如果像備份這類工作真的需要 root 金鑰,forced-commands-only 只允許帶有 command="..." 選項的金鑰以 root 登入。

Q如何安全地從 CI 或 GitHub Actions 透過 SSH 部署?
A

CI runner 是典型的臨時環境,而且確實發生過 CI 中使用的工具遭入侵、包括 SSH 金鑰在內的機密被蒐集的案例。把部署用金鑰登記到專用的非 root 使用者,用 command="..." 和 restrict 限制為單一的 pull/部署操作。如果來源 IP 固定,再用 from="..." 縮小範圍。每條管線、每個環境使用不同的金鑰;當有入侵事件公布時,先刪除舊金鑰,再登記新金鑰。