<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Access on Tarragon</title><link>https://tarrragon.github.io/blog/tags/access/</link><description>Recent content in Access on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Sun, 25 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/access/index.xml" rel="self" type="application/rss+xml"/><item><title>登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/</link><pubDate>Sat, 24 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/</guid><description>&lt;p>建立頁的 SSH 金鑰欄位決定主機第一次開機之後，誰能用哪一把金鑰、以哪一個帳號登入。範圍是第一次登入用的金鑰與帳號；長期給一個團隊用的登入設計（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">身分感知代理（identity-aware proxy）&lt;/a>、短效憑證、離職撤銷）見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>。&lt;/p>
&lt;h2 id="ssh-金鑰欄位把公鑰放進主機的方式">SSH 金鑰欄位把公鑰放進主機的方式&lt;/h2>
&lt;p>Linux 主機用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 公鑰驗證登入：主機的使用者家目錄裡有一份允許清單（&lt;code>~/.ssh/authorized_keys&lt;/code>），持有清單上某一把公鑰對應私鑰的人才能登入。建立頁的 SSH 金鑰欄位把一把公鑰交給業者，業者把它存進主機的中繼資料（metadata）。Oracle Cloud 與 AWS 的 Linux 映像由 cloud-init（多數 Linux 雲端映像預先裝好、開機時讀中繼資料做初始設定的程式）在主機第一次開機時讀出公鑰、寫進預設使用者的 &lt;code>authorized_keys&lt;/code>；Google Cloud 的映像由業者的 guest agent 持續依中繼資料同步金鑰，不限第一次開機。&lt;/p>
&lt;p>Oracle Cloud 的建立頁提供四個選項：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>讓業者產生一對金鑰（Generate a key pair for me）&lt;/strong>：業者產生一對 RSA 金鑰，公鑰放進主機，私鑰讓使用者在這一頁下載。私鑰只能在這一刻下載，離開頁面之後就拿不到；官方提醒持有私鑰的人都能登入這台主機，要存在安全的位置。&lt;/li>
&lt;li>&lt;strong>上傳公鑰檔（Upload public key files）&lt;/strong>：上傳自己電腦上已經有的公鑰（&lt;code>.pub&lt;/code> 檔）。私鑰從頭到尾只在自己的電腦上。&lt;/li>
&lt;li>&lt;strong>貼上公鑰（Paste public keys）&lt;/strong>：同上，改成貼上公鑰的文字。誤貼私鑰時頁面會報錯。&lt;/li>
&lt;li>&lt;strong>不放金鑰（No SSH keys）&lt;/strong>：主機裡沒有任何允許登入的公鑰，而且官方說明選了之後就不能再提供公鑰。SSH 登入只能經由 Bastion 的 managed SSH session（見〈不放金鑰時的登入服務〉）；Bastion 的 port forwarding session 要用主機 &lt;code>authorized_keys&lt;/code> 裡事先放好的公鑰，選了 No SSH keys 的主機用不了它。所以 managed SSH session 不支援的主機（A1 機型加非 Oracle Linux，或可用的 plugin 裡沒有 Bastion 的作業系統版本，見〈不放金鑰時的登入服務〉）選了 No SSH keys，就沒有 SSH 登入的入口。其餘的入口是〈建立之後加金鑰與金鑰遺失時的入口〉列的救援入口。&lt;/li>
&lt;/ul>
&lt;p>自己已經在用 SSH 的話，上傳或貼上自己的公鑰比較好：私鑰不經過任何網頁，也不必管理另一把專門給這台主機的金鑰。本機還沒有金鑰時，用 &lt;code>ssh-keygen -t ed25519&lt;/code> 產生一對（ed25519 是新版 OpenSSH 的 &lt;code>ssh-keygen&lt;/code> 預設產生的型別），公鑰在 &lt;code>~/.ssh/id_ed25519.pub&lt;/code>；那個路徑已經有一把金鑰時，&lt;code>ssh-keygen&lt;/code> 會問要不要覆寫，回答 n，或用 &lt;code>-f &amp;lt;新的檔名&amp;gt;&lt;/code> 另存一把。&lt;/p>
&lt;h2 id="預設使用者名稱與映像檔的對應">預設使用者名稱與映像檔的對應&lt;/h2>
&lt;p>在 Oracle Cloud 與 AWS 上，公鑰寫進的是映像檔預先建立的那個使用者，名稱依作業系統而定。Oracle Cloud 的官方說明是 Ubuntu 平台映像的使用者名稱是 &lt;code>ubuntu&lt;/code>，Oracle Linux 與相容 RHEL 的平台映像是 &lt;code>opc&lt;/code>。登入指令因此是 &lt;code>ssh ubuntu@&amp;lt;主機的 IP&amp;gt;&lt;/code> 或 &lt;code>ssh opc@&amp;lt;主機的 IP&amp;gt;&lt;/code>，用錯名稱時 SSH 回的是 &lt;code>Permission denied (publickey)&lt;/code>，看起來跟金鑰錯了一樣。Google Cloud 的使用者名稱取自中繼資料裡每一把公鑰附帶的名稱，guest agent 依那個名稱在主機上建使用者。&lt;/p>
&lt;p>這個預設使用者有 &lt;code>sudo&lt;/code> 權限，而且只用金鑰登入、沒有設密碼。多人共用同一台主機時，各自建自己的使用者、各自放自己的公鑰，誰做了什麼才分得出來；共用一個帳號時，登入紀錄只顯示那個帳號，對不到是哪一個人，有人離職也無法只撤銷他一個人的存取，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a> 的〈主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回〉。&lt;/p>
&lt;h2 id="建立之後加金鑰與金鑰遺失時的入口">建立之後加金鑰與金鑰遺失時的入口&lt;/h2>
&lt;p>建立之後能不能再加金鑰，看業者用什麼把金鑰寫進主機。Oracle Cloud 的金鑰由 cloud-init 在第一次開機時寫進主機一次，之後要加金鑰，做法是先用現有的金鑰登入，再在主機裡把新的公鑰加進 &lt;code>authorized_keys&lt;/code>。Google Cloud 的 guest agent 持續同步中繼資料，主機建好之後把公鑰加進主機或專案的中繼資料，guest agent 就會把它寫進主機。AWS 的 Systems Manager Session Manager 不經過 SSH，主機的 &lt;code>authorized_keys&lt;/code> 裡沒有任何公鑰也能開工作階段，條件備齊時可以當成另一個入口。&lt;/p></description><content:encoded><![CDATA[<p>建立頁的 SSH 金鑰欄位決定主機第一次開機之後，誰能用哪一把金鑰、以哪一個帳號登入。範圍是第一次登入用的金鑰與帳號；長期給一個團隊用的登入設計（<a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">身分感知代理（identity-aware proxy）</a>、短效憑證、離職撤銷）見 <a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>。</p>
<h2 id="ssh-金鑰欄位把公鑰放進主機的方式">SSH 金鑰欄位把公鑰放進主機的方式</h2>
<p>Linux 主機用 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 公鑰驗證登入：主機的使用者家目錄裡有一份允許清單（<code>~/.ssh/authorized_keys</code>），持有清單上某一把公鑰對應私鑰的人才能登入。建立頁的 SSH 金鑰欄位把一把公鑰交給業者，業者把它存進主機的中繼資料（metadata）。Oracle Cloud 與 AWS 的 Linux 映像由 cloud-init（多數 Linux 雲端映像預先裝好、開機時讀中繼資料做初始設定的程式）在主機第一次開機時讀出公鑰、寫進預設使用者的 <code>authorized_keys</code>；Google Cloud 的映像由業者的 guest agent 持續依中繼資料同步金鑰，不限第一次開機。</p>
<p>Oracle Cloud 的建立頁提供四個選項：</p>
<ul>
<li><strong>讓業者產生一對金鑰（Generate a key pair for me）</strong>：業者產生一對 RSA 金鑰，公鑰放進主機，私鑰讓使用者在這一頁下載。私鑰只能在這一刻下載，離開頁面之後就拿不到；官方提醒持有私鑰的人都能登入這台主機，要存在安全的位置。</li>
<li><strong>上傳公鑰檔（Upload public key files）</strong>：上傳自己電腦上已經有的公鑰（<code>.pub</code> 檔）。私鑰從頭到尾只在自己的電腦上。</li>
<li><strong>貼上公鑰（Paste public keys）</strong>：同上，改成貼上公鑰的文字。誤貼私鑰時頁面會報錯。</li>
<li><strong>不放金鑰（No SSH keys）</strong>：主機裡沒有任何允許登入的公鑰，而且官方說明選了之後就不能再提供公鑰。SSH 登入只能經由 Bastion 的 managed SSH session（見〈不放金鑰時的登入服務〉）；Bastion 的 port forwarding session 要用主機 <code>authorized_keys</code> 裡事先放好的公鑰，選了 No SSH keys 的主機用不了它。所以 managed SSH session 不支援的主機（A1 機型加非 Oracle Linux，或可用的 plugin 裡沒有 Bastion 的作業系統版本，見〈不放金鑰時的登入服務〉）選了 No SSH keys，就沒有 SSH 登入的入口。其餘的入口是〈建立之後加金鑰與金鑰遺失時的入口〉列的救援入口。</li>
</ul>
<p>自己已經在用 SSH 的話，上傳或貼上自己的公鑰比較好：私鑰不經過任何網頁，也不必管理另一把專門給這台主機的金鑰。本機還沒有金鑰時，用 <code>ssh-keygen -t ed25519</code> 產生一對（ed25519 是新版 OpenSSH 的 <code>ssh-keygen</code> 預設產生的型別），公鑰在 <code>~/.ssh/id_ed25519.pub</code>；那個路徑已經有一把金鑰時，<code>ssh-keygen</code> 會問要不要覆寫，回答 n，或用 <code>-f &lt;新的檔名&gt;</code> 另存一把。</p>
<h2 id="預設使用者名稱與映像檔的對應">預設使用者名稱與映像檔的對應</h2>
<p>在 Oracle Cloud 與 AWS 上，公鑰寫進的是映像檔預先建立的那個使用者，名稱依作業系統而定。Oracle Cloud 的官方說明是 Ubuntu 平台映像的使用者名稱是 <code>ubuntu</code>，Oracle Linux 與相容 RHEL 的平台映像是 <code>opc</code>。登入指令因此是 <code>ssh ubuntu@&lt;主機的 IP&gt;</code> 或 <code>ssh opc@&lt;主機的 IP&gt;</code>，用錯名稱時 SSH 回的是 <code>Permission denied (publickey)</code>，看起來跟金鑰錯了一樣。Google Cloud 的使用者名稱取自中繼資料裡每一把公鑰附帶的名稱，guest agent 依那個名稱在主機上建使用者。</p>
<p>這個預設使用者有 <code>sudo</code> 權限，而且只用金鑰登入、沒有設密碼。多人共用同一台主機時，各自建自己的使用者、各自放自己的公鑰，誰做了什麼才分得出來；共用一個帳號時，登入紀錄只顯示那個帳號，對不到是哪一個人，有人離職也無法只撤銷他一個人的存取，見 <a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> 的〈主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回〉。</p>
<h2 id="建立之後加金鑰與金鑰遺失時的入口">建立之後加金鑰與金鑰遺失時的入口</h2>
<p>建立之後能不能再加金鑰，看業者用什麼把金鑰寫進主機。Oracle Cloud 的金鑰由 cloud-init 在第一次開機時寫進主機一次，之後要加金鑰，做法是先用現有的金鑰登入，再在主機裡把新的公鑰加進 <code>authorized_keys</code>。Google Cloud 的 guest agent 持續同步中繼資料，主機建好之後把公鑰加進主機或專案的中繼資料，guest agent 就會把它寫進主機。AWS 的 Systems Manager Session Manager 不經過 SSH，主機的 <code>authorized_keys</code> 裡沒有任何公鑰也能開工作階段，條件備齊時可以當成另一個入口。</p>
<p>所以在 Oracle Cloud 上，金鑰遺失或一開始沒放、又沒有開代理程式的 Bastion plugin（讓 Oracle Cloud 的 Bastion 服務在建立工作階段時，把一把公鑰臨時放進主機的功能，見〈不放金鑰時的登入服務〉）時，主機上沒有可以用 SSH 進去的入口。剩下的入口各有前提，而多數前提要在金鑰遺失之前就準備好：</p>
<ul>
<li><strong>序列主控台（serial console）</strong>：不經過網路與 sshd，在主控台上建一個主控台連線就能看到主機的開機畫面與登入提示。要在登入提示登進去，主機裡要事先有一個設了密碼的帳號，而平台映像的預設使用者只用金鑰、沒有密碼；沒有這種帳號時，只能照官方範例從開機選單進維護模式重設金鑰，那份範例只寫了 Oracle Linux 的 <code>opc</code>。救援帳號怎麼事先準備見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> 的〈設計責任〉；在 Oracle Cloud 的 Ubuntu 24.04 主機上建序列主控台連線、改寫成 ProxyJump 設定與連上之後看到的開機輸出，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉。</li>
<li><strong>Compute Instance Run Command</strong>：在主控台送一段腳本到主機裡執行。官方說明這個 plugin 以 <code>ocarun</code> 這個使用者執行，要寫另一個使用者家目錄裡的 <code>authorized_keys</code>，得事先在主機上替 <code>ocarun</code> 設好 sudo（官方列的做法是建立時用 cloud-init 設，或建好之後登入主機設；主機登不進去時只剩前一種）；2026-10-25 對一台沒設的 A1 Ubuntu 主機實測，<code>ocarun</code> 沒有 sudo，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈Run Command 補公鑰的嘗試：dynamic group、IAM 政策與 ocarun 沒有 sudo 的結果〉；送指令的人也要有對應的權限政策，主機上的代理程式要連得到 Oracle 的服務端點（主機在私有 subnet 時經由服務閘道或 NAT 閘道；官方只對「腳本或輸出放在 Object Storage」這種用法寫明出站條件，列的是 NAT 閘道、服務閘道或 internet gateway 其中一種）。</li>
<li><strong>把開機磁碟掛到另一台主機</strong>：停止這台主機、把它的開機磁碟卸下，當成資料磁碟掛到另一台主機上，直接修改裡面的 <code>authorized_keys</code>，再掛回去開機。這一條不需要事先準備，代價是一段停機。</li>
<li><strong>重建</strong>：終止這台主機，帶著新的公鑰重建。官方對金鑰遺失列的處置就是重建與序列主控台這兩種。</li>
</ul>
<h2 id="不放金鑰時的登入服務">不放金鑰時的登入服務</h2>
<p>選「不放金鑰」是一種刻意的設計：主機裡不放固定的公鑰，登入時由業者的服務決定這一次可以用哪一把金鑰。管理員登入一台主機要過三關，<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> 把它們叫做網路可達性（連線怎麼到得了主機）、通道授權（誰可以開這條連線）與主機登入驗證（主機是否接受登入者出示的金鑰）。業者的服務各自包辦其中幾關，所以三家對應的服務包辦的範圍不同：</p>
<ul>
<li>
<p><strong>Oracle Cloud 的 Bastion</strong>：Bastion 是業者託管的服務，不是自己架的 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">跳板機（bastion host）</a>。官方〈Bastion〉概觀頁的說明是 Bastion 位在公開 subnet（原文 reside in a public subnet），替使用者建好連到私有 subnet 裡目標資源所需的網路設施，所以經由它登入的主機不需要公開 IP；同一頁寫明 Bastion 不收費，但有服務配額（service limits）。誰能建工作階段由 Oracle Cloud 的權限政策決定。登入的流程是在主控台 Identity &amp; Security → Bastion 建一個 Bastion，選一個目標 subnet、設定允許連入的來源位址範圍，再建一個工作階段（session），主控台會給出一條經由 Bastion 轉接的 <code>ssh</code> 指令。官方建立 Bastion 的說明頁寫明，目標 subnet 要是主機所在的 subnet，或主機所在的 subnet 接受其流量的另一個 subnet。概觀頁列的工作階段有三種，每一種都只在建立時設定的存續時間內能用來連線：</p>
<ul>
<li><strong>managed SSH session</strong>：主機上代理程式的 Bastion plugin 把建立工作階段時指定的公鑰臨時放進主機。網路可達性、通道授權與主機登入驗證都由 Bastion 包辦，所以主機可以不放任何金鑰。官方〈Known Issues for Bastion〉寫明，用 Arm 架構的 Ampere A1 機型建立、作業系統不是 Oracle Linux（例如 Ubuntu）的主機不支援這種工作階段，要改用下一項的 port forwarding session；Always Free 的 A1 主機選 Ubuntu 時就落在這個組合，實測的經過見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈建立之後：Bastion managed SSH session 逾時的排查〉。Known Issues 只寫到作業系統，沒有寫版本，而 Bastion plugin 是否提供依作業系統的版本而定：同一份實測紀錄在 2026-10-25 用 <code>oci instance-agent available-plugins get</code> 查到 Oracle Linux 10 可用的 plugin 裡確定沒有 Bastion（9 與 8 的回應跟查不到時相同，這支工具分不出），查法與改用 Oracle Linux 並放公鑰重建的經過在〈重建：Oracle Linux 映像加建立時放公鑰，經由 port forwarding session 登入〉。</li>
<li><strong>SSH port forwarding session</strong>：只把連線轉到一台主機的 22 port，不經過代理程式，只包辦網路可達性與通道授權；主機登入驗證仍然是主機自己的 sshd 與 <code>authorized_keys</code> 裡事先放好的公鑰。建立工作階段、在本機開轉送與登入的指令，見同一份實測紀錄的〈重建〉那一節。</li>
<li><strong>dynamic port forwarding（SOCKS5）session</strong>：跟 SSH port forwarding session 一樣不經過代理程式，差別是轉接的目標不固定，可以動態連到私有 subnet 裡的任何資源；只登入一台主機時，用 managed SSH session 或 SSH port forwarding session 就夠。</li>
</ul>
</li>
<li>
<p><strong>Google Cloud 的 IAP 加 OS Login</strong>：兩個服務各管一部分。IAP TCP forwarding 把連線轉進沒有外部 IP 的主機，誰能開這條通道由 IAM 決定，包辦網路可達性與通道授權；OS Login 在登入當下由 Google 查這個 Google 帳號登記的公鑰與 IAM 角色，決定能不能登入、以主機上的哪個 Linux 帳號登入，只包辦主機登入驗證。只開 OS Login、不用 IAP 時，連線是一般的 SSH：從 VPC 外面登入的話，主機要有外部 IP，防火牆規則要放行登入者的位址連到 22 port。兩者的機制與 OS Login 的啟用條件見 <a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道</a> 與 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 卡，啟用 OS Login 的指令見 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a> 的〈啟用 OS Login：先授角色、單台先行、再做專案層級〉。</p>
</li>
<li>
<p><strong>AWS 的 Systems Manager Session Manager</strong>：經由主機上的 SSM Agent 開工作階段，不經過 SSH port，網路可達性、通道授權與主機登入驗證都由 Session Manager 包辦。<strong>EC2 Instance Connect</strong> 則只包辦主機登入驗證：它把一把公鑰推送到主機的中繼資料，官方說明這把公鑰在中繼資料裡保留 60 秒；連線本身仍然是一般的 SSH，主機的 security group 要放行 22 port。</p>
</li>
</ul>
<p>這幾種服務需要的條件，依各個服務包辦的是哪幾關而不同：</p>
<ul>
<li>
<p><strong>主機上的代理程式</strong>（agent）：Oracle Cloud 的 managed SSH session 要代理程式的 Bastion plugin，在建立頁或主機頁面的 Oracle Cloud Agent 設定裡打開。AWS 的 Session Manager 要主機上有 SSM Agent 在執行，多數 AMI 預先裝了它。兩家的代理程式見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈代理程式與 plugin：業者裝在主機裡的程式〉。Oracle Cloud 的 port forwarding session 與 Google Cloud 的 IAP 不需要代理程式，OS Login 由映像預先裝好的 guest environment 處理。</p>
</li>
<li>
<p><strong>主機自己的權限</strong>：AWS 的 Systems Manager 要管理一台主機有兩個條件：主機上有 SSM Agent 在執行，以及 Systems Manager 有操作這台主機的權限。Systems Manager 預設沒有這個權限，給法有兩種，擇一即可：替主機掛一個 instance profile（主機用的 IAM 角色），或在 Systems Manager 的 Fleet Manager 打開 Default Host Management Configuration。Default Host Management Configuration 以帳號加區域為範圍，替那一區的每一台主機套用一個預設的 IAM 角色；它對一台主機生效的條件是那台主機用 IMDSv2、SSM Agent 在 3.2.582.0 以上。</p>
</li>
<li>
<p><strong>登入者的權限</strong>：登入的人在雲端帳號裡要有建立工作階段、連到這台主機的權限，由管理員在身分與權限的政策裡授予。</p>
</li>
<li>
<p><strong>連線的路徑</strong>：Oracle Cloud 的 managed SSH session 與 AWS 的 Session Manager，是主機上的代理程式主動往外連到業者的服務端點；主機在私有 subnet 時，這條往外的路經由業者的私有連線或 NAT 閘道，私有連線與 NAT 閘道有一種就夠；主機在公開 subnet 而且有公開 IP 時，經由 internet gateway。私有連線在 Oracle Cloud 是服務閘道；在 AWS 是 interface VPC endpoint，官方〈Improve the security of EC2 instances by using VPC endpoints for Systems Manager〉列出要建的有 <code>ssm</code>、<code>ssmmessages</code>、<code>ec2messages</code> 三個，這三個 endpoint 都要建（同一頁註明 SSM Agent 3.3.40.0 起，有 <code>ssmmessages</code> 可用時改走它而不走 <code>ec2messages</code>；SSM Agent 自己更新時另外要 <code>s3</code> 的 endpoint）。</p>
<p>Oracle Cloud 對 managed SSH session 的閘道要求，官方兩頁的寫法不同：〈Bastion〉概觀頁寫的是 VCN 要有服務閘道、internet gateway、NAT 閘道三者之中的一個，以及通往那個閘道的路由規則；建立 Bastion 的說明頁只寫 VCN 要有服務閘道與通往它的路由規則。VCN 有服務閘道與對應的路由規則時，兩頁的要求都滿足。</p>
<p>Oracle Cloud 的 port forwarding session 與 Google Cloud 的 IAP 是服務往裡連到主機的 22 port，主機的防火牆規則要放行來自服務那一端的連線。Oracle Cloud 那一端是建立 Bastion 時選的目標 subnet，主機的 security list 要放行來自那個 subnet 的 22 port；Google Cloud 那一端是官方 IAP TCP forwarding 說明列的 IAP 來源位址範圍 <code>35.235.240.0/20</code>。</p>
</li>
</ul>
<p>上面這幾項條件少了任一項時，主機照樣建得起來，而登入會失敗；主機上又沒有放公鑰，能進去查原因的只剩〈建立之後加金鑰與金鑰遺失時的入口〉那一節列的序列主控台、Run Command、把開機磁碟掛到另一台主機修改，以及重建。在 Oracle Cloud 上第一次建主機、還不熟這些服務時，先放自己的公鑰比較保險：代理程式或 plugin 出問題、managed SSH session 建不起來時，port forwarding session 不經過代理程式，用自己的公鑰仍然登得進去。這條退路有兩個前提：主機讀得到中繼資料（cloud-init 要從那裡讀出公鑰寫進 <code>authorized_keys</code>，中繼資料的設定見 <a href="/blog/devops/infra/cloud-providers/vm-options/management/" data-link-title="代理程式、中繼資料服務、初始化腳本、維護行為與標籤" data-link-desc="雲端主機建立頁管理區塊的選項：業者裝在主機裡的代理程式與它的 plugin 怎麼決定開關、中繼資料服務的第二版存取驗證、開機時執行的初始化腳本、底層硬體維護時主機會被遷移還是重開，以及標籤">代理程式、中繼資料服務、初始化腳本、維護行為與標籤</a> 的〈中繼資料服務與 Authorization header〉），以及主機的 security list 放行來自 Bastion 目標 subnet 的 22 port。managed SSH session 走通之後，這把公鑰拿掉與否取決於還有沒有另一條不經過代理程式的入口：拿掉之後只剩代理程式那一條；先建好序列主控台的救援帳號並登入過，拿掉公鑰時才仍然有兩條入口，判斷依據見 <a href="/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/" data-link-title="建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨" data-link-desc="在建立頁按下建立之前，把位置、機型與映像檔、網路、登入、安全開機各篇的選項串成幾項檢查：哪些設定建立之後只能重建、怎麼查，登入用的業者服務各自支援哪些機型與作業系統、元件狀態正常為什麼不代表支援，兩條登入路徑各自依賴哪些元件、哪些仍然共用，以及機型容量難取得時設定怎麼選；Oracle Cloud、Google Cloud 與 AWS 各自查這些事的位置">建立主機之前的檢查</a> 的〈兩條登入路徑：各自依賴的元件與仍然共用的部分〉。這個做法只適用於 Oracle Cloud：Google Cloud 開了 OS Login 之後，guest agent 會忽略中繼資料裡的 SSH 公鑰。</p>
<h2 id="各家的欄位名稱金鑰欄位預設使用者與不放金鑰時的登入服務">各家的欄位名稱：金鑰欄位、預設使用者與不放金鑰時的登入服務</h2>
<table>
  <thead>
      <tr>
          <th>概念</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>建立時放公鑰</td>
          <td>Add SSH keys</td>
          <td>SSH keys（metadata <code>ssh-keys</code>）</td>
          <td>Key pair</td>
      </tr>
      <tr>
          <td>Ubuntu 預設使用者</td>
          <td><code>ubuntu</code></td>
          <td>依 metadata 裡公鑰附帶的使用者名稱</td>
          <td><code>ubuntu</code></td>
      </tr>
      <tr>
          <td>不放金鑰時的登入服務</td>
          <td>Bastion：登入單一主機用 managed SSH session 或 SSH port forwarding session，另有 SOCKS5 session</td>
          <td>IAP TCP forwarding（連線）加 OS Login（登入驗證）</td>
          <td>Systems Manager Session Manager；EC2 Instance Connect（只推送金鑰）</td>
      </tr>
  </tbody>
</table>
<p>金鑰對了之後，連線還要通過的網路與防火牆見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a>，SSH 連線逾時與連線被拒兩種症狀各自指向哪一層見 <a href="/blog/linux/dotfile/knowledge-cards/connection-refused-vs-timeout/" data-link-title="Connection Refused vs. Timeout（連線被拒與逾時）" data-link-desc="遠端連不上、要判斷是封包到不了還是服務拒絕、決定往網路層還是服務層除錯時回來讀">Connection Refused vs. Timeout（連線被拒與逾時）</a>；選 No SSH keys、改走 Bastion 時建立頁上要一起改的設定，見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的 SSH keys 與 Management 段。</p>
]]></content:encoded></item><item><title>伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口</title><link>https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/</guid><description>&lt;p>這篇整理管理員登入伺服器作業系統的通道怎麼設計，內容與雲端廠商無關，適用於雲端 VM、地端主機與 VPS。適用的前提是服務跑在自己管理的主機上、而且有人要登入那台主機的作業系統；跑在不提供主機登入的平台（PaaS、serverless）上，就沒有這條通道要設計。機制以 SSH 說明，RDP 的差異在〈對外開放管理 port 的實際風險〉與〈主機登入驗證的做法〉另外交代。GCP 上的機制見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding&lt;/a>，逐步的指令見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a>。&lt;/p>
&lt;h2 id="管理通道的三層網路可達性通道授權主機登入驗證">管理通道的三層：網路可達性、通道授權、主機登入驗證&lt;/h2>
&lt;p>管理通道指管理員從自己的電腦登入伺服器作業系統的那條路徑。一次 SSH 登入依序經過三層檢查，每一層的做法可以分開選，但有些做法同時落在兩層：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>網路可達性&lt;/strong>：管理員電腦送出的封包是否到達得了伺服器上 sshd（SSH 的伺服器端程式）監聽的 port。防火牆規則、伺服器是否持有公開 IP、中間是否經過代理，決定的都是這一層。&lt;/li>
&lt;li>&lt;strong>通道授權&lt;/strong>：在封包抵達 sshd 之前，是否有一個元件先確認「這個人可以使用這條通道」，以及它憑什麼確認——來源 IP、VPN 憑證、跳板機上的 SSH 金鑰，或一個身分提供者（&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">identity provider&lt;/a>，IdP）上的帳號。&lt;/li>
&lt;li>&lt;strong>主機登入驗證&lt;/strong>：封包抵達 sshd 之後，sshd 用什麼判斷這個連線可以登入哪個帳號——&lt;code>authorized_keys&lt;/code> 裡的靜態公鑰、由中央服務動態提供的公鑰，或 SSH CA 簽發的短期 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH 憑證（SSH certificate）&lt;/a>。&lt;/li>
&lt;/ul>
&lt;p>防火牆對 &lt;code>0.0.0.0/0&lt;/code>（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR&lt;/a> 表示法裡的「所有 IPv4 位址」）開 22 port 的狀態，在這個模型裡是網路可達性全開、通道授權不存在、只剩主機登入驗證一層在擋。收斂管理通道的做法，多數是替網路可達性與通道授權換上更嚴格的做法；主機登入驗證另有它自己的換法，從要逐台刪除的靜態金鑰，換成跟著帳號收回、或會自動到期的金鑰。&lt;/p>
&lt;p>每一層擋下的攻擊者，是手上的存取憑證與所在位置過得了前面幾層、過不了這一層的人。網路可達性擋的是只能從網際網路送封包、還沒有任何存取憑證的攻擊者，例如掃描程式與 sshd 漏洞的利用程式；通道授權擋的是已經能把封包送到通道入口、卻沒有通道身分的人，例如私鑰從筆電外洩、但沒有組織 IdP 帳號的人；主機登入驗證擋的是持有通道權限、卻不該登入某台主機或某個帳號的成員，例如開發者登入他不負責的資料庫主機、或取得他不該有的 sudo。同一個做法可能同時改變兩層（來源 IP 白名單、跳板機、VPN、身分感知代理都兼任通道授權），還有少數做法把三層一起換掉（SSM Session Manager 的預設模式、Tailscale SSH，見〈網路可達性的做法〉），所以評估一個做法時要逐層問它改了哪一層。只加強其中一層時，有能力通過那一層的攻擊者（例如持有 IdP 帳號、卻不該碰這台主機的成員）遇到的另外兩層仍是原本的設定；各層要彼此獨立，這個原則叫&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/defense-in-depth/" data-link-title="Defense in Depth" data-link-desc="有人主張某個弱環節「還有其他防線擋著」時，用來判斷這個說法成不成立">縱深防禦（defense in depth）&lt;/a>。&lt;/p>
&lt;h2 id="對外開放管理-port-的實際風險">對外開放管理 port 的實際風險&lt;/h2>
&lt;p>管理 port 對整個網際網路開放之後，主機的認證紀錄很快就會出現來自陌生 IP 的失敗登入，開一台測試主機查一次紀錄就看得到。紀錄的位置依發行版不同：Debian 與 Ubuntu 是 &lt;code>journalctl -u ssh&lt;/code> 或 &lt;code>/var/log/auth.log&lt;/code>，RHEL 系是 &lt;code>journalctl -u sshd&lt;/code> 或 &lt;code>/var/log/secure&lt;/code>。這些嘗試要成功，取決於主機登入驗證那一層的設定；而只剩那一層在擋時，風險集中在四個地方：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>sshd 本身的漏洞&lt;/strong>：2024 年的 regreSSHion（CVE-2024-6387）讓未經認證的連線有機會在 OpenSSH 伺服器上執行程式碼。這類漏洞發生在認證之前，金鑰設定得再嚴格也擋不住；能降低這類風險的是修補速度（縮短暴露期）與收窄網路可達性（減少能把封包送到 sshd 的來源）。&lt;/li>
&lt;li>&lt;strong>密碼登入&lt;/strong>：多數雲端廠商的官方 Linux 映像檔預設關閉 SSH 密碼登入、只接受金鑰，單純猜密碼在沒改過設定的主機上不會成功。不少 VPS 商在開機時寄出 root 密碼、或提供「用密碼建立主機」的選項，這類主機的密碼登入一開始就是開著的，要自己關掉。有人為了方便打開 &lt;code>PasswordAuthentication yes&lt;/code>、或建了弱密碼的本機帳號，這項預設也會失效，而這類改動發生在主機內部，防火牆看不到。&lt;/li>
&lt;li>&lt;strong>金鑰的生命週期&lt;/strong>：私鑰從筆電、CI 或共用資料夾外洩之後，持有者從任何地方都能登入；成員離職時沒從 &lt;code>authorized_keys&lt;/code> 移除的公鑰持續有效。組織處理離職時停用的是公司帳號（郵件、IdP），這個動作不會碰到任何一台主機上的 &lt;code>authorized_keys&lt;/code>；主機的登入紀錄記的是金鑰指紋而不是人名，那把公鑰之後被拿來登入，紀錄上也對不到是誰。靜態公鑰沒有到期時間，所以「誰還拿得到這台主機」只能靠人工盤點查出來，離職流程要把主機登入層列進去，撤銷清單的寫法見&lt;a href="https://tarrragon.github.io/blog/devops/infra/08-governance-habits/handover-design/" data-link-title="職務交接與存取撤銷設計" data-link-desc="人員異動時的存取撤銷順序、credential rotation、最小交接清單，以及讓交接成本結構性降低的 infra 設計原則">職務交接與存取撤銷設計&lt;/a>的〈離職或轉調的存取撤銷清單〉。&lt;/li>
&lt;li>&lt;strong>RDP&lt;/strong>：Windows 的遠端桌面預設接受帳號密碼，暴力猜測對它的成功率比對只收金鑰的 SSH 高，所以 3389 對外開放的被入侵機率高於 22。RDP 在主機登入驗證那一層的對應是 Windows 帳號，其餘兩層的做法與 SSH 相同。&lt;/li>
&lt;/ul>
&lt;p>同一個內部網段裡的主機之間如果所有 port 互通，一台主機被攻破後，攻擊者從那台機器可以直接連到其他主機的資料庫 port 與管理 port，這種擴散方式叫&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">橫向移動（lateral movement）&lt;/a>。收斂對外的管理 port 時，內部網段之間的規則要一起盤點，最小開放的設計見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/vpc-subnet-security-group/" data-link-title="網路地基 — VPC、subnet 分層與 security group 設計" data-link-desc="VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計，以及 NACL 的定位">網路地基 — VPC、subnet 分層與 security group 設計&lt;/a>的〈security group 設計：最小開放〉一節。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理管理員登入伺服器作業系統的通道怎麼設計，內容與雲端廠商無關，適用於雲端 VM、地端主機與 VPS。適用的前提是服務跑在自己管理的主機上、而且有人要登入那台主機的作業系統；跑在不提供主機登入的平台（PaaS、serverless）上，就沒有這條通道要設計。機制以 SSH 說明，RDP 的差異在〈對外開放管理 port 的實際風險〉與〈主機登入驗證的做法〉另外交代。GCP 上的機制見 <a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding</a>，逐步的指令見 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a>。</p>
<h2 id="管理通道的三層網路可達性通道授權主機登入驗證">管理通道的三層：網路可達性、通道授權、主機登入驗證</h2>
<p>管理通道指管理員從自己的電腦登入伺服器作業系統的那條路徑。一次 SSH 登入依序經過三層檢查，每一層的做法可以分開選，但有些做法同時落在兩層：</p>
<ul>
<li><strong>網路可達性</strong>：管理員電腦送出的封包是否到達得了伺服器上 sshd（SSH 的伺服器端程式）監聽的 port。防火牆規則、伺服器是否持有公開 IP、中間是否經過代理，決定的都是這一層。</li>
<li><strong>通道授權</strong>：在封包抵達 sshd 之前，是否有一個元件先確認「這個人可以使用這條通道」，以及它憑什麼確認——來源 IP、VPN 憑證、跳板機上的 SSH 金鑰，或一個身分提供者（<a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">identity provider</a>，IdP）上的帳號。</li>
<li><strong>主機登入驗證</strong>：封包抵達 sshd 之後，sshd 用什麼判斷這個連線可以登入哪個帳號——<code>authorized_keys</code> 裡的靜態公鑰、由中央服務動態提供的公鑰，或 SSH CA 簽發的短期 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH 憑證（SSH certificate）</a>。</li>
</ul>
<p>防火牆對 <code>0.0.0.0/0</code>（<a href="/blog/devops/infra/knowledge-cards/cidr/" data-link-title="CIDR（Classless Inter-Domain Routing）" data-link-desc="用前綴長度表示 IP 地址範圍的表示法，決定 VPC 與 subnet 的地址空間大小">CIDR</a> 表示法裡的「所有 IPv4 位址」）開 22 port 的狀態，在這個模型裡是網路可達性全開、通道授權不存在、只剩主機登入驗證一層在擋。收斂管理通道的做法，多數是替網路可達性與通道授權換上更嚴格的做法；主機登入驗證另有它自己的換法，從要逐台刪除的靜態金鑰，換成跟著帳號收回、或會自動到期的金鑰。</p>
<p>每一層擋下的攻擊者，是手上的存取憑證與所在位置過得了前面幾層、過不了這一層的人。網路可達性擋的是只能從網際網路送封包、還沒有任何存取憑證的攻擊者，例如掃描程式與 sshd 漏洞的利用程式；通道授權擋的是已經能把封包送到通道入口、卻沒有通道身分的人，例如私鑰從筆電外洩、但沒有組織 IdP 帳號的人；主機登入驗證擋的是持有通道權限、卻不該登入某台主機或某個帳號的成員，例如開發者登入他不負責的資料庫主機、或取得他不該有的 sudo。同一個做法可能同時改變兩層（來源 IP 白名單、跳板機、VPN、身分感知代理都兼任通道授權），還有少數做法把三層一起換掉（SSM Session Manager 的預設模式、Tailscale SSH，見〈網路可達性的做法〉），所以評估一個做法時要逐層問它改了哪一層。只加強其中一層時，有能力通過那一層的攻擊者（例如持有 IdP 帳號、卻不該碰這台主機的成員）遇到的另外兩層仍是原本的設定；各層要彼此獨立，這個原則叫<a href="/blog/backend/knowledge-cards/defense-in-depth/" data-link-title="Defense in Depth" data-link-desc="有人主張某個弱環節「還有其他防線擋著」時，用來判斷這個說法成不成立">縱深防禦（defense in depth）</a>。</p>
<h2 id="對外開放管理-port-的實際風險">對外開放管理 port 的實際風險</h2>
<p>管理 port 對整個網際網路開放之後，主機的認證紀錄很快就會出現來自陌生 IP 的失敗登入，開一台測試主機查一次紀錄就看得到。紀錄的位置依發行版不同：Debian 與 Ubuntu 是 <code>journalctl -u ssh</code> 或 <code>/var/log/auth.log</code>，RHEL 系是 <code>journalctl -u sshd</code> 或 <code>/var/log/secure</code>。這些嘗試要成功，取決於主機登入驗證那一層的設定；而只剩那一層在擋時，風險集中在四個地方：</p>
<ul>
<li><strong>sshd 本身的漏洞</strong>：2024 年的 regreSSHion（CVE-2024-6387）讓未經認證的連線有機會在 OpenSSH 伺服器上執行程式碼。這類漏洞發生在認證之前，金鑰設定得再嚴格也擋不住；能降低這類風險的是修補速度（縮短暴露期）與收窄網路可達性（減少能把封包送到 sshd 的來源）。</li>
<li><strong>密碼登入</strong>：多數雲端廠商的官方 Linux 映像檔預設關閉 SSH 密碼登入、只接受金鑰，單純猜密碼在沒改過設定的主機上不會成功。不少 VPS 商在開機時寄出 root 密碼、或提供「用密碼建立主機」的選項，這類主機的密碼登入一開始就是開著的，要自己關掉。有人為了方便打開 <code>PasswordAuthentication yes</code>、或建了弱密碼的本機帳號，這項預設也會失效，而這類改動發生在主機內部，防火牆看不到。</li>
<li><strong>金鑰的生命週期</strong>：私鑰從筆電、CI 或共用資料夾外洩之後，持有者從任何地方都能登入；成員離職時沒從 <code>authorized_keys</code> 移除的公鑰持續有效。組織處理離職時停用的是公司帳號（郵件、IdP），這個動作不會碰到任何一台主機上的 <code>authorized_keys</code>；主機的登入紀錄記的是金鑰指紋而不是人名，那把公鑰之後被拿來登入，紀錄上也對不到是誰。靜態公鑰沒有到期時間，所以「誰還拿得到這台主機」只能靠人工盤點查出來，離職流程要把主機登入層列進去，撤銷清單的寫法見<a href="/blog/devops/infra/08-governance-habits/handover-design/" data-link-title="職務交接與存取撤銷設計" data-link-desc="人員異動時的存取撤銷順序、credential rotation、最小交接清單，以及讓交接成本結構性降低的 infra 設計原則">職務交接與存取撤銷設計</a>的〈離職或轉調的存取撤銷清單〉。</li>
<li><strong>RDP</strong>：Windows 的遠端桌面預設接受帳號密碼，暴力猜測對它的成功率比對只收金鑰的 SSH 高，所以 3389 對外開放的被入侵機率高於 22。RDP 在主機登入驗證那一層的對應是 Windows 帳號，其餘兩層的做法與 SSH 相同。</li>
</ul>
<p>同一個內部網段裡的主機之間如果所有 port 互通，一台主機被攻破後，攻擊者從那台機器可以直接連到其他主機的資料庫 port 與管理 port，這種擴散方式叫<a href="/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">橫向移動（lateral movement）</a>。收斂對外的管理 port 時，內部網段之間的規則要一起盤點，最小開放的設計見<a href="/blog/devops/infra/03-network-foundation/vpc-subnet-security-group/" data-link-title="網路地基 — VPC、subnet 分層與 security group 設計" data-link-desc="VPC CIDR 規劃、public / private subnet 切分、route table 與 NAT 的可用性成本取捨、security group 最小開放設計，以及 NACL 的定位">網路地基 — VPC、subnet 分層與 security group 設計</a>的〈security group 設計：最小開放〉一節。</p>
<h3 id="更換-ssh-port-與-fail2ban-改變的範圍">更換 SSH port 與 fail2ban 改變的範圍</h3>
<p>更換 SSH port 與安裝 fail2ban 常被當成上面這些風險的對策，而它們都不改變「sshd 對全網可達、只靠主機登入驗證在擋」的狀態。更換 port 之後，sshd 仍然對整個網際網路可達，掃描程式找的是開著的 port，不是 22 這個數字。fail2ban 在同一個來源失敗多次之後才封鎖它，所以每個來源的第一次嘗試仍然送得到 sshd；它擋得住重複猜測，擋不住第一次就成功的攻擊（sshd 漏洞的利用、外洩的私鑰）。兩者減少的是認證紀錄裡的雜訊。</p>
<h2 id="網路可達性的做法管理-port-對誰開放">網路可達性的做法：管理 port 對誰開放</h2>
<p>伺服器是否仍需接受入站連線，把網路可達性的做法分成兩類；這一層的目標是讓 sshd 只對經過通道授權的來源可見。</p>
<p><strong>仍然接受入站、但縮小來源</strong>的做法有四種：</p>
<ul>
<li><strong>來源 IP 白名單</strong>：防火牆只放行公司對外的固定 IP。設定成本最低；代價是遠端工作與 IP 變動時要一直改規則，而且公司網路裡任何一台被入侵的電腦都在白名單內。它同時兼任通道授權，判斷身分的依據是 IP。</li>
<li><strong>跳板機（<a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">bastion host</a>，其他中文資料常寫作「堡壘主機」）</strong>：一台放在公開網段、只用來轉接 SSH 的主機，其他主機只接受來自它的連線。暴露面從每台主機收斂成一台，但那一台仍然對外開著管理 port，要修補、要監控，它的 sshd 漏洞就是整個環境的入口。它同時兼任通道授權，判斷依據是跳板機上的 SSH 金鑰，或跳板機信任的 SSH CA 所簽的憑證。好處是與廠商無關、可以集中錄製操作紀錄。主機只有一台時，跳板機本身就多出一台對外開著 22 port 的主機，暴露面沒有縮小。</li>
<li><strong>從代理位址段連入的身分感知代理（<a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">identity-aware proxy</a>）</strong>：由廠商提供或自己架設的代理服務在網際網路上接收連線，向 IdP 驗過身分之後，才從代理服務自己的固定位址段連到主機的內部 IP。防火牆只放行那段代理位址，主機本身不需要公開 IP。它同時承擔通道授權。GCP 的 IAP TCP forwarding、AWS 的 EC2 Instance Connect Endpoint 屬於這一類；它是<a href="/blog/backend/knowledge-cards/zero-trust/" data-link-title="Zero Trust（零信任）" data-link-desc="判斷內網或 VPN 裡的服務能不能因為連線來自內部就省掉認證、或要評估一條連線該放行到什麼範圍時，查「每一次連線各自通過身分與授權」這個原則的內容與導入代價">零信任（zero trust）</a>原則在存取入口上的一種實作。身分感知代理也可以改由主機上的代理程式反向連線送進主機，那種送達方式歸在下面的「主機不接受任何入站連線」。</li>
<li><strong>VPN</strong>：讓管理員的電腦加入內部網路，再用內部 IP 連主機，主機接受的入站只來自 VPN 範圍內的位址。它解決的範圍比管理通道大（資料庫用戶端、內部管理介面都能用），而連上之後通常整個網段都可達，要另外用防火牆規則限縮。VPN 閘道的形態決定它本身的暴露面：SSL VPN 與 IPsec 設備在網際網路上監聽、在認證之前就處理封包，2023 到 2024 年被大規模利用的認證前漏洞集中在這類設備上，它的修補與 sshd 同等重要；WireGuard 不回應沒有通過金鑰驗證的封包，掃描程式看不到它在監聽。Tailscale 這類 mesh VPN 讓每台裝置彼此直連、組成一個私有網路（Tailscale 稱為 tailnet），sshd 只監聽 tailnet 介面的位址，裝置透過 NAT traversal 建立連線、不必對外開入站 port；tailnet 裡的裝置預設彼此互通，收窄要靠 Tailscale 的 ACL。mesh VPN 的登入多半接組織的 IdP，所以它的通道授權依據是 IdP 帳號。Tailscale 另有 Tailscale SSH 模式，由 Tailscale 依 tailnet 的 ACL 決定能登入哪個帳號，取代 sshd 的金鑰驗證，主機登入驗證那一層跟著換掉。Tailscale 依賴它營運的協調伺服器，不想依賴第三方時改用自建的 WireGuard，或接得上 OIDC IdP 的自建協調伺服器（Headscale、NetBird）。連上之後的可達範圍怎麼收窄、哪些限制要放在被入侵的一端改不到的位置，見<a href="/blog/backend/07-security-data-protection/cross-network-connection-security/" data-link-title="7.43 兩個網路之間的連線安全：可達範圍、連線方向與流入的資料、被入侵端改不到的限制、應用層認證、監控主機的位置、金鑰與控制平面、異地備份的寫入憑證與連線紀錄" data-link-desc="地端主機與雲端主機、或兩個雲端業者的主機以 VPN 或存取金鑰接起來之後的安全設計與評估：連線清單上各台連得到什麼、每一台各假設一次被入侵、限制要放在被入侵端沒有寫入路徑的地方、連線的發起方向與流入的資料、VPN 之內的應用層認證、監控主機與管理者裝置的位置、WireGuard 與 Tailscale 的金鑰與控制平面、異地備份的寫入憑證，以及連線本身的紀錄">兩個網路之間的連線安全</a>的〈VPN 提供的保證與邊界模型〉；Tailscale 與自建 WireGuard 的取捨見 <a href="/blog/linux/tools/remote/tailscale-tailnet-and-relay/" data-link-title="Tailscale 深入：tailnet、MagicDNS、直連與 DERP 中繼" data-link-desc="要用 Tailscale 讓散在不同網路的機器互連、遇到連得到但延遲高、或想讀懂 tailscale status 的 relay / direct 標示時回來讀">Tailscale 深入：tailnet、MagicDNS、直連與 DERP 中繼</a>的〈取捨與邊界〉。</li>
</ul>
<p><strong>主機不接受任何入站連線</strong>的做法是代理程式（agent）反向連線，一種 <a href="/blog/backend/knowledge-cards/outbound-tunnel/" data-link-title="Outbound Tunnel" data-link-desc="反向隧道把出站連線轉成可達入口、與傳統 port-forward 的責任倒轉">Outbound Tunnel</a>：主機上常駐一個代理程式，由它主動往外連到管理服務並維持一條連線，管理員的工作階段經由那條連線送進來，防火牆不必為管理用途開任何入站 port。AWS 的 SSM Session Manager 是這一類的代表；Cloudflare Access 搭配主機上的 <code>cloudflared</code>、Teleport 的節點以反向通道連到 proxy，在網路可達性上也屬於這一類，同時它們也承擔通道授權。Tailscale 的連線方向與 outbound tunnel 相同，但 sshd 仍然接受 tailnet 介面的入站，本篇把它歸在 VPN。這一類的前提是代理程式持續在主機上執行，而且主機有連到管理服務端點的出站路徑。</p>
<p>代理程式反向連線的工作階段不一定經過 sshd。SSM Session Manager 預設由代理程式直接在主機上開 shell，決定能登入成哪個系統帳號、是否能用 sudo 的是 AWS 端的權限設定與代理程式的設定，主機登入驗證那一層因此跟著換掉；只有改用經由 Session Manager 轉送 SSH 的模式時，sshd 與金鑰才仍然在路徑上。Teleport 的節點用它自己內建的 SSH 伺服器與 Teleport 簽發的憑證，同樣不經過系統的 sshd。採用前要查清楚該服務的工作階段是直接開 shell、走自己的 SSH 伺服器，還是轉送到 sshd，再決定主機登入驗證那一層要設計什麼。</p>
<h2 id="通道授權的做法判斷連線使用者身分的依據">通道授權的做法：判斷連線使用者身分的依據</h2>
<p>比較通道授權的判斷依據時看四件事：分不分辨得出個人、判斷依據能不能被複製給別人、收回之後多久生效、留不留得下「是誰」的紀錄。判斷依據與網路可達性的做法不是一對一：VPN 可以用 PKI 簽發的憑證，也可以接 IdP 帳號（mesh VPN、多數商用 VPN 的 SAML 登入），接 IdP 時屬性看 IdP 帳號那一列。</p>
<table>
  <thead>
      <tr>
          <th>判斷依據</th>
          <th>分辨個人</th>
          <th>能否被複製</th>
          <th>收回生效</th>
          <th>「是誰」的紀錄</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>來源 IP</td>
          <td>分辨不出</td>
          <td>同一網路的任何人都通過</td>
          <td>改防火牆規則之後立即生效，但收回的是整個網路</td>
          <td>只有 IP，對不到人</td>
      </tr>
      <tr>
          <td>VPN 憑證</td>
          <td>每人一張時分辨得出</td>
          <td>憑證檔能複製，綁在硬體 token 上時不能</td>
          <td>PKI 型 VPN 靠伺服器維護的憑證撤銷清單；WireGuard 從伺服器設定刪掉那個人的公鑰</td>
          <td>VPN 伺服器的連線紀錄</td>
      </tr>
      <tr>
          <td>跳板機上的 SSH 金鑰</td>
          <td>每人一把時分辨得出</td>
          <td>私鑰檔能複製；存在硬體安全金鑰裡的 <code>-sk</code> 金鑰不能</td>
          <td>要到跳板機的 <code>authorized_keys</code> 刪除</td>
          <td>跳板機紀錄裡的金鑰指紋</td>
      </tr>
      <tr>
          <td>IdP 帳號</td>
          <td>分辨得出</td>
          <td>登入那一刻取決於多因素驗證；登入後留在本機的 token 能被複製，取決於代理的 session 長度與裝置綁定</td>
          <td>IdP 停用之後，代理自己簽發的 session 或憑證到期才失效；收回延遲的上限是代理端設定的 session 長度</td>
          <td>每一次開通道都記錄是哪個帳號</td>
      </tr>
  </tbody>
</table>
<p>跳板機與目的主機接受同一把金鑰時，一把私鑰外洩就同時通過通道授權與主機登入驗證，兩層退化成一層。SSH 金鑰那一列的硬體金鑰（OpenSSH 8.2 起支援 FIDO2 的 <code>ed25519-sk</code>）讓私鑰離不開那支實體金鑰，沒有 IdP 的組織用它就能補上「能否被複製」那一格。</p>
<p>IdP 帳號是 Google Workspace、Microsoft Entra ID、Okta 這類身分提供者上的帳號，通道服務把驗證交給它；GCP IAP 用的 Google 帳號就是一個實例。Cloudflare Access 可以接任何支援 OIDC 或 SAML（兩種把登入交給 IdP 的標準協定）的 IdP，Teleport 的 Enterprise 版也可以，Community 版的單一登入只接 GitHub。IdP 帳號在分辨個人與留下紀錄上比另外三種好，收回速度則由代理的 session 長度決定：IdP 停用一個帳號之後，代理已經簽給他的 session 或憑證（Cloudflare Access 的 session 最長可以設到一個月、Teleport 的使用者憑證有自己的有效期、<code>gcloud</code> 在本機保存 refresh token）要到期或被撤銷才失效，所以導入時要在代理端設定 session 長度，處理離職或帳號外洩時另外在代理端撤銷 session。選用 IdP 帳號之後，管理通道的風險轉移到那個帳號上，帳號被釣魚，攻擊者就能開通道。所以前提是 IdP 對可以開通道的帳號強制多因素驗證，至少管理員帳號用防釣魚的方式（硬體安全金鑰或 <a href="/blog/backend/knowledge-cards/passkey/" data-link-title="Passkey" data-link-desc="要用金鑰對取代密碼、或判斷已導入的 passkey 實際擋掉了什麼時，用來定位它的保護落在哪一段、上限由哪條路徑決定">passkey</a>）——一次性驗證碼會在釣魚頁面上被一起騙走。要再收緊時，多數 IdP 與代理服務支援裝置狀態（device posture）條件，例如只允許公司管理的裝置。授權範圍也要一起設計：限定在負責維運的群組、限定可以連的 port 與主機，原則見<a href="/blog/devops/infra/02-identity-credentials/iam-oidc-privilege-boundary/" data-link-title="身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計" data-link-desc="IAM 的 identity / policy / role 三元件、最小權限的持續收斂、用 OIDC 取代長期 access key，以及 SCP 與 Permissions Boundary 的環境隔離">身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計</a>的〈最小權限：持續收斂而非一次設定〉。</p>
<p>CI 或自動化工具要使用管理通道時，它的身分是<a href="/blog/devops/infra/knowledge-cards/service-account/" data-link-title="Service Account（服務帳號）" data-link-desc="程式、排程或雲端服務要以自己的身分呼叫雲端 API、要決定給它哪些角色、遇到「狀態正常卻呼叫失敗」的權限問題、或看到 IAM 綁定裡的 serviceAccount: 成員時，查服務帳號是什麼、它的權限要從「它能做什麼」與「誰能以它的身分行事」兩邊設，以及它和人的帳號在管理上的差別">服務帳號</a>或機器身分。長期有效的機器金鑰外洩，等同一把能進所有主機的鑰匙，所以這類身分用 OIDC 換取短期存取憑證，做法見<a href="/blog/devops/infra/02-identity-credentials/iam-oidc-privilege-boundary/" data-link-title="身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計" data-link-desc="IAM 的 identity / policy / role 三元件、最小權限的持續收斂、用 OIDC 取代長期 access key，以及 SCP 與 Permissions Boundary 的環境隔離">身分與存取憑證地基 — IAM 模型、OIDC 短期憑證與權限邊界設計</a>的〈OIDC：給 CI/CD 的短期憑證〉。</p>
<h2 id="主機登入驗證的做法ssh-金鑰怎麼發怎麼收回">主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回</h2>
<p>sshd 接受哪些金鑰由主機登入驗證決定，而設計的重點是金鑰的生命週期：誰發、多久失效、離職時怎麼收回。</p>
<ul>
<li><strong>靜態 <code>authorized_keys</code></strong>：公鑰手動或由<a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理</a>工具寫進主機。沒有到期時間，收回要逐台刪除，主機數量一多就難以盤點；金鑰認證的基本設定見 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a>。</li>
<li><strong>由中央服務決定可用的公鑰</strong>：主機在登入當下（或登入前由中央服務推送）取得這個帳號可用的公鑰，帳號或角色被移除，下一次登入就失敗。GCP 的 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 在登入當下向 Google 查詢，AWS 的 EC2 Instance Connect 在連線前推送只在短時間內有效的公鑰，兩者都綁定各自的雲端廠商。自建的版本是 sshd 的 <code>AuthorizedKeysCommand</code> 接到 LDAP 或 FreeIPA 這類目錄服務，與廠商無關。這一類都依賴中央服務在登入當下可用：目錄服務或雲端的 metadata server 中斷時，沒有快取的主機一起登不進去；FreeIPA 的用戶端 SSSD 會快取公鑰，目錄服務離線時仍能登入。</li>
<li><strong>SSH CA 簽發的短期憑證</strong>：主機只信任一把 CA 公鑰（sshd 的 <code>TrustedUserCAKeys</code>），管理員登入前向簽發服務取得一張有效期有限的使用者憑證。憑證過期自動失效，不需要逐台收回；有效期就是能接受的最晚收回延遲。管理員手動簽發時常設成一個工作日左右，讓每天開工時簽一次；簽發由用戶端在連線時自動完成時（Teleport 的 <code>tsh</code>、smallstep 的 <code>step ssh</code>），有效期可以縮到幾小時以內。私鑰外洩而等不到到期時，sshd 用 <code>RevokedKeys</code> 指向撤銷清單提前作廢，這份清單要派送到每一台主機。<a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a> 的 SSH secrets engine 與 Teleport 都以這種方式運作；OpenSSH 本身就支援，用 <code>ssh-keygen -s</code> 簽發，所以這一種做法同時與廠商無關、又不需要逐台收回；只用 OpenSSH 導入的完整流程見 <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a>。</li>
</ul>
<p>三種做法收回的都是下一次登入；SSH 憑證的「收回」是到期之後失效。已經建立的工作階段不受影響，處理離職或帳號外洩時，管理員要另外登入主機結束那個使用者的工作階段（systemd 的發行版用 <code>loginctl terminate-user &lt;username&gt;</code>），結束已建立工作階段的動作在應用程式的層次叫 <a href="/blog/backend/knowledge-cards/session-invalidation/" data-link-title="Session Invalidation" data-link-desc="說明事件後如何讓既有會話失效，避免被重放或延續利用">session invalidation</a>。</p>
<p>Windows 主機用 RDP 登入時，這一層是 Windows 帳號而不是 SSH 金鑰。本機帳號的收回與靜態 <code>authorized_keys</code> 同類，要逐台停用；主機加入 Active Directory 或 Entra ID 時，登入用網域帳號，停用網域帳號之後下一次登入就失敗，與由中央服務決定公鑰同類。網域主機仍有本機管理員帳號，它的密碼由 Windows LAPS 逐台產生並定期輪替，避免一組密碼通用於所有主機。已經建立的 RDP 工作階段同樣要另外結束（<code>query session</code> 找出工作階段、<code>logoff &lt;session-id&gt;</code> 結束）。</p>
<p>主機登入驗證與通道授權常由同一個系統提供（例如 Teleport 同時做代理與簽發憑證、GCP 的 IAP 與 OS Login 都依 IAM 判斷），但兩者仍然是兩次檢查：通道授權允許開通道，主機登入驗證決定進了通道之後能登入哪個帳號、是否取得 sudo。</p>
<h2 id="移除公開-ip-之後的出站路徑">移除公開 IP 之後的出站路徑</h2>
<p>管理通道改走身分感知代理、VPN 或代理程式反向連線之後，伺服器的公開 IP 對管理用途就不再必要，移除它可以讓主機從網際網路上無法定址。移除之前要先處理主機往外的連線：沒有公開 IP 的主機預設連不到網際網路，套件更新、拉容器映像檔、呼叫第三方 API 都會逾時。</p>
<p>出站需求的對應做法是 <a href="/blog/devops/infra/knowledge-cards/nat/" data-link-title="NAT Gateway" data-link-desc="private subnet 或沒有外部 IP 的 VM 要主動對外連線、評估 NAT 的數量與出站成本、或在 AWS 與 GCP 之間對照出站做法時，查 NAT Gateway 的職責、部署模式與取捨">NAT</a>：讓內部主機主動往外連、外部無法主動連進來。存取同一家雲端廠商的服務（物件儲存、容器 registry）時，多數廠商提供不經過 NAT 的私有端點，流量不出公網。計費依端點類型不同：GCP 的 Private Google Access 與 AWS 的 Gateway Endpoint（S3、DynamoDB）不另收費，可以省下 NAT 的處理費用；AWS 的 Interface Endpoint 另有按小時與傳輸量的計費。主機還帶著公開 IP 時，雲端廠商的 NAT 不處理這台主機的出站流量，流量走的是它自己的公開 IP，所以在移除公開 IP 之前測出站，結果一定是通的、證明不了 NAT 有效；確認的方式是比對出站時對方看到的來源 IP（例如 <code>curl -s https://ifconfig.me</code>），印出的是 NAT 的 IP 才代表流量走了 NAT。</p>
<p>採用代理程式反向連線的環境，管理通道本身也走出站路徑：代理程式要連到管理服務端點，出站路徑一斷，管理通道也跟著斷。這種中斷不會出現在服務的監控上。收緊出站規則的理由通常與管理通道無關（拿掉 NAT 省費用、限縮出站的目的地），而對外服務的請求不經過代理程式那條連線，服務照常回應；管理通道平時只在除錯時才有人用，除非另外監看管理服務端顯示的代理程式連線狀態，第一個發現它斷掉的人就是下一次要登入主機的人，往往是在事故當下。出站規則若在雲端防火牆上，透過雲端廠商的 API 改得回來，代價是事故處理要等找出並恢復出站路徑之後才能繼續；規則若在主機上的防火牆（ufw、nftables），就只能從救援入口改回來。所以收緊出站規則時，要把管理服務端點列進必須保留的目的地。網站經由 Cloudflare Tunnel 對外時，<code>cloudflared</code> 也是這一類的出站依賴：它用 QUIC 連 Cloudflare 的 UDP 7844、以 http2 連 TCP 7844，實測擋掉 UDP 7844 時 quick tunnel 一直重試 QUIC 而沒有改用 http2，網站回 Cloudflare 的 530，見 <a href="/blog/devops/infra/03-network-foundation/hands-on/cloudflare-origin-pull-walkthrough/" data-link-title="Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠" data-link-desc="源站要放在 Cloudflare 後面、想確認只放行 Cloudflare 位址的雲端防火牆放不放得下整份清單、回源實際用哪些位址、Authenticated Origin Pulls 怎麼驗證、Cloudflare Worker 能不能穿過這道防火牆並偽造 CF-Connecting-IP、或 Tunnel 要開哪些出站連接埠時，查 Oracle Cloud 與 Cloudflare 免費方案上實際跑過的指令與輸出">Cloudflare 回源的實測紀錄：Oracle Cloud NSG 只放行 Cloudflare 位址與規則數上限、回源的來源位址、Authenticated Origin Pulls、Worker 偽造 CF-Connecting-IP 與 Tunnel 的出站連接埠</a> 的〈Cloudflare Tunnel：源站不開入站，以及出站要放行的連接埠〉。</p>
<p>對外提供服務的主機（網站、API）移除公開 IP 之前，要先把流量入口換到負載平衡器，見<a href="/blog/devops/infra/03-network-foundation/traffic-entry-layer/" data-link-title="流量入口層 — 請求怎麼從使用者到達應用" data-link-desc="自管環境要決定請求從使用者到應用經過哪幾層入口、每層承擔什麼——TLS 終結的位置、動靜分離、單機 reverse proxy 與雲端負載平衡器的選擇">流量入口層 — 請求怎麼從使用者到達應用</a>。</p>
<h2 id="切換管理通道時避免把自己鎖在外面">切換管理通道時避免把自己鎖在外面</h2>
<p>切換管理通道的順序由一個事實決定：舊路徑被移除的那一刻起，所有走舊路徑的連線都會被拒絕，包括正在操作的管理員自己的連線。所以新路徑要在舊路徑還在的時候建好並驗證，才移除舊路徑，這個順序在網路變更上叫 make-before-break。順序是：救援入口先準備好並實際登入過一次（做法見〈救援入口：管理通道斷掉時的備用路徑〉），同時開啟舊路徑的紀錄開始觀察，接著建新通道、每一位管理員都用自己的身分實際連通一次，等觀察期涵蓋過一次完整的部署週期之後遷移非人員的來源，然後停用舊路徑驗證新路徑，最後才刪除舊路徑。</p>
<p>走舊路徑的不只管理員。CI 或部署腳本用公開 IP 直連主機、組態管理工具、監控探測、第三方廠商的維護連線，都會在舊路徑移除時一起中斷。全開規則的防火牆紀錄大部分是網際網路掃描程式的來源，CI 託管執行環境的 IP 每次不同，混在裡面認不出來，所以防火牆紀錄只用來列出候選來源；實際依賴舊路徑的連線要從主機的認證紀錄裡找成功登入的那幾筆：sshd 的 <code>Accepted publickey for &lt;user&gt; from &lt;ip&gt;</code> 帶著登入的帳號與金鑰指紋，Windows 的事件 ID 4624（登入類型 10 是 RDP）帶著登入的帳號。觀察期要涵蓋一次完整的部署週期與排程工作，找出來的非人員來源先改走新通道（機器身分用短期存取憑證開通道）。</p>
<p>舊路徑還在時，連通測試在兩種情形下會誤報成功。主機同時有公開 IP 與新通道時，連線工具可能優先走舊的直連路徑，所以測試要用工具的強制參數走新路徑（GCP 上是 <code>gcloud compute ssh --tunnel-through-iap</code>）。即使連線確實走了新通道，舊的全開規則（來源 <code>0.0.0.0/0</code>）也同時放行代理的位址段，放行這次測試連線的可能是舊規則，新規則寫錯了也測不出來。驗證新規則要先停用舊規則再測一次——停用的規則一行指令就能恢復原樣，刪除之後要重新建立、重建時可能寫錯——或查防火牆紀錄裡這次連線命中的是哪一條規則；防火牆沒有停用功能時，改用查紀錄這一種。規則在雲端防火牆上時，停用之後 SSH 連不上，管理員仍然能透過雲端廠商的 API 還原。規則在主機上的防火牆時，停用或改錯就只剩救援入口能進去，所以改之前先排一個自動還原：<code>iptables-apply</code> 套用新規則之後要操作者在時限內確認、逾時就自動回到舊規則，用 ufw 或 nftables 時用 <code>at</code> 排一個幾分鐘後還原的工作，確認新路徑可用再取消它。</p>
<p>用 IaC 管防火牆的環境，切換要改在程式碼裡，落差有兩個方向：只在網頁管理介面或 CLI 刪掉的規則，IaC 工具下一次執行 apply 時會依程式碼建回來；只在 CLI 新增的設定（新規則、主機上的 tag、IAM 授權、metadata），所屬資源若在程式碼裡被完整定義，下一次 apply 會把它拿掉，舊規則這時已經刪了，新通道跟著消失。這種落差叫 <a href="/blog/devops/infra/knowledge-cards/drift/" data-link-title="Drift（設定漂移）" data-link-desc="IaC 的 state 與雲端實際狀態之間的不一致，通常因為有人繞過 IaC 直接在 Console 改設定">drift</a>，處置的方式見<a href="/blog/devops/infra/takeover/partial-iac-dual-truth-operation/" data-link-title="兩套真相並存的過渡期操作" data-link-desc="部分資源在 IaC、部分在手動時，怎麼安全操作避免比全手動更危險，以及怎麼縮短這個過渡期">兩套真相並存的過渡期操作</a>。在 CLI 上停用舊規則做驗證的期間，要暫停所有 apply，否則驗證中的規則會被改回去。</p>
<h2 id="救援入口管理通道斷掉時的備用路徑">救援入口：管理通道斷掉時的備用路徑</h2>
<p>管理通道的任何一層出錯——防火牆規則寫錯、代理服務的權限設錯、sshd 設定改壞、磁碟滿導致 sshd 起不來——管理員就進不了主機。救援入口是一條不經過主機作業系統的網路設定與 sshd 的路徑：雲端廠商的<a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">序列主控台（serial console）</a>（GCP 的 Serial Console、AWS 的 EC2 Serial Console、Oracle Cloud 的 serial console connection）、VPS 商在管理面板提供、在瀏覽器裡顯示主機畫面的 VNC 主控台，以及地端主機的 IPMI、iDRAC 這類嵌在主機板上的頻外管理控制器（out-of-band management；其他中文資料常寫作「帶外管理」）。頻外管理控制器有自己的網路埠，它的網路要接在獨立的管理網段。救援入口不經過管理通道那三層的設定，但它有自己的授權：雲端上是雲端帳號的 IAM 權限與開啟它的開關，登入時用主機上的本機帳號密碼。</p>
<p>救援入口本身也是一個能登入主機的入口，所以它的設計與管理通道相反：平時保持關閉或只授權給少數持有救援責任的帳號，只在救援時對單一主機開啟，用完關回去。組織層級預設關閉救援入口時，救援名單裡要有能對那台主機所在的專案或帳號解除這項限制的人，否則事故當下只有主機權限的人開不了它。這種「平時不存在、事故時才開、事後回顧」的權限叫 <a href="/blog/backend/knowledge-cards/break-glass-access/" data-link-title="Break-Glass Access" data-link-desc="說明緊急情況下臨時授予的高權限存取，如何用工單、時限與事後審查治理">break-glass access</a>，工單、時限與事後回顧的做法見<a href="/blog/devops/infra/02-identity-credentials/team-access-management/" data-link-title="團隊權限分級與存取管理" data-link-desc="用 admin / operator / viewer 三級劃分團隊成員的雲端操作權限，設計臨時提權流程、定期 access review 節奏，以及 contractor 與外部 vendor 的存取邊界">團隊權限分級與存取管理</a>的〈臨時提權（break-glass）〉。</p>
<p>序列主控台與 VNC 主控台多半用主機上的本機帳號密碼登入，所以救援帳號要在主機還正常時就建好：密碼由密碼管理工具逐台隨機產生並存在受控的密碼庫，sshd 仍然維持只收金鑰（<code>PasswordAuthentication no</code>），持有救援密碼的人離開時輪替，並在切換管理通道之前用它實際登入一次，確認帳號、密碼與 sudo 都可用。地端與部分 VPS 沒有事先建好的帳號時，主機內部沒有任何不經登入的寫入路徑，只能重開機進單人模式或掛救援映像檔。雲端 VM 另外有不需要事先建帳號的修復路徑：GCP 在 metadata 設的 startup-script 每次開機都以 root 執行（Linux VM 用 <code>startup-script</code> 這個鍵），可以用來建帳號或改回 sshd 設定；AWS 的 user-data 預設只在第一次開機執行，要拿來救援得改成每次開機都執行的 cloud-init 格式；GCP、AWS 與 Oracle Cloud 都可以把開機磁碟卸下、掛到另一台 VM 修改再掛回去。這幾條路徑都要重開機或停機，比事先建好的帳號慢。不必重開機的路徑是業者在主機裡執行指令的服務：AWS 的 SSM Run Command，以及 Oracle Cloud 的 Run Command。Oracle Cloud 的 Run Command 以 <code>ocarun</code> 這個使用者執行腳本，要改 sshd 設定或別的帳號的 <code>authorized_keys</code>，得事先在主機上替 <code>ocarun</code> 設好 sudo（官方列的做法是建立時用 cloud-init 設，或建好之後登入主機設）。兩者都靠主機上的代理程式執行，代理程式沒在運作、或連不到業者的服務端點時用不上。Oracle Cloud 上序列主控台、Run Command、掛開機磁碟與重建這幾個入口各自要事先準備什麼，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。救援工作階段不經過平時錄製操作的位置，有逐指令稽核要求的環境，要在事後把救援時做了什麼另外記錄下來。</p>
<h2 id="組織層級的護欄與稽核紀錄">組織層級的護欄與稽核紀錄</h2>
<p>單一專案或帳號的收斂只保護那一處，下一個新建的專案仍可能帶著對外開放的管理 port。組織層級的護欄有兩種：限制資源能被建成什麼樣子（禁止建立帶公開 IP 的主機、不讓預設網路與它的全開規則被自動建立、強制主機登入驗證改用中央服務提供公鑰、預設關閉序列主控台），以及在組織層級評估、優先於專案規則的防火牆政策，讓專案裡即使建出來源是 <code>0.0.0.0/0</code> 的管理 port 規則，它也不生效。IaC 環境另外可以在 PR 階段用 checkov 這類工具擋下全開規則，見 <a href="/blog/devops/infra/07-infra-as-pr/plan-review-apply-guardrails/" data-link-title="infra 走 PR 流程與自動化護欄" data-link-desc="infra 變更走 PR → plan → review diff → 合併 → apply，配 fmt / validate / tflint / checkov / tfsec 與 Atlantis 自動化，讓基礎設施可審查、可回溯、可交接">infra 走 PR 流程</a>的〈tflint / checkov / tfsec：抓壞寫法與安全漏洞〉。</p>
<p><a href="/blog/backend/knowledge-cards/audit-log/" data-link-title="Audit Log" data-link-desc="說明高風險操作如何留下可追溯、可稽核的紀錄">稽核紀錄（audit log）</a>是在事後用到的：主機上出現沒人認領的設定變更、某把私鑰被回報外洩而要知道它在外洩之後有沒有被用過、成員離職後要確認他的存取確實停了。這些時刻要查的是三項紀錄：誰在什麼時候經由哪條通道登入了哪台主機、誰在什麼時候開了救援入口、環境裡是否出現新的對外開放管理 port（新建的專案或主機照預設建出全開規則，是這一項最常見的來源）。防火牆紀錄只看得到來源位址；走代理的環境，來源位址都是代理的位址段，要知道是哪個人，得靠通道授權那一層的紀錄（代理服務或 IdP 的 audit log），或主機登入驗證那一層的紀錄（每人一把時的金鑰指紋、SSH 憑證的識別字串、OS Login 的使用者名稱）。通道授權用來源 IP 時，「是誰」只剩 sshd 紀錄裡的金鑰指紋可查，而且只在每人一把金鑰時對得到人。這些紀錄多半要事先開啟，只記錄開啟之後的事，所以在切換管理通道的同一天開啟。代理服務記的多半是誰在什麼時候開了通道，不記錄通道裡執行的指令；要逐指令的紀錄，得用有工作階段錄製功能的代理或跳板機（見下面〈錄製操作與不開管理 port 的要求〉）。AWS 上對外開放規則的盤點方法見 <a href="/blog/devops/infra/03-network-foundation/security-group-audit-cleanup/" data-link-title="Security Group 稽核與清理" data-link-desc="盤點所有 security group 規則、找出 0.0.0.0/0 全開與未使用的 SG、依賴檢查後安全刪除、自動化治理">Security Group 稽核與清理</a>。</p>
<h2 id="三層做法的選擇依存取者與主機擁有者的關係主機分布收回方式與開放時間挑選跳板機vpn身分感知代理與-ssh-憑證">三層做法的選擇：依存取者與主機擁有者的關係、主機分布、收回方式與開放時間挑選跳板機、VPN、身分感知代理與 SSH 憑證</h2>
<p>選擇三層做法的順序，與封包通過三層的順序不同。封包先過網路可達性，而設計時先定下的是通道授權的身分來源，也就是通道授權判斷「這個人是誰」時查的那個帳號系統，因為網路可達性與主機登入驗證能用哪些服務，由身分來源篩出來：一個只認 Google 帳號的代理，組織的身分在 Entra ID 時，要先把身分同步或聯合（federation）進 Google 的帳號系統才用得了；OS Login 這類由中央提供公鑰的做法，前提是登入者的身分已經在提供這項服務的雲端廠商的 IAM 裡。所以下面依身分來源、網路可達性、主機登入驗證的順序，說明環境的哪一項條件會排除哪些做法；同一批主機有多種存取者時怎麼合併、錄製操作與不開管理 port 這兩種需求怎麼改變結果，各有一節；通道平時要不要開著，放在最後一節。</p>
<h3 id="通道授權的身分來源存取者與主機擁有者的關係身分所在的帳號系統">通道授權的身分來源：存取者與主機擁有者的關係、身分所在的帳號系統</h3>
<p>分開各種存取者的軸有三件事：存取者是人還是機器（機器不經過登入畫面）、存取者與主機擁有者是不是同一方，以及存取者的身分放在哪個帳號系統裡。依這三件事，存取者分成組織成員、外部合作方、個人與機器四類，各類的身分來源不同。四類都走完之後，若結果是「沒有任何可用的帳號系統」，就落在本節最後一段的處境。</p>
<p><strong>組織成員</strong>（主機擁有者自己的人）的身分在組織的 IdP 上時，通道授權用 IdP 帳號，前提是 IdP 對可以開通道的帳號強制多因素驗證，至少管理員帳號用防釣魚的方式（見〈通道授權的做法〉）。通道服務認的帳號系統與 IdP 不是同一家時有兩條路：把 IdP 的帳號聯合進那個帳號系統（例如讓 Google 的 Cloud Identity 以 SAML 單一登入接 Entra ID 或 Okta，並由 IdP 把帳號同步進去，使用者在原本的 IdP 登入、拿到的是那家雲端認得的身分），或改選一個直接接這個 IdP 的通道服務（Cloudflare Access、Teleport Enterprise）。兩條路怎麼選要看主機分布在哪裡：主機全在同一家雲端時，聯合進那家雲端是常見的選擇，代價是要維運同步與單一登入的設定；主機跨多個環境時，直接接 IdP 的通道服務一套就涵蓋全部。</p>
<p><strong>外部合作方</strong>（外包團隊、維護廠商，主機屬於委託他們的組織）的身分來源有三種狀態，由主機擁有者決定：</p>
<ul>
<li><strong>主機擁有者替他們建帳號</strong>（Entra 的 B2B 訪客、在 Cloud Identity 替外包人員建的帳號）：外部人員走組織成員那一條，多因素驗證也由主機擁有者強制；合約到期時停用帳號，或把授權設成有期限的綁定。</li>
<li><strong>主機擁有者授權給他們自己的帳號</strong>（外部人員公司或個人的 Google 帳號）：部分通道服務接受，GCP 上要另外授予外部帳號登入 VM 的角色，而組織若限制 IAM 綁定只能用自己網域的成員，這種授權會被拒絕（指令見 GCP 篇〈IAP 的 IAM 授權範圍：誰拿得到通道〉）。組織 IdP 的多因素驗證強制管不到這些帳號，採用前要確認對方帳號的驗證方式。</li>
<li><strong>兩者都不行</strong>：外部人員落在沒有可用帳號系統的處境（見本節最後一段）。合約結束、整個團隊一起離場是收回權限的時間點，所以要有一份清單，列出所有主機上屬於這個團隊的金鑰，或改用由主機擁有者的 SSH CA 簽發、會到期的 SSH 憑證（見〈主機登入驗證：登入權限的收回靠人工刪除、帳號連動或到期〉）。</li>
</ul>
<p><strong>個人</strong>（一個人管理屬於自己的一台或幾台主機）用的身分是自己在 GitHub 或 Google 的個人帳號，這個帳號就是通道授權的依據，所以它同樣要開多因素驗證，能用硬體安全金鑰或 passkey 就用。主機屬於客戶時不是這一類，那是外部合作方：用個人帳號把客戶的主機接進自己的 tailnet 或 Cloudflare 帳號，主機擁有者就失去控制，合約結束時也沒有收回的位置。主機在自己申請的雲端帳號裡時，身分已經在那一家的 IAM 裡，可以直接用那一家的代理（GCP 的 IAP、Oracle Cloud 的 Bastion、AWS 的 Session Manager），建立主機時的選法見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a> 的〈虛擬網路與 subnet：主機放在哪一段網路〉；自己有固定的對外 IP 時，來源 IP 白名單也可以用。主機分散在不同業者或家裡的機器上時，跳板機只是多開一台對外的主機，這時的選擇是 mesh VPN 或以個人帳號登入的代理程式反向連線（Cloudflare Tunnel 搭配 Access）：除了 SSH 還要連主機上其他 port、而且每台要連的裝置都能裝用戶端時選 mesh VPN；想從瀏覽器或沒裝用戶端的裝置登入時選反向連線，代價是依賴 Cloudflare 營運的服務。兩者更完整的取捨見 <a href="/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/" data-link-title="7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel" data-link-desc="以「手機遠端操作本機 shell」為情境，比較 Tailscale mesh VPN 與 Cloudflare Tunnel &#43; Access 兩種存取模型的選型判讀。">7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel</a>。</p>
<p><strong>機器</strong>（CI、Ansible 這類組態管理工具、監控探測）的身分不經過 IdP 的登入畫面，放在雲端的服務帳號上。跑在 CI 平台上的機器，用 CI 平台簽發的 OIDC token 換成服務帳號的身分（GCP 與 AWS 都稱為 workload identity federation，概念見 <a href="/blog/backend/knowledge-cards/workload-identity/" data-link-title="Workload Identity" data-link-desc="用於機器工作負載的身份語意與授權邊界">Workload Identity</a>，做法見〈通道授權的做法〉最後一段）；跑在雲端 VM 上的機器（一台定期執行 Ansible 的控制主機），用那台 VM 掛的服務帳號。收回是撤掉那個服務帳號的授權。主機登入驗證用 OS Login 這類帳號連動的做法、或 SSH 憑證時，機器不必持有長期金鑰；用靜態 <code>authorized_keys</code> 時，機器手上的 SSH 私鑰仍然長期有效，要定期輪替。</p>
<p><strong>沒有任何可用的帳號系統</strong>時，分兩種情形。主機擁有者有 IdP、只是不給外部人員帳號時，直接從下面的白名單、VPN 與跳板機裡選。組織自己還沒有 IdP 時，先比較建一個的成本：要比較的是每人一把金鑰的清單要逐台維護的工作量（人數乘以主機數、人員異動的頻率），以及稽核需不需要「是誰」的紀錄，對上建立 IdP 的成本（Google 的 Cloud Identity 免費版、把 GitHub 組織當 IdP 用）與替每個人設定多因素驗證的工作。決定不建時，管理員有固定的對外 IP 就用來源 IP 白名單，代價是分辨不出個人，「是誰」只能靠每人一把、登錄在主機 <code>authorized_keys</code> 的使用者金鑰的指紋追。沒有固定的對外 IP（遠端工作的小團隊、外包團隊維護客戶的幾台主機）時白名單縮不下來，剩下 VPN 與跳板機兩種：除了 SSH 還要連其他內部服務時選 VPN，每個人用自己的 VPN 憑證；主機有多台、只需要 SSH、或要集中錄製操作時選跳板機，每個人在跳板機上用一把金鑰、在目的主機上用另一把。每人一張憑證或一把金鑰時，〈通道授權的做法〉那張表裡的分辨個人與「是誰」的紀錄都做得到，代價是靠人工維護那份清單。</p>
<h3 id="網路可達性主機分布與代理把連線送進主機的方式">網路可達性：主機分布與代理把連線送進主機的方式</h3>
<p>這一步的輸入是〈通道授權的身分來源〉的結果（身分落在哪個帳號系統、哪些通道服務認得它）與主機分布在哪裡。</p>
<p>身分落在主機擁有者的通道服務認得的帳號系統時（組織成員、主機擁有者替外部人員建的帳號、主機擁有者授權給外部人員自己的帳號、機器的服務帳號），選擇取決於代理怎麼把連線送進主機。這些通道服務都開在主機擁有者的帳號底下，外部人員只是被授權使用。代理從自己的固定位址段連到主機內部 IP 時（GCP 的 IAP），它只到得了從那家雲端網路路由得到的主機：同一家雲端裡的主機，以及經專線或 VPN 接進那家雲端網路、而且雲端的路由器有對地端通告代理位址段的地端主機；兩種主機都要那家的代理認得這個身分（直接認得，或經由〈通道授權的身分來源〉說的聯合）。代理經由主機上的代理程式反向連線時（Cloudflare Access、Teleport），任何有出站路徑的主機都接得上，跨雲、地端與不提供代理服務的 VPS 商都落在這一類，一套通道授權涵蓋所有環境；反向連線的前提換成那條出站路徑，見〈移除公開 IP 之後的出站路徑〉。AWS 的 SSM Session Manager 也能管理地端主機（在主機上安裝代理程式並註冊為受管節點），但通道授權只認 AWS 的 IAM。接 IdP 的 mesh VPN（Tailscale、NetBird）同樣涵蓋所有環境，適合除了 SSH 還要連其他內部服務的團隊。主機全在同一家雲端、而且只需要 SSH 與 RDP 時，用那家雲端的代理（GCP 的 IAP）：身分已經在那家的 IAM 裡，主機登入驗證也能接同一套帳號（OS Login），不必另外維運代理或 VPN。</p>
<p>不想依賴第三方服務時，代理可以自己架設，代價是代理本身成為組織自己維運、對網際網路開放的入口（見〈錄製操作與不開管理 port 的要求〉）：HashiCorp Boundary 的社群版與 Pomerium 都能接任何 OIDC IdP。Teleport Community 版也能自架，但單一登入只接 GitHub（另有 Teleport 自己的本機帳號），接 OIDC 或 SAML 的 IdP 要 Enterprise 版；而且從 Teleport 16 起，它的授權只允許員工少於 100 人、年營收低於 1,000 萬美元的公司使用（兩個條件都要符合，個人使用不受限），超過的組織要用 Enterprise 版。<code>cloudflared</code> 依賴 Cloudflare 營運的服務。</p>
<p>存取者屬於「個人」這一類（主機屬於自己）、或沒有任何可用的帳號系統時，網路可達性在〈通道授權的身分來源〉已經定了：個人在 mesh VPN 與反向連線之間選，沒有帳號系統的處境在白名單、VPN 與跳板機之間選。</p>
<h3 id="同一批主機有多種存取者時">同一批主機有多種存取者時</h3>
<p>同一批主機常同時有員工、外包與 CI 要連。通道授權可以依存取者各自不同，網路可達性卻不行：一台主機對網際網路的暴露面，由所有存取者用的路徑裡最寬的那一條決定。員工改走 IAP、外包仍走公開的跳板機時，主機仍然能從網際網路經由那台跳板機觸及，員工那條路的收斂就被抵銷了。所以合併的做法是讓每一類存取者都走同一條路：替外包人員建帳號、或把他們自己的帳號授權進主機擁有者的通道服務，讓他們和員工一起走 IAP；機器以服務帳號走同一個代理。兩種帳號都拿不到的那一類，用主機擁有者自己維運、以金鑰認證、不回應未通過驗證的封包的 VPN（WireGuard），金鑰由主機擁有者發給每一個人；mesh VPN 與反向連線也行，但要開在主機擁有者的帳號底下、由主機擁有者授權，不是開在外部人員自己的帳號裡。主機登入驗證也照同一個原則合併：同一台主機上混用兩種做法（例如員工用 OS Login、外包用 SSH 憑證）會讓收回分散在兩處，能不能並存也依實作而定（OS Login 啟用之後，sshd 的設定由 guest agent 管理），所以先讓所有存取者落在同一種做法。</p>
<h3 id="錄製操作與不開管理-port-的要求">錄製操作與不開管理 port 的要求</h3>
<p>不論主機在哪裡，有兩種需求會改變上面的結果。</p>
<p>要集中錄製管理員在主機上執行了什麼時（需要這種錄製的情境例如外部廠商或外包人員要登入組織的主機、內部稽核要求留下逐指令的操作內容而不只是登入紀錄），錄製要有一個所有工作階段都經過的位置：有工作階段錄製功能的代理（Teleport 錄製每一個 SSH 工作階段），或跳板機。選了跳板機時，身分來源仍然沿用〈通道授權的身分來源〉選定的那一個：有 IdP 的組織架一個接 IdP 登入的 SSH CA，管理員登入 IdP 換到短期 SSH 憑證，跳板機只信任這個 CA。沒有 IdP、或外部人員拿不到帳號時，由主機擁有者的 SSH CA 依人簽發短期憑證，憑證的識別字串寫進錄製紀錄，做法與〈主機登入驗證：登入權限的收回靠人工刪除、帳號連動或到期〉的外包團隊相同。跳板機退回每人一把靜態金鑰的話，錄製是有了，隨帳號收回的能力卻沒了。GCP 的 IAP 這類代理只記錄誰開了通道，不錄製通道裡的指令。</p>
<p>需求是「沒有任何主機對網際網路開管理 port」（客戶或稽核常這樣要求）時，這個需求要防的是一個由組織自己維運、在網際網路上監聽、而且會回應沒有通過認證的來源的入口：sshd、SSL VPN 設備與自架代理的對外端點都是這種入口，掃描程式找得到它，認證之前的漏洞也打得到它。代理服務的邊緣節點同樣回應未認證的來源，差別在它由廠商維運，修補的責任跟著移到廠商身上。依這個判斷，跳板機與來源 IP 白名單都不符合：跳板機本身仍對外開著 22 port，白名單則是每台主機都對外開著 22 port、只是限制了來源；組織自己維運的 SSL VPN 與 IPsec 閘道，以及自架、對網際網路開放的 Teleport、Boundary、Pomerium 的 proxy 也不符合。符合的是廠商維運的代理程式反向連線、mesh VPN（裝置之間以 NAT traversal 連線，協調伺服器由廠商維運）、從代理位址段連入的身分感知代理（主機只接受代理位址的入站，對外監聽的是廠商的代理服務），以及 WireGuard：它在主機上開一個 UDP port，但不回應沒有通過金鑰驗證的封包，掃描程式看不到它在監聽。WireGuard 與 mesh VPN 算不算符合，取決於提出需求的一方怎麼定義「開管理 port」，採用前要跟客戶或稽核確認。</p>
<p>兩個需求同時成立時（受稽核的金融業常見），符合的組合有兩種：一種是有工作階段錄製功能、而且以反向連線或代理位址段把連線送進主機的代理，例如 Teleport Enterprise 的廠商代管版（Teleport Cloud：proxy 由廠商維運，節點以反向通道連到 proxy、錄製每個工作階段、接 Entra ID 或 Okta）；自架的 Teleport proxy 本身就是對網際網路開放的入口，不符合不開管理 port 的需求；另一種是沒有公開 IP、放在內部網段的跳板機負責錄製，管理員經由 IAP 或反向連線的代理連到這台跳板機。救援工作階段不經過這兩種錄製位置，處理方式見〈救援入口：管理通道斷掉時的備用路徑〉。</p>
<h3 id="主機登入驗證登入權限的收回靠人工刪除帳號連動或到期">主機登入驗證：登入權限的收回靠人工刪除、帳號連動或到期</h3>
<p>網路可達性選了直接在主機上開 shell 或自帶 SSH 伺服器的做法（SSM Session Manager 的預設模式、Tailscale SSH、Teleport 的節點）時，sshd 不在路徑上，這一層改由那個服務的權限設定承擔。其餘情形，主機登入驗證的選擇取決於登入權限的收回靠什麼發生：</p>
<ul>
<li><strong>收回靠人工刪除</strong>（靜態 <code>authorized_keys</code>）：成立的條件是主機少、人員穩定，而且收回已經是離職與合約結束交接流程裡的一項。</li>
<li><strong>收回靠帳號連動</strong>（由中央服務決定公鑰）：已經用某家廠商的 IdP、而那家廠商提供依同一個身分判斷的公鑰服務（GCP 的 OS Login）時，不必另建簽發服務或金鑰派送流程，收回跟著帳號走；它綁定那家廠商，主機在別的環境就用不了。自建的 <code>AuthorizedKeysCommand</code> 接 LDAP 或 FreeIPA 跨環境也做得到，代價是每台主機登入時都要連得到目錄服務，主機端沒有快取時（FreeIPA 的用戶端 SSSD 會在主機上快取公鑰），目錄服務中斷所有主機一起登不進去。</li>
<li><strong>收回靠到期</strong>（SSH 憑證）：主機跨多個環境（不同雲端、地端、不同 VPS 商，各自管理金鑰的主機群）時，它不依賴任何一家雲端廠商的帳號系統，也不必逐台收回，代價是要維運一個簽發服務並保管 CA 私鑰；私鑰外洩而要提前作廢時，撤銷清單仍要派送到每一台主機。SSH 憑證也適用於人員流動高、而且進不了客戶帳號系統的外包團隊：由主機擁有者的 CA 每天簽發、有效期設成一個工作日左右，合約結束時停止簽發，最後一張憑證在一個有效期之內失效，不必逐台盤點金鑰。</li>
<li><strong>Windows 主機</strong>（RDP）：主機加入 Active Directory 或 Entra ID 時，登入用網域帳號，收回跟著網域帳號走，屬於帳號連動；沒有網域時（例如組織只有 Google Workspace），每個管理員在每台主機上各建一個本機帳號、不共用，收回靠人工逐台停用。本機管理員帳號的密碼在有網域時用 Windows LAPS 逐台產生並輪替，沒有網域時用密碼管理工具逐台產生；Windows 主機多的組織，把主機加入 Entra ID 通常比逐台維護本機帳號省事。</li>
</ul>
<h3 id="不可變基礎設施下的臨時授權通道平時關閉">不可變基礎設施下的臨時授權：通道平時關閉</h3>
<p>服務已經用不可變基礎設施（immutable infrastructure）的方式部署，也就是以重新建立主機取代登入修改時，管理通道只在除錯與救援時才用得到。這時可以讓通道平時對任何人都開不起來：管理員平時移除通道授權的權限綁定，除錯時才臨時授予，用完收回；這一整個循環叫 just-in-time access，授予與收回的流程與救援入口相同，見〈救援入口：管理通道斷掉時的備用路徑〉。主機由 managed instance group 或 GKE 這類機制依 template 重建時，救援也多半改成重建一台而不是登入修，重建、退回舊版 template 與登入修各自適用的情形見<a href="/blog/devops/infra/cloud-providers/gcp/gke-mig-management-access/" data-link-title="GCP 上由 MIG 與 GKE 管理的 VM 的管理存取：instance template、node pool、kubectl debug 與控制平面存取" data-link-desc="VM 由 managed instance group 或 GKE node pool 管理時，管理通道的設定為什麼要寫在 instance template 與 node pool 而不是逐台修改、MIG 救援用重建、退回 template 與 Serial Console 各自適用的情形、GKE 的控制平面與節點兩個存取面、kubectl debug 取代節點 SSH 的權限邊界（RBAC 與 Pod Security Admission），以及 GKE 控制平面三種端點（authorized networks、DNS-based endpoint 與 private endpoint）的差別">GCP 上由 MIG 與 GKE 管理的 VM 的管理存取</a>，重建一台 VM 的指令與實測結果見 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/mig-gke-access-walkthrough/" data-link-title="GCP 上 MIG 與 GKE 管理存取的實測紀錄：instance template 與 canary 滾動更新、重建與主機金鑰、kubectl debug 的 namespace、node pool 設定與控制平面端點的切換" data-link-desc="要把 IAP 的 tag、OS Login 與移除外部 IP 套到 MIG 或 GKE 上，想照一次實跑過的順序做、看每一步換一種做法的結果時讀；也用在 MIG 重建之後 SSH 出現 REMOTE HOST IDENTIFICATION HAS CHANGED、kubectl debug 停在等待，或關掉 GKE 控制平面的 IP 端點之前要確認每個人都改連的時候">GCP 上 MIG 與 GKE 管理存取的實測紀錄</a> 的〈重建一台壞掉的 VM：recreate-instances 與重建之後的主機金鑰〉。</p>
<h2 id="aws-與-oracle-cloud-上對應的元件">AWS 與 Oracle Cloud 上對應的元件</h2>
<p>這篇提到的 AWS 元件按層列在這裡，方便對照：網路可達性有 SSM Session Manager（代理程式反向連線）、EC2 Instance Connect Endpoint（從代理位址連入，支援原生 SSH 與 RDP）與 security group（防火牆規則）；通道授權由 AWS IAM 決定誰能開 Session Manager 的工作階段或使用 Instance Connect Endpoint；主機登入驗證有 EC2 Instance Connect（推送短效公鑰）；出站路徑有 NAT Gateway 與 VPC Endpoint；救援入口是 EC2 Serial Console；組織層級的護欄是 AWS Organizations 的 <a href="/blog/devops/infra/knowledge-cards/scp/" data-link-title="SCP (Service Control Policy)" data-link-desc="AWS Organizations 層級的權限天花板，套用到 OU 後連管理員都越不過">SCP</a>。規則的盤點方式見 <a href="/blog/devops/infra/03-network-foundation/security-group-audit-cleanup/" data-link-title="Security Group 稽核與清理" data-link-desc="盤點所有 security group 規則、找出 0.0.0.0/0 全開與未使用的 SG、依賴檢查後安全刪除、自動化治理">Security Group 稽核與清理</a>。</p>
<p>Oracle Cloud 的 Bastion 名字跟跳板機相同，位置在這張地圖上卻不一樣。它是業者託管的服務，不是放在公開網段、對外開 22 port 的那台主機：官方說明 Bastion 位在公開 subnet，替使用者建好連到私有 subnet 裡目標主機所需的網路設施，主機不需要公開 IP，所以它承擔網路可達性；誰能建立工作階段由 Oracle Cloud 的 IAM 政策決定，這是通道授權。Bastion 的工作階段共有三種：managed SSH session 由主機上的代理程式臨時放進一把公鑰，承擔主機登入驗證，而官方列出 Arm 架構的 A1 機型加非 Oracle Linux 的主機不支援這一種，Oracle Linux 也要選代理程式提供 Bastion plugin 的版本（支援範圍與查法見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉）；SSH port forwarding session 只承擔網路可達性與通道授權，主機登入驗證仍然是主機自己的 sshd 與 <code>authorized_keys</code>；dynamic port forwarding（SOCKS5）session 同樣不經過代理程式，轉接的目標不固定，可以動態連到私有 subnet 裡的任何資源。私有 subnet 裡的主機要用 managed SSH session，VCN 要有服務閘道之類的閘道與路由，實際的網路設定見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈進建立頁之前：compartment、VCN、閘道與 private subnet〉。各欄位怎麼設定見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉，金鑰遺失時 Oracle Cloud 的救援入口（序列主控台、Run Command、把開機磁碟掛到另一台主機修改）見同一篇的〈建立之後加金鑰與金鑰遺失時的入口〉。〈移除公開 IP 之後的出站路徑〉在 Oracle Cloud 上的實作（私有 subnet 的主機加 NAT gateway、路由規則與只掛在那台主機上的出站 NSG），見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈加出站：NAT gateway、路由規則與只掛在這台主機上的 NSG〉。</p>
<h2 id="延伸閱讀gcp-上的實作與反向連線的運作細節">延伸閱讀：GCP 上的實作與反向連線的運作細節</h2>
<ul>
<li>→ <a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding</a>：IAP、OS Login、Cloud NAT、Serial Console 與組織政策在 GCP 上的機制與切換順序；停用舊規則驗證的逐步指令在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a></li>
<li>→ <a href="/blog/backend/05-deployment-platform/outbound-tunnel-entry/" data-link-title="5.10 Outbound Tunnel 入口與生命週期" data-link-desc="整理 cloudflared / Tailscale 等反向隧道的入口形態、生命週期合約與故障模式">5.10 Outbound Tunnel 入口與生命週期</a>：代理程式反向連線那條往外的連線怎麼建立、斷線時的故障模式</li>
<li>→ <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a>：只用 ssh-keygen 與 sshd 簽發、撤銷 SSH 憑證的完整流程，主機登入驗證選「收回靠到期」時的實作</li>
<li>→ <a href="/blog/devops/infra/cloud-providers/gcp/gke-mig-management-access/" data-link-title="GCP 上由 MIG 與 GKE 管理的 VM 的管理存取：instance template、node pool、kubectl debug 與控制平面存取" data-link-desc="VM 由 managed instance group 或 GKE node pool 管理時，管理通道的設定為什麼要寫在 instance template 與 node pool 而不是逐台修改、MIG 救援用重建、退回 template 與 Serial Console 各自適用的情形、GKE 的控制平面與節點兩個存取面、kubectl debug 取代節點 SSH 的權限邊界（RBAC 與 Pod Security Admission），以及 GKE 控制平面三種端點（authorized networks、DNS-based endpoint 與 private endpoint）的差別">GCP 上由 MIG 與 GKE 管理的 VM 的管理存取</a>：主機由 template 重建時，三層的設定要寫在哪裡</li>
<li>→ <a href="/blog/backend/07-security-data-protection/cases/remote-shell-access-tailscale-vs-cloudflare-tunnel/" data-link-title="7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel" data-link-desc="以「手機遠端操作本機 shell」為情境，比較 Tailscale mesh VPN 與 Cloudflare Tunnel &#43; Access 兩種存取模型的選型判讀。">7.C11 選型：單人遠端 Shell — Tailscale vs Cloudflare Tunnel</a>：mesh VPN 與代理程式反向連線用在同一個需求上的取捨</li>
</ul>
]]></content:encoded></item><item><title>OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證</title><link>https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/</guid><description>&lt;p>這篇整理只用 OpenSSH 本身（&lt;code>ssh-keygen&lt;/code> 與 &lt;code>sshd&lt;/code>，不加 Vault 或 Teleport 這類簽發服務）導入 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 憑證（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH certificate&lt;/a>）的做法。範圍從「主機上的 sshd 已經只收金鑰登入」開始，到每位管理員用短期憑證登入、憑證外洩時能提前作廢、用戶端不必再逐台確認主機指紋為止。文中的指令與紀錄格式在 OpenSSH 9.6 上跑過；本篇用到的 ed25519 金鑰與 KRL 撤銷清單要 OpenSSH 6.5 以上，憑證機制本身從 5.4 起就有。為什麼要把登入權限從靜態 &lt;code>authorized_keys&lt;/code> 換成會到期的憑證、它跟 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> 這類由中央服務決定公鑰的做法怎麼選，見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/#%e4%b8%bb%e6%a9%9f%e7%99%bb%e5%85%a5%e9%a9%97%e8%ad%89%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90%e7%9a%84%e6%94%b6%e5%9b%9e%e9%9d%a0%e4%ba%ba%e5%b7%a5%e5%88%aa%e9%99%a4%e5%b8%b3%e8%99%9f%e9%80%a3%e5%8b%95%e6%88%96%e5%88%b0%e6%9c%9f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>的〈主機登入驗證：登入權限的收回靠人工刪除、帳號連動或到期〉。&lt;/p>
&lt;h2 id="ssh-ca-的運作使用者憑證主機憑證與兩把-ca">SSH CA 的運作：使用者憑證、主機憑證與兩把 CA&lt;/h2>
&lt;p>靜態 &lt;code>authorized_keys&lt;/code>（每個帳號家目錄裡的 &lt;code>~/.ssh/authorized_keys&lt;/code>，一行一把可以登入這個帳號的公鑰）的做法裡，每台主機各自記著「哪些公鑰可以登入哪個帳號」，所以新增一個人要改每一台，收回一個人也要改每一台。SSH CA 把這份清單換成一個簽名：一把 CA 私鑰對使用者的公鑰簽名，簽出一張憑證，憑證裡寫著一組名稱（principal，主機依它決定這張憑證能登入哪些帳號）、在什麼期間內有效、識別字串是什麼（key ID）。主機的 sshd 只要信任那把 CA 的公鑰，任何帶著有效 CA 簽名、還在有效期內、principal 對得上的憑證都能登入，主機上不必有任何一個人的公鑰。&lt;/p>
&lt;p>同一個機制反過來用在主機上，叫主機憑證：CA 對主機的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-host-key/" data-link-title="SSH Host Key 與 known_hosts（主機金鑰）" data-link-desc="ssh 第一次連線問要不要信任指紋、出現 REMOTE HOST IDENTIFICATION HAS CHANGED 警告，或主機重建之後連不上時，查 host key 怎麼證明主機身分、known_hosts 記了什麼，以及首次信任的風險">host key&lt;/a>（主機用來向用戶端證明自己身分的金鑰，用戶端第一次連線時確認的指紋就是它的）簽名，用戶端只要信任這把 CA，連到任何一台帶著有效主機憑證的主機都不必再確認指紋。使用者憑證與主機憑證最好用兩把不同的 CA 簽：使用者 CA 的私鑰外洩，攻擊者能簽出登入任何主機的憑證；主機 CA 的私鑰外洩，攻擊者能冒充任何主機。兩把分開，一把外洩只影響一個方向。&lt;/p>
&lt;p>這套做法換掉的是收回方式：權限到期自動失效，收回延遲的上限是憑證的有效期。代價是多了要保管的 CA 私鑰（使用者與主機各一把）、一份要派送到每台主機的撤銷清單，以及一個每天替管理員簽發憑證的流程。&lt;/p>
&lt;h2 id="產生使用者-ca-與主機-ca-的金鑰">產生使用者 CA 與主機 CA 的金鑰&lt;/h2>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 在簽發主機（保管 CA 私鑰的機器，位置的選擇見下方）上產生兩把 CA 金鑰：使用者 CA 與主機 CA 分開&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">ssh-keygen -t ed25519 -f user_ca -C &lt;span class="s2">&amp;#34;user-ca&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">ssh-keygen -t ed25519 -f host_ca -C &lt;span class="s2">&amp;#34;host-ca&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>-t ed25519&lt;/code>：金鑰類型。CA 金鑰與一般金鑰沒有格式上的差別，差別只在用途。&lt;/li>
&lt;li>&lt;code>-f user_ca&lt;/code>、&lt;code>-f host_ca&lt;/code>：輸出的檔名，自己取。執行之後產生 &lt;code>user_ca&lt;/code>（私鑰）與 &lt;code>user_ca.pub&lt;/code>（公鑰），主機 CA 同理。&lt;/li>
&lt;li>&lt;code>-C&lt;/code>：註解，只是讓人辨認這把金鑰，不影響驗證。&lt;/li>
&lt;li>執行時會要求輸入 passphrase，CA 私鑰要設：之後每次簽發都要輸入它，私鑰檔被複製走時攻擊者也還要猜這個 passphrase。&lt;/li>
&lt;/ul>
&lt;p>CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以它的保管位置決定了這套做法的安全上限：放在一台專用、不對外開放、只有少數人能登入的簽發主機上，或放在&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/hsm/" data-link-title="HSM（Hardware Security Module）" data-link-desc="判斷金鑰材料需不需要脫離軟體邊界、放進不可讀取明文的專用硬體時的核心術語">硬體安全模組（HSM）&lt;/a>裡（&lt;code>ssh-keygen&lt;/code> 支援用 &lt;code>-D&lt;/code> 指定 PKCS#11 函式庫，讓私鑰留在硬體裡簽名；PKCS#11 是程式存取硬體金鑰的標準介面。這時 &lt;code>-s&lt;/code> 給的是 CA 的公鑰檔，&lt;code>ssh-keygen&lt;/code> 用它找出硬體裡對應的那把私鑰，簽發指令寫成 &lt;code>ssh-keygen -D &amp;lt;pkcs11-library-path&amp;gt; -s user_ca.pub -I ... id_ed25519.pub&lt;/code>，函式庫路徑看硬體廠商的文件）。成本介於兩者之間的做法是把 CA 私鑰放進 FIDO 或 PIV 這類硬體金鑰、經由 &lt;code>ssh-agent&lt;/code> 簽名（&lt;code>ssh-keygen -U -s user_ca.pub ...&lt;/code>，&lt;code>-U&lt;/code> 表示私鑰在 agent 裡），私鑰同樣不以檔案形式存在。不放在任何人的筆電硬碟上，也不放進版本控制。公鑰（&lt;code>.pub&lt;/code>）不是機密，可以放進&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理&lt;/a>工具派送。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理只用 OpenSSH 本身（<code>ssh-keygen</code> 與 <code>sshd</code>，不加 Vault 或 Teleport 這類簽發服務）導入 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 憑證（<a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH certificate</a>）的做法。範圍從「主機上的 sshd 已經只收金鑰登入」開始，到每位管理員用短期憑證登入、憑證外洩時能提前作廢、用戶端不必再逐台確認主機指紋為止。文中的指令與紀錄格式在 OpenSSH 9.6 上跑過；本篇用到的 ed25519 金鑰與 KRL 撤銷清單要 OpenSSH 6.5 以上，憑證機制本身從 5.4 起就有。為什麼要把登入權限從靜態 <code>authorized_keys</code> 換成會到期的憑證、它跟 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 這類由中央服務決定公鑰的做法怎麼選，見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e4%b8%bb%e6%a9%9f%e7%99%bb%e5%85%a5%e9%a9%97%e8%ad%89%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90%e7%9a%84%e6%94%b6%e5%9b%9e%e9%9d%a0%e4%ba%ba%e5%b7%a5%e5%88%aa%e9%99%a4%e5%b8%b3%e8%99%9f%e9%80%a3%e5%8b%95%e6%88%96%e5%88%b0%e6%9c%9f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>的〈主機登入驗證：登入權限的收回靠人工刪除、帳號連動或到期〉。</p>
<h2 id="ssh-ca-的運作使用者憑證主機憑證與兩把-ca">SSH CA 的運作：使用者憑證、主機憑證與兩把 CA</h2>
<p>靜態 <code>authorized_keys</code>（每個帳號家目錄裡的 <code>~/.ssh/authorized_keys</code>，一行一把可以登入這個帳號的公鑰）的做法裡，每台主機各自記著「哪些公鑰可以登入哪個帳號」，所以新增一個人要改每一台，收回一個人也要改每一台。SSH CA 把這份清單換成一個簽名：一把 CA 私鑰對使用者的公鑰簽名，簽出一張憑證，憑證裡寫著一組名稱（principal，主機依它決定這張憑證能登入哪些帳號）、在什麼期間內有效、識別字串是什麼（key ID）。主機的 sshd 只要信任那把 CA 的公鑰，任何帶著有效 CA 簽名、還在有效期內、principal 對得上的憑證都能登入，主機上不必有任何一個人的公鑰。</p>
<p>同一個機制反過來用在主機上，叫主機憑證：CA 對主機的 <a href="/blog/devops/infra/knowledge-cards/ssh-host-key/" data-link-title="SSH Host Key 與 known_hosts（主機金鑰）" data-link-desc="ssh 第一次連線問要不要信任指紋、出現 REMOTE HOST IDENTIFICATION HAS CHANGED 警告，或主機重建之後連不上時，查 host key 怎麼證明主機身分、known_hosts 記了什麼，以及首次信任的風險">host key</a>（主機用來向用戶端證明自己身分的金鑰，用戶端第一次連線時確認的指紋就是它的）簽名，用戶端只要信任這把 CA，連到任何一台帶著有效主機憑證的主機都不必再確認指紋。使用者憑證與主機憑證最好用兩把不同的 CA 簽：使用者 CA 的私鑰外洩，攻擊者能簽出登入任何主機的憑證；主機 CA 的私鑰外洩，攻擊者能冒充任何主機。兩把分開，一把外洩只影響一個方向。</p>
<p>這套做法換掉的是收回方式：權限到期自動失效，收回延遲的上限是憑證的有效期。代價是多了要保管的 CA 私鑰（使用者與主機各一把）、一份要派送到每台主機的撤銷清單，以及一個每天替管理員簽發憑證的流程。</p>
<h2 id="產生使用者-ca-與主機-ca-的金鑰">產生使用者 CA 與主機 CA 的金鑰</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在簽發主機（保管 CA 私鑰的機器，位置的選擇見下方）上產生兩把 CA 金鑰：使用者 CA 與主機 CA 分開</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -t ed25519 -f user_ca -C <span class="s2">&#34;user-ca&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">ssh-keygen -t ed25519 -f host_ca -C <span class="s2">&#34;host-ca&#34;</span></span></span></code></pre></div><ul>
<li><code>-t ed25519</code>：金鑰類型。CA 金鑰與一般金鑰沒有格式上的差別，差別只在用途。</li>
<li><code>-f user_ca</code>、<code>-f host_ca</code>：輸出的檔名，自己取。執行之後產生 <code>user_ca</code>（私鑰）與 <code>user_ca.pub</code>（公鑰），主機 CA 同理。</li>
<li><code>-C</code>：註解，只是讓人辨認這把金鑰，不影響驗證。</li>
<li>執行時會要求輸入 passphrase，CA 私鑰要設：之後每次簽發都要輸入它，私鑰檔被複製走時攻擊者也還要猜這個 passphrase。</li>
</ul>
<p>CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以它的保管位置決定了這套做法的安全上限：放在一台專用、不對外開放、只有少數人能登入的簽發主機上，或放在<a href="/blog/backend/knowledge-cards/hsm/" data-link-title="HSM（Hardware Security Module）" data-link-desc="判斷金鑰材料需不需要脫離軟體邊界、放進不可讀取明文的專用硬體時的核心術語">硬體安全模組（HSM）</a>裡（<code>ssh-keygen</code> 支援用 <code>-D</code> 指定 PKCS#11 函式庫，讓私鑰留在硬體裡簽名；PKCS#11 是程式存取硬體金鑰的標準介面。這時 <code>-s</code> 給的是 CA 的公鑰檔，<code>ssh-keygen</code> 用它找出硬體裡對應的那把私鑰，簽發指令寫成 <code>ssh-keygen -D &lt;pkcs11-library-path&gt; -s user_ca.pub -I ... id_ed25519.pub</code>，函式庫路徑看硬體廠商的文件）。成本介於兩者之間的做法是把 CA 私鑰放進 FIDO 或 PIV 這類硬體金鑰、經由 <code>ssh-agent</code> 簽名（<code>ssh-keygen -U -s user_ca.pub ...</code>，<code>-U</code> 表示私鑰在 agent 裡），私鑰同樣不以檔案形式存在。不放在任何人的筆電硬碟上，也不放進版本控制。公鑰（<code>.pub</code>）不是機密，可以放進<a href="/blog/devops/infra/knowledge-cards/configuration-management/" data-link-title="Configuration Management（組態管理）" data-link-desc="要讓好幾台主機的套件、設定檔與服務維持同一個狀態、讀到 Ansible 的 inventory 與 group_vars、或要判斷主機上的設定該交給 Ansible 這類工具還是 Terraform 這類 IaC 管理時">組態管理</a>工具派送。</p>
<h2 id="設定主機的-sshd信任使用者-ca">設定主機的 sshd：信任使用者 CA</h2>
<p>每台要讓管理員登入的主機，在改 <code>sshd_config</code> 之前先把三行設定要指向的檔案放到位：設定一旦生效，sshd 每次驗證都會去讀這三個檔，少了其中一個，下面〈兩個陷阱〉那一段講的情形就會在重新載入的那一刻發生。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在每台主機上：放使用者 CA 的公鑰（從簽發主機複製過來）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo install -m <span class="m">644</span> user_ca.pub /etc/ssh/user_ca.pub
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 建立放 principals 檔的目錄，所有帳號都要讀得到</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">sudo install -d -m <span class="m">755</span> /etc/ssh/auth_principals
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"># 產生一份不含任何項目的撤銷清單</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">sudo ssh-keygen -k -f /etc/ssh/revoked.krl</span></span></code></pre></div><p><code>ssh-keygen -k -f</code> 沒有 <code>-u</code>，會建立一份新的清單；只在第一次建立時用，對已有內容的清單執行會把它清空。principals 檔的內容見下方。三個檔都到位之後，在 <code>sshd_config</code> 加入：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># /etc/ssh/sshd_config：信任使用者 CA 簽發的憑證
</span></span><span class="line"><span class="ln">2</span><span class="cl">TrustedUserCAKeys /etc/ssh/user_ca.pub
</span></span><span class="line"><span class="ln">3</span><span class="cl">AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
</span></span><span class="line"><span class="ln">4</span><span class="cl">RevokedKeys /etc/ssh/revoked.krl</span></span></code></pre></div><ul>
<li><code>TrustedUserCAKeys</code>：指向使用者 CA 的公鑰檔。這一行讓 sshd 接受這把 CA 簽的憑證；檔案裡可以放多把 CA 公鑰，一行一把，換 CA 時新舊兩把並列一段時間。</li>
<li><code>AuthorizedPrincipalsFile</code>：指定每個帳號接受哪些 principal，<code>%u</code> 會代入要登入的帳號名稱，所以 <code>/etc/ssh/auth_principals/deploy</code> 這個檔案列出的 principal 能登入 <code>deploy</code> 帳號。設了這一行之後，sshd 對每個帳號都只看它自己的 principals 檔：個人帳號 <code>alice</code> 也要有一份 <code>/etc/ssh/auth_principals/alice</code>、裡面列出 <code>alice</code>，沒有檔案的帳號用憑證登不進去。<strong><code>sshd_config</code> 沒有這一行時，憑證裡的 principal 必須與登入的帳號名稱完全相同</strong>（簽給 <code>alice</code> 的憑證只能登入 <code>alice</code> 帳號）。</li>
<li><code>RevokedKeys</code>：撤銷清單的位置，格式與用法見〈憑證外洩時提前作廢：KRL 撤銷清單〉。</li>
</ul>
<p>principals 檔是一行一個 principal：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl"># /etc/ssh/auth_principals/deploy：哪些 principal 可以登入 deploy 帳號
</span></span><span class="line"><span class="ln">2</span><span class="cl">alice
</span></span><span class="line"><span class="ln">3</span><span class="cl">ops-oncall
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"># /etc/ssh/auth_principals/alice：alice 自己的帳號
</span></span><span class="line"><span class="ln">6</span><span class="cl">alice</span></span></code></pre></div><p>principal 的命名有兩種常見做法：一種是用人名（<code>alice</code>），principals 檔列出誰能登入哪個帳號；另一種是用角色（<code>ops-oncall</code>、<code>db-admin</code>），簽發時依這個人的角色把對應的 principal 簽進憑證，主機上的 principals 檔只列角色，人員異動時不必改任何一台主機。人少、人員很少異動時用人名就夠；人員常異動、或主機數量多到改 principals 檔本身就是負擔時，用角色。主機上只有一個共用帳號（雲端映像預設的 <code>ubuntu</code>、<code>ec2-user</code>）時，用角色當 principal、簽進憑證的 key ID 寫人名，sshd 的紀錄才分得出是誰登入（見〈sshd 紀錄裡的憑證資訊〉）。</p>
<p>改完設定先檢查語法，再重新載入。<code>sshd -t</code> 只檢查設定檔的語法，下面兩個陷阱它都抓不到（撤銷清單不存在、principals 檔讀不到時它照樣沒有輸出），所以重新載入之後要從另一個終端機實際用憑證登入一次：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 檢查設定檔語法，沒有輸出代表語法正確</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">sudo sshd -t
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># 重新載入設定，已經建立的連線不受影響</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">sudo systemctl reload ssh    <span class="c1"># RHEL 系的服務名稱是 sshd</span></span></span></code></pre></div><p>設定這一步有兩個在實測中會讓所有人登不進去的陷阱，套用到一台以上的主機之前都要先在一台上確認：</p>
<ul>
<li><strong><code>AuthorizedPrincipalsFile</code> 是用登入帳號本人的權限讀的</strong>：sshd 在驗證時以那個帳號的身分開這個檔，路徑上任何一層目錄那個帳號讀不到，sshd 就當成沒有任何 principal、拒絕登入，紀錄裡寫的是 <code>Could not open user 'alice' authorized principals ...: Permission denied</code> 與 <code>Certificate does not contain an authorized principal</code>。放在 <code>/etc/ssh/auth_principals/</code>、目錄權限 755、檔案權限 644，所有帳號都讀得到。反方向也一樣：檔案若被改成其他人可寫（例如 666），sshd 會以權限過寬拒絕它，而預設紀錄等級下留下的只有 <code>Certificate does not contain an authorized principal</code>，看不出原因在權限。</li>
<li><strong><code>RevokedKeys</code> 指的檔案不存在時，sshd 拒絕所有金鑰登入</strong>：不只憑證，連 <code>authorized_keys</code> 裡的一般公鑰也一起被拒，紀錄裡是 <code>Error checking authentication key ... in revoked keys file ...: No such file or directory</code>。所以空的撤銷清單要在加這一行設定之前放好（上面準備檔案的那一段）。之後的派送用新檔覆蓋舊檔，派送失敗時主機上留著的是上一版清單：sshd 照常運作，只是這次新撤銷的憑證在那台主機上還沒生效。</li>
</ul>
<p>導入期間 <code>authorized_keys</code> 照常生效：sshd 同時接受憑證與 <code>authorized_keys</code> 裡的公鑰，所以可以先讓每位管理員用憑證登入過一次，確認之後再停用 <code>authorized_keys</code>（做法見〈導入順序〉）。用另一個終端機保留一條已登入的連線再改設定，改錯時還有一條路能改回來；整台都進不去時的備用路徑見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>的〈救援入口：管理通道斷掉時的備用路徑〉。</p>
<h2 id="簽發使用者憑證識別字串principal-與有效期">簽發使用者憑證：識別字串、principal 與有效期</h2>
<p>管理員把自己的公鑰交給持有 CA 私鑰的人或簽發主機，簽出一張憑證：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 對 alice 的公鑰簽發一張可以登入 alice 與 deploy 帳號、8 小時有效的憑證</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -s user_ca -I <span class="s2">&#34;alice@example.com&#34;</span> -n alice,deploy -V -5m:+8h -z <span class="m">1001</span> id_ed25519.pub</span></span></code></pre></div><ul>
<li><code>-s user_ca</code>：用來簽名的 CA 私鑰。</li>
<li><code>-I &quot;alice@example.com&quot;</code>：識別字串（key ID），會出現在 sshd 的認證紀錄與撤銷清單裡，填一個能對回人的值，通常是這個人在 IdP 的帳號。</li>
<li><code>-n alice,deploy</code>：principal，多個用逗號分隔，對應主機 principals 檔裡的名稱（<code>sshd_config</code> 沒有 <code>AuthorizedPrincipalsFile</code> 這一行時對應帳號名稱）。這張憑證能登入 <code>alice</code> 帳號，前提是主機上有列出 <code>alice</code> 的 <code>/etc/ssh/auth_principals/alice</code>。不給 <code>-n</code> 時簽出的憑證不帶任何 principal：<code>TrustedUserCAKeys</code> 會拒絕這種憑證；但 OpenSSH 另有一種逐帳號信任 CA 的寫法，在某個帳號的 <code>authorized_keys</code> 裡寫一行 <code>cert-authority ssh-ed25519 AAAA...</code>（CA 公鑰），這一行會接受不帶 principal 的憑證登入那個帳號。本篇用的是 <code>TrustedUserCAKeys</code>，而主機上若有人在某個帳號的 <code>authorized_keys</code> 寫過 <code>cert-authority</code> 行，不帶 principal 的憑證就能登入那個帳號，所以每次簽發都寫出 principal。<code>authorized_keys</code> 裡的 <code>cert-authority</code> 與主機憑證一節用戶端 <code>known_hosts</code> 裡的 <code>@cert-authority</code> 是兩回事：<code>authorized_keys</code> 那一行在主機上信任使用者 CA，<code>known_hosts</code> 那一行在用戶端信任主機 CA。</li>
<li><code>-V -5m:+8h</code>：有效期，從五分鐘前到八小時後。起點往前推五分鐘是為了容忍主機之間的時鐘誤差：只寫 <code>+8h</code> 時有效期從簽發主機的此刻開始，時鐘稍慢的主機會判定憑證「尚未生效」而拒絕，用戶端只看到 <code>Permission denied</code>。也可以寫成 <code>20261101:20261102</code> 這種起訖日期。有效期就是收回延遲的上限，手動簽發時常設成一個工作日左右，範例的八小時就是這個長度：每天簽一次，下班時自然失效。</li>
<li><code>-z 1001</code>：序號，撤銷清單可以用序號指定要作廢的憑證，所以每張憑證給不重複的序號（從某個數字起逐張加一即可），簽發主機要記下每個序號簽給了誰。</li>
<li><code>id_ed25519.pub</code>：要被簽的使用者公鑰。</li>
</ul>
<p>執行之後在公鑰旁邊產生 <code>id_ed25519-cert.pub</code>。這個檔名是固定的對應關係：<code>ssh</code> 用 <code>id_ed25519</code> 這把私鑰時，會自動找同目錄的 <code>id_ed25519-cert.pub</code> 一起送出，所以管理員把憑證放回 <code>~/.ssh/</code> 就能直接登入，不必加任何參數。兩種情形要另外處理：金鑰載入 <code>ssh-agent</code> 時憑證是跟著一起載入的，每天重簽之後 agent 裡仍是舊的那一張，要重新 <code>ssh-add</code>（<code>ssh-add -t 8h</code> 讓 agent 裡的金鑰與憑證同時失效）；<code>ssh_config</code> 用 <code>IdentityFile</code> 指定金鑰而憑證不在同目錄或不同名時，用 <code>CertificateFile</code> 指出憑證檔。用 <code>ssh-keygen -L</code> 讀得出憑證的內容：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 讀出憑證的識別字串、序號、有效期與 principal</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -L -f id_ed25519-cert.pub</span></span></code></pre></div><p>輸出裡要對的是 <code>Key ID</code>、<code>Serial</code>、<code>Valid</code> 與 <code>Principals</code> 四欄；<code>Extensions</code> 欄列出這張憑證允許的功能（<code>permit-pty</code>、<code>permit-port-forwarding</code>、<code>permit-agent-forwarding</code> 這類），預設全開，簽發時用 <code>-O</code>（指定憑證選項）加上 <code>no-agent-forwarding</code>、<code>no-port-forwarding</code> 這類值逐項關掉。需要讓 CI 這類機器用憑證時，同樣用 <code>-O force-command=&lt;指令&gt;</code> 限定它登入之後只能執行一條指令，並用 <code>-O source-address=&lt;cidr&gt;</code> 限定這張憑證只能從 CI 所在的位址範圍使用。</p>
<p>經由<a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">跳板機</a>（<code>ProxyJump</code>）連線時，跳板機與目的主機各自驗證一次，同一張憑證兩段都能用，所以跳板機也信任同一把使用者 CA 時，一張憑證外洩就同時通過兩層。要讓兩層分開，跳板機的 principals 檔用另一個 principal（例如 <code>bastion-access</code>），只簽給需要經過跳板機的人；要分得更徹底就讓跳板機信任另一把 CA。</p>
<p>手動簽發時，簽發的人每天要處理每位管理員的申請，每一次都要先確認申請者的身分，否則任何人寄來一把公鑰都能拿到憑證。組織有 <a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">IdP</a> 時，常見的做法是讓簽發服務接 IdP 登入，管理員登入 IdP 之後由服務自動簽發，這就是 <a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a> 的 SSH secrets engine、smallstep 的 <code>step ssh</code> 與 Teleport 做的事；本篇的手動流程適合人數少、或要先理解機制再導入簽發服務的團隊。</p>
<h2 id="憑證外洩時提前作廢krl-撤銷清單">憑證外洩時提前作廢：KRL 撤銷清單</h2>
<p>憑證到期之前就要失效時（筆電遺失、私鑰外洩、人員在有效期內離職），把它加進撤銷清單（KRL，Key Revocation List）並派送到每一台主機。撤銷清單的正本放在簽發主機上，以下指令都在簽發主機上對正本執行，改完再把整份檔案派送到每台主機的 <code>/etc/ssh/revoked.krl</code>（<code>RevokedKeys</code> 指的位置）。主機上那份空清單是各自產生的，簽發主機上的正本要另外先建一次（<code>ssh-keygen -k -f revoked.krl</code>），否則下面加了 <code>-u</code> 的指令會因為檔案不存在而失敗：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 依憑證檔撤銷：把 alice 那張憑證加進既有的撤銷清單</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">ssh-keygen -k -u -f revoked.krl id_ed25519-cert.pub
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 依序號或識別字串撤銷：手上沒有憑證檔時用規格檔描述要撤銷的憑證</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;serial: 1001&#34;</span> &gt; krl.spec
</span></span><span class="line"><span class="ln">6</span><span class="cl">ssh-keygen -k -u -f revoked.krl -s user_ca.pub krl.spec
</span></span><span class="line"><span class="ln">7</span><span class="cl">
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"># 確認某張憑證是否已在清單裡（輸出 REVOKED 代表已撤銷）</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl">ssh-keygen -Q -f revoked.krl id_ed25519-cert.pub</span></span></code></pre></div><ul>
<li><code>-k</code>：產生或更新 KRL；<code>-u</code> 表示加進既有的清單，不加 <code>-u</code> 會用這次的內容覆蓋整份清單。</li>
<li><code>-f revoked.krl</code>：簽發主機上那份正本的檔名。</li>
<li><code>-s user_ca.pub</code>：用規格檔撤銷時，指定這些序號或識別字串屬於哪一把 CA。給的是公鑰：撤銷清單只記下「這把 CA 簽的哪些序號作廢」，不需要 CA 私鑰簽名，這與簽發時 <code>-s user_ca</code> 給私鑰不同。</li>
<li><code>krl.spec</code>：規格檔，一行一項，<code>serial: 1001</code> 撤銷那個序號，<code>id: alice@example.com</code> 撤銷那個識別字串簽出的所有憑證。</li>
<li><code>-Q</code>：查詢一把金鑰或一張憑證是否在清單裡。</li>
</ul>
<p>sshd 每次驗證都重新讀這份檔案，換掉檔案之後不必重新載入 sshd。撤銷清單的成本在派送：它要送到每一台主機，送不到的那幾台仍然接受被撤銷的憑證，所以派送要交給組態管理工具或 <a href="/blog/devops/infra/knowledge-cards/iac/" data-link-title="Infrastructure as Code (IaC)" data-link-desc="用程式碼描述基礎設施的最終狀態，由工具負責收斂現實與描述的差異">IaC</a> 這類會回報失敗的機制，派送失敗時有人看得到。撤銷擋下的是下一次登入，已經建立的工作階段要另外在主機上結束，用的是 systemd 管理登入工作階段的 <code>loginctl</code>（要 root 權限）：一人一個帳號時用 <code>sudo loginctl terminate-user &lt;username&gt;</code>；多人共用一個帳號（<code>deploy</code>、<code>ubuntu</code>）時 <code>terminate-user</code> 會結束所有人的工作階段，改用 <code>loginctl list-sessions</code> 列出工作階段、對照 sshd 紀錄裡那一行 <code>Accepted publickey</code> 的來源 IP 與時間找出那個人的 <code>&lt;session-id&gt;</code>，再用 <code>sudo loginctl terminate-session &lt;session-id&gt;</code> 只結束它。</p>
<p>有效期短的時候，撤銷清單能爭取到的時間有限：一張八小時的憑證剩下的有效時間最多八小時，派送清單到每台主機若也要花上幾小時，撤銷只比自然到期早一點生效。撤銷清單是給「等不到到期」的情形用的，而 CA 私鑰本身外洩時撤銷清單也救不了，那時要換掉 CA：產生新的 CA、把新公鑰加進每台主機的 <code>TrustedUserCAKeys</code>、同一次就拿掉舊的那一把、重新簽發所有人的憑證。改 <code>TrustedUserCAKeys</code> 要逐台改設定；撤銷清單也收 CA 公鑰本身（<code>ssh-keygen -k -u -f revoked.krl user_ca.pub</code>，這把 CA 簽的所有憑證一起作廢），已經演練過的清單派送路徑能比逐台改設定更早送到，兩者可以一起做。〈設定主機的 sshd〉講的新舊兩把並列一段時間，是有計畫輪替 CA 時的做法；私鑰外洩時舊的那一把每多留一刻，攻擊者就多一刻能簽出有效憑證。</p>
<h2 id="sshd-紀錄裡的憑證資訊">sshd 紀錄裡的憑證資訊</h2>
<p>用憑證登入時，sshd 的認證紀錄會帶著憑證的識別字串、序號與簽發的 CA，這一行才分得出是誰登入。在預設的 <code>LogLevel INFO</code> 下，成功登入的那一行是：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Accepted publickey for deploy from 10.0.1.20 port 53080 ssh2: ED25519-CERT SHA256:eLfd... ID alice@example.com (serial 1001) CA ED25519 SHA256:kSP8...</span></span></code></pre></div><p><code>for deploy</code> 是登入的帳號，<code>ID alice@example.com (serial 1001)</code> 是憑證的識別字串與序號，多人共用 <code>deploy</code> 帳號時靠這一段分出是誰。<code>sshd_config</code> 設 <code>LogLevel VERBOSE</code>（比預設 <code>INFO</code> 多記驗證細節的紀錄等級）時，上面這一行照樣出現，而且在它之前還會多記一行 <code>Accepted certificate ID &quot;alice@example.com&quot; (serial 1001) signed by ED25519 CA ...</code>。</p>
<p>被拒時，紀錄寫出原因：過期是 <code>Certificate invalid: expired</code>；principal 對不上時，有設 <code>AuthorizedPrincipalsFile</code> 是 <code>Certificate does not contain an authorized principal</code>，沒設時是 <code>Certificate invalid: name is not a listed principal</code>；在撤銷清單裡是 <code>Authentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked.krl</code>。這幾種情形用戶端看到的都只是 <code>Permission denied (publickey)</code>，原因只在主機的紀錄裡（Debian 與 Ubuntu 用 <code>journalctl -u ssh</code>，RHEL 系用 <code>journalctl -u sshd</code>）。</p>
<h2 id="主機憑證簽發流程hostcertificate-與-cert-authority">主機憑證：簽發流程、HostCertificate 與 @cert-authority</h2>
<p>第一次連到一台主機時，<code>ssh</code> 會問要不要信任它的指紋；主機重建（autoscaling、重新部署）之後 host key 換了，<code>ssh</code> 會印出 <code>WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!</code> 並拒絕連線。主機經常重建的環境裡，管理員會被這個警告訓練成直接刪掉舊指紋、接受新的，而那正是中間人攻擊需要的反應。主機憑證讓用戶端改成信任主機 CA，帶著有效主機憑證的主機直接通過，不再逐台確認指紋。</p>
<p>主機 CA 的私鑰在簽發主機上，所以流程是把目標主機的 host key 公鑰取回簽發主機、在那裡簽、再把簽出的憑證放回目標主機：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># 在簽發主機上執行：從 web-01 取回 host key 的公鑰（公鑰不是機密）</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">scp web-01:/etc/ssh/ssh_host_ed25519_key.pub ./web-01_host_ed25519_key.pub
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># 在簽發主機上執行：對取回的公鑰簽發主機憑證，-h 表示這是主機憑證</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">ssh-keygen -s host_ca -I <span class="s2">&#34;web-01&#34;</span> -h -n web-01.example.com -V +52w web-01_host_ed25519_key.pub
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"># 在簽發主機上執行：把簽出的憑證送回 web-01</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">scp web-01_host_ed25519_key-cert.pub web-01:/tmp/
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># 在 web-01 上執行：把憑證放到 host key 旁邊</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">sudo install -m <span class="m">644</span> /tmp/web-01_host_ed25519_key-cert.pub /etc/ssh/ssh_host_ed25519_key-cert.pub</span></span></code></pre></div><p>兩次 <code>scp</code> 都由簽發主機發起：簽發主機要能連到目標主機，而目標主機不需要能登入簽發主機，簽發主機因此可以維持只有少數人能登入。</p>
<ul>
<li><code>-h</code>：簽的是主機憑證而不是使用者憑證。</li>
<li><code>-I &quot;web-01&quot;</code>：主機的識別字串，填主機名稱。</li>
<li><code>-n web-01.example.com</code>：用戶端連線時會用到的名稱，逗號分隔可以列多個（用 IP 連線時也列 IP）。用戶端連線時用的名稱不在這份 <code>-n</code> 清單裡，用戶端就不接受這張憑證，用戶端的 <code>ssh -v</code> 輸出裡是 <code>Certificate invalid: name is not a listed principal</code>。</li>
<li><code>-V +52w</code>：主機憑證的有效期通常比使用者憑證長（主機不會離職），但仍要在到期前重新簽發，否則到期那天所有用戶端都會連不上這台主機。</li>
<li><code>web-01</code>：目標主機的位址。<code>web-01_host_ed25519_key.pub</code>：取回之後在簽發主機上的檔名，自己取，帶主機名稱以免混淆；來源路徑以目標主機上實際存在的 <code>/etc/ssh/ssh_host_*_key.pub</code> 為準。</li>
</ul>
<p>憑證放到 <code>/etc/ssh/ssh_host_ed25519_key-cert.pub</code> 之後，在 web-01 的 <code>sshd_config</code> 加一行 <code>HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub</code>，同樣先 <code>sudo sshd -t</code> 檢查語法再重新載入。用戶端在 <code>~/.ssh/known_hosts</code>（用戶端記錄已信任主機的檔案；全機共用的是 <code>/etc/ssh/ssh_known_hosts</code>）加一行：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 在用戶端執行：信任 host_ca 簽給 *.example.com 的主機憑證（host_ca.pub 從簽發主機複製過來）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;@cert-authority *.example.com </span><span class="k">$(</span>cat host_ca.pub<span class="k">)</span><span class="s2">&#34;</span> &gt;&gt; ~/.ssh/known_hosts</span></span></code></pre></div><p>寫進去的那一行是 <code>@cert-authority *.example.com ssh-ed25519 AAAAC3Nza... host-ca</code>：樣式後面接的就是 <code>host_ca.pub</code> 整個檔案的內容。</p>
<p><code>@cert-authority</code> 後面的樣式限定這把 CA 只對哪些主機名稱有效，填組織自己的網域；樣式寫成 <code>*</code>（符合任何主機名稱）時，拿到這把 CA 私鑰的人簽出的憑證，不論掛著哪個主機名稱（包括別的組織的網域），用戶端都會接受。這個樣式比對的是用戶端連線時用的名稱，所以一次連線要通過兩道比對：那個名稱符合 <code>@cert-authority</code> 的樣式，而且列在主機憑證的 <code>-n</code> 裡。用 IP 連線時兩處都要涵蓋那個 IP；樣式只寫網域時，用戶端不採信這把 CA、退回逐台確認指紋。主機憑證的撤銷在用戶端：主機退役或 host key 外洩時，用戶端的 <code>ssh_config</code> 以 <code>RevokedHostKeys &lt;krl-file&gt;</code> 指向一份列了那張主機憑證或 host key 的撤銷清單（產生方式與使用者憑證相同），或在 <code>known_hosts</code> 用 <code>@revoked</code> 開頭的一行標記那把 host key；派送的對象是用戶端，而不是主機。主機憑證最適合交給建立主機的流程：映像檔或開機流程產生 host key 之後，由簽發服務自動簽發，主機重建時憑證跟著重簽。</p>
<h2 id="導入順序ca主機設定憑證與-authorized_keys-之間的依賴">導入順序：CA、主機設定、憑證與 authorized_keys 之間的依賴</h2>
<p>上面各節的設定有先後依賴，而導入期間管理員仍要天天登入主機，所以順序的原則是新的登入路徑驗證過之前，舊的那一條一直保留。</p>
<p>CA 與主機設定之間的依賴最直接：主機要信任的是 CA 公鑰，所以 CA 先產生、私鑰先放進簽發主機或硬體安全模組，才有公鑰可以派送。主機那一側加 <code>TrustedUserCAKeys</code>、<code>AuthorizedPrincipalsFile</code>、<code>RevokedKeys</code> 三行之前，先把使用者 CA 公鑰、principals 檔與一份空的撤銷清單放到位：三行設定指向的檔案只要有一個不在，〈設定主機的 sshd：信任使用者 CA〉講的兩個陷阱就會在重新載入的那一刻讓所有人登不進去。這時 <code>authorized_keys</code> 仍然有效，憑證是多出來的一條路。</p>
<p>憑證與 <code>authorized_keys</code> 之間的依賴決定什麼時候能拿掉舊路徑：每位管理員都用憑證登入過每一類主機、並在 sshd 紀錄裡看到自己那一行帶著 <code>ID &lt;自己的識別字串&gt;</code> 的 <code>Accepted publickey</code> 之後，<code>authorized_keys</code> 才可以停用。停用的做法是在 <code>sshd_config</code> 加 <code>AuthorizedKeysFile none</code>（先 <code>sshd -t</code> 再重新載入），而不只是清空檔案：只清空檔案時，任何登入過的帳號都能把自己的公鑰寫回 <code>~/.ssh/authorized_keys</code>，這把金鑰不受憑證有效期與撤銷清單約束，收回延遲有上限的前提就不成立。撤銷清單的派送也要在停用之前接上組態管理工具並實際演練一次——撤銷一張測試憑證、確認每台主機都拒絕它——因為停用 <code>authorized_keys</code> 之後，收回一個人的權限只剩兩個手段：不再替他簽發、等手上的憑證到期，以及把他的憑證加進撤銷清單，而撤銷清單要靠演練過的派送才送得到每台主機。救援用的本機帳號（給 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console</a> 這類救援入口用、不經過 sshd 金鑰登入的帳號）不跟著清掉，它的密碼與保管方式另外管理。</p>
<p>主機憑證與使用者憑證之間沒有依賴，可以獨立導入；它的前提是用戶端的 <code>known_hosts</code> 已經加了 <code>@cert-authority</code> 那一行，否則用戶端仍然逐台確認指紋，主機憑證放上去也不會被用到。</p>
<h2 id="延伸閱讀簽發服務知識卡與-gcp-上的對應做法">延伸閱讀：簽發服務、知識卡與 GCP 上的對應做法</h2>
<ul>
<li>→ <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>：SSH 憑證與靜態金鑰、OS Login 三種金鑰發放方式的對照</li>
<li>→ <a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a>：接 IdP 登入、自動簽發 SSH 憑證的簽發服務，也是保管 CA 私鑰的位置之一</li>
<li>→ <a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e4%b8%bb%e6%a9%9f%e7%99%bb%e5%85%a5%e9%a9%97%e8%ad%89%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90%e7%9a%84%e6%94%b6%e5%9b%9e%e9%9d%a0%e4%ba%ba%e5%b7%a5%e5%88%aa%e9%99%a4%e5%b8%b3%e8%99%9f%e9%80%a3%e5%8b%95%e6%88%96%e5%88%b0%e6%9c%9f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>：SSH 憑證適合的情境（主機跨多個環境、進不了客戶帳號系統的外包團隊），以及它與跳板機、身分感知代理怎麼組合</li>
<li>→ <a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道</a>：主機全在 GCP 時改用 OS Login 讓登入權限跟著 Google 帳號走的做法</li>
</ul>
]]></content:encoded></item><item><title>建立主機之前的檢查：建好就改不了的設定、登入路徑上各服務的支援組合、兩條登入路徑各自依賴的元件，以及容量稀缺時的取捨</title><link>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/</link><pubDate>Sun, 25 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/pre-launch-checks/</guid><description>&lt;p>雲端主機建立頁上的設定，有一部分建立之後只能重建才改得了，而登入要經過的業者服務各自只在一部分機型與作業系統上成立。這一篇把這兩類條件整理成按下建立之前要做的檢查：建好就改不了的設定，以及怎麼從業者的文件與工具查出來；登入路徑上每一個業者服務支援的機型與作業系統，以及元件狀態顯示正常為什麼不能當成支援的證據；兩條登入路徑各自依賴哪些元件、哪些元件仍然共用、不經過 sshd 的救援入口，以及停用其他入口之前要先確認什麼；機型容量難取得時，設定為什麼要選補救代價低的那一種；以及 Oracle Cloud、Google Cloud 與 AWS 查這些事的位置。各項設定本身的機制在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM&lt;/a>；一次沒做這些檢查的實測經過（2026-10-25 在 Oracle Cloud 的 &lt;code>ap-batam-1&lt;/code> 建了一台 A1 Ubuntu 主機，建好之後登不進去；之後改用 Oracle Linux 並放公鑰重建一台，經由 port forwarding session 登入）在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄&lt;/a>。&lt;/p>
&lt;h2 id="建好就改不了的設定補救的代價與查法">建好就改不了的設定：補救的代價與查法&lt;/h2>
&lt;p>建立頁上的設定依建立之後的處置分成三種：可以直接改（Oracle Cloud 的 plugin 開關、磁碟的效能等級）、改的時候要停機或重開（核心數與記憶體）、只能重建（CPU 架構與作業系統）。只能重建的設定，代價出現在發現選錯的那一刻：重建主機會換掉臨時公開 IP（私有 IP 沒有在建立時指定也會換）、開機磁碟上的資料要另外搬，機型容量難取得時還要再搶一次容量。所以建立之前要逐項找出只能重建的設定，替每一項寫出選錯時的補救路徑與代價。&lt;/p>
&lt;p>同一項設定在三家的處置不同，所以這份清單要對著業者查，不能從另一家的經驗推過來。以 SSH 公鑰為例：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Oracle Cloud&lt;/strong>：公鑰與初始化腳本存在主機的中繼資料裡，&lt;code>oci compute instance update --help&lt;/code> 對 &lt;code>--metadata&lt;/code> 的說明寫明 &lt;code>ssh_authorized_keys&lt;/code> 與 &lt;code>user_data&lt;/code> 在主機建立之後不能改，送出修改的請求會被拒絕。cloud-init 只在第一次開機時把公鑰寫進 &lt;code>authorized_keys&lt;/code>，所以建立時選了不放金鑰，之後就沒有經由中繼資料補公鑰的方式；剩下的入口（代理程式臨時放公鑰、Run Command、把開機磁碟掛到另一台主機）各有前提，見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。&lt;/li>
&lt;li>&lt;strong>Google Cloud&lt;/strong>：公鑰存在專案或主機的中繼資料裡，建立之後可以再加。Google 的映像預先裝了 guest environment（業者裝在主機裡的一組程式），其中的 guest agent 持續依中繼資料同步金鑰，加進去的公鑰會寫進主機。&lt;/li>
&lt;li>&lt;strong>AWS&lt;/strong>：EC2 Instance Connect 每次連線前把一把公鑰暫時推送到主機的中繼資料，不依賴建立時選的 key pair。條件是主機上裝了 EC2 Instance Connect 的套件、登入者有對應的 IAM 權限，以及連線到得了主機：官方〈Prerequisites for EC2 Instance Connect〉寫明主機要有公開 IP，或經由 Direct Connect、Site-to-Site VPN、VPC peering 這類私有連線，沒有公開 IP 時另一個選擇是 EC2 Instance Connect Endpoint。&lt;/li>
&lt;/ul>
&lt;p>其他類設定的處置寫在各自的那一篇：架構與映像檔見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統&lt;/a>（換作業系統要用新的映像檔重建；Oracle Cloud 可以替換開機磁碟，官方〈Replacing a Boot Volume〉限定只能換成同一個 Linux 發行版，Ubuntu 換不成 Oracle Linux），主要網卡所在的 subnet 與公開 IP 的種類見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則&lt;/a>，Oracle Cloud 的安全開機見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM&lt;/a>，區域與可用區見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>雲端主機建立頁上的設定，有一部分建立之後只能重建才改得了，而登入要經過的業者服務各自只在一部分機型與作業系統上成立。這一篇把這兩類條件整理成按下建立之前要做的檢查：建好就改不了的設定，以及怎麼從業者的文件與工具查出來；登入路徑上每一個業者服務支援的機型與作業系統，以及元件狀態顯示正常為什麼不能當成支援的證據；兩條登入路徑各自依賴哪些元件、哪些元件仍然共用、不經過 sshd 的救援入口，以及停用其他入口之前要先確認什麼；機型容量難取得時，設定為什麼要選補救代價低的那一種；以及 Oracle Cloud、Google Cloud 與 AWS 查這些事的位置。各項設定本身的機制在 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>、<a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a>、<a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a>、<a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 與 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a>；一次沒做這些檢查的實測經過（2026-10-25 在 Oracle Cloud 的 <code>ap-batam-1</code> 建了一台 A1 Ubuntu 主機，建好之後登不進去；之後改用 Oracle Linux 並放公鑰重建一台，經由 port forwarding session 登入）在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a>。</p>
<h2 id="建好就改不了的設定補救的代價與查法">建好就改不了的設定：補救的代價與查法</h2>
<p>建立頁上的設定依建立之後的處置分成三種：可以直接改（Oracle Cloud 的 plugin 開關、磁碟的效能等級）、改的時候要停機或重開（核心數與記憶體）、只能重建（CPU 架構與作業系統）。只能重建的設定，代價出現在發現選錯的那一刻：重建主機會換掉臨時公開 IP（私有 IP 沒有在建立時指定也會換）、開機磁碟上的資料要另外搬，機型容量難取得時還要再搶一次容量。所以建立之前要逐項找出只能重建的設定，替每一項寫出選錯時的補救路徑與代價。</p>
<p>同一項設定在三家的處置不同，所以這份清單要對著業者查，不能從另一家的經驗推過來。以 SSH 公鑰為例：</p>
<ul>
<li><strong>Oracle Cloud</strong>：公鑰與初始化腳本存在主機的中繼資料裡，<code>oci compute instance update --help</code> 對 <code>--metadata</code> 的說明寫明 <code>ssh_authorized_keys</code> 與 <code>user_data</code> 在主機建立之後不能改，送出修改的請求會被拒絕。cloud-init 只在第一次開機時把公鑰寫進 <code>authorized_keys</code>，所以建立時選了不放金鑰，之後就沒有經由中繼資料補公鑰的方式；剩下的入口（代理程式臨時放公鑰、Run Command、把開機磁碟掛到另一台主機）各有前提，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉。</li>
<li><strong>Google Cloud</strong>：公鑰存在專案或主機的中繼資料裡，建立之後可以再加。Google 的映像預先裝了 guest environment（業者裝在主機裡的一組程式），其中的 guest agent 持續依中繼資料同步金鑰，加進去的公鑰會寫進主機。</li>
<li><strong>AWS</strong>：EC2 Instance Connect 每次連線前把一把公鑰暫時推送到主機的中繼資料，不依賴建立時選的 key pair。條件是主機上裝了 EC2 Instance Connect 的套件、登入者有對應的 IAM 權限，以及連線到得了主機：官方〈Prerequisites for EC2 Instance Connect〉寫明主機要有公開 IP，或經由 Direct Connect、Site-to-Site VPN、VPC peering 這類私有連線，沒有公開 IP 時另一個選擇是 EC2 Instance Connect Endpoint。</li>
</ul>
<p>其他類設定的處置寫在各自的那一篇：架構與映像檔見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a>（換作業系統要用新的映像檔重建；Oracle Cloud 可以替換開機磁碟，官方〈Replacing a Boot Volume〉限定只能換成同一個 Linux 發行版，Ubuntu 換不成 Oracle Linux），主要網卡所在的 subnet 與公開 IP 的種類見 <a href="/blog/devops/infra/cloud-providers/vm-options/networking/" data-link-title="網路：subnet、私有 IP、公開 IP 與防火牆規則" data-link-desc="雲端主機建立頁網路區塊的選項：主機放進哪個虛擬網路與 subnet、私有 IP 與公開 IP 各自的用途與生命週期、防火牆規則掛在 subnet、網卡與作業系統哪一個位置以及各家怎麼合併，以及主控台新建的網路預設對外開放了什麼">網路：subnet、私有 IP、公開 IP 與防火牆規則</a>，Oracle Cloud 的安全開機見 <a href="/blog/devops/infra/cloud-providers/vm-options/shielded-boot/" data-link-title="安全開機：Secure Boot、Measured Boot 與 TPM" data-link-desc="雲端主機的開機保護選項：Secure Boot 檢查什麼、Measured Boot 與 TPM 記錄什麼、三者在 Oracle Cloud 上建立後不能修改的限制、與機密運算互斥、維護時不能遷移，以及實驗主機要不要開">安全開機：Secure Boot、Measured Boot 與 TPM</a>，區域與可用區見 <a href="/blog/devops/infra/cloud-providers/vm-options/placement/" data-link-title="位置：區域、可用區與故障域" data-link-desc="雲端主機的區域、可用區與故障域各自代表多大的範圍、各自決定主機的哪些性質（延遲、出口 IP 的國家、可用機型與容量、硬體故障的影響範圍），以及哪一層建立後就不能改">位置：區域、可用區與故障域</a>。</p>
<p>哪些設定只能重建，業者自己的工具也查得到。修改主機的 API 與 CLI 說明會寫哪些欄位不能改（上面 Oracle Cloud 那一條就是從 <code>--help</code> 讀到的）；用 Terraform 管理主機時，<code>terraform plan</code> 把只能重建的屬性標成 forces replacement，在 plan 上改一次那個屬性，就看得到它屬於哪一種（見 <a href="/blog/devops/infra/knowledge-cards/terraform-plan-apply/" data-link-title="terraform plan / apply" data-link-desc="IaC 的兩個核心操作：plan 只看不動（產出差異報告）、apply 真的動（執行差異）">terraform plan / apply</a>）。</p>
<h2 id="登入路徑上各服務的支援組合支援清單known-issues-與元件狀態">登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態</h2>
<p>登入一台沒有公開 IP 的主機，連線要經過幾個業者服務：哪個服務負責網路可達性、通道授權與主機登入驗證，見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。每一個服務都只在一部分機型與作業系統上成立，而這份範圍寫在服務自己的文件裡，不寫在建立頁上：建立頁讓任何映像檔配任何相容架構的機型，建立會成功，登入在之後才失敗。</p>
<p>所以建立之前，對路徑上每一個服務讀下面三種文件，業者另外提供查詢工具時再查一次：</p>
<ul>
<li><strong>支援的作業系統與映像檔清單</strong>：例如 Oracle Cloud 的 Run Command 列出的是 Oracle Autonomous Linux、Oracle Linux、CentOS、Windows Server 與以它們為基礎的自訂映像，沒有 Ubuntu。不在清單上代表官方不保證，不代表一定不能執行：2026-10-25 在一台 A1 Ubuntu 主機上實測，Run Command 的 plugin 確實執行了指令，而能做到哪些事還要看下面的前置條件。AWS 的 EC2 Instance Connect 要主機用預先裝好它的 AMI，或在官方列為支援的 AMI 上自己安裝。</li>
<li><strong>Known Issues</strong>：支援清單寫的是作業系統，機型與作業系統的組合不支援的情形常常只寫在這一頁。Oracle Cloud〈Known Issues for Bastion〉的第一條是 Arm 架構的 A1 機型加非 Oracle Linux 的主機不支援 managed SSH session；這個組合在建立頁上建立會成功，A1 是 Always Free 的機型、Ubuntu 是建立頁列出的平台映像，兩者放在一起時 managed SSH session 才不支援。這一條只寫到作業系統，沒有寫版本。</li>
<li><strong>可以查詢的支援清單</strong>：文件寫到作業系統為止時，作業系統的版本之間仍然可能不同，而有些業者提供依版本查詢的工具。Oracle Cloud 的 <code>oci instance-agent available-plugins get</code> 依作業系統名稱與版本列出代理程式可用的 plugin：2026-10-25 查到 Oracle Linux 10 確定沒有 Bastion plugin，所以同樣是 A1 加 Oracle Linux，選 10 的主機少了 managed SSH session 需要的 plugin（從 plugin 清單推論，沒有在那台主機上實際建工作階段）。這類工具查不到對應時可能回傳不分版本的全部項目而不是報錯：這支工具填不存在的版本或作業系統名稱，回傳的都是同一份全部清單，裡面有 Bastion。所以版本要照抄映像檔的作業系統版本欄位，先拿一個不存在的值查一次當對照；查到的清單跟對照不同才算查到這個版本的資料，跟對照相同時分不出來，這次的 Oracle Linux 9 與 8 就是這樣，要建起來看主機上的 plugin 清單，或另找逐版本列出的文件。查法與重建的經過在 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈建立之前查映像檔版本與它可用的 plugin〉。</li>
<li><strong>前置條件</strong>：服務在主機上用哪個使用者執行、要什麼權限。Oracle Cloud 的 Run Command 以 <code>ocarun</code> 這個使用者執行，要寫其他使用者的檔案得先替它設 sudo；官方列的做法有兩種，建立時用 cloud-init 放 sudoers 檔，或建好之後登入主機設定。主機登不進去時才想用 Run Command 救援，只剩前一種，而初始化腳本是建立後不能改的欄位，所以這件事要在建立之前決定。AWS 的 Session Manager 要主機上有 SSM Agent，並讓 Systems Manager 取得操作這台主機的權限。</li>
</ul>
<p>元件的狀態欄顯示的是元件啟動了，這個組合能不能完成登入，寫在支援清單與 Known Issues 裡，兩者描述的是不同的事。Oracle Cloud 的主機頁面上，A1 Ubuntu 主機的 Bastion plugin 顯示 <code>RUNNING</code>，Bastion 的工作階段照樣在約 15 分鐘後逾時。缺了前置條件時，狀態欄也一樣顯示正常：同一台主機的 Run Command plugin 也是 <code>RUNNING</code>，指令確實執行了，而 <code>ocarun</code> 沒有 sudo，寫不了 <code>authorized_keys</code>。</p>
<p>演練能驗到的，只有演練主機與正式主機相同的那些條件。演練主機與正式主機在機型、映像檔、服務三者都相同時，演練才驗得到組合不支援的情形；上面那一條 Known Issue 只針對 A1，在 x86 機型上用同一版 Ubuntu 的 x86 映像演練，照 Known Issues 的寫法不在不支援的範圍裡（這個推論沒有實跑），演練成功只代表 x86 加 Ubuntu 這個組合支援。機型本身就是要驗的條件時，演練換不了機型，只剩讀文件這一條路。</p>
<h2 id="兩條登入路徑各自依賴的元件與仍然共用的部分">兩條登入路徑：各自依賴的元件與仍然共用的部分</h2>
<p>登入路徑上的每一個元件都可能失效：代理程式沒有啟動、服務不支援這個組合、權限漏給、出站路徑被收緊、sshd 的設定改壞。只有一條路徑時，那條路徑上任何一個元件失效，主機就沒有入口；這個代價在重建昂貴時才成立（主機上有狀態、機型容量難取得、第一次用這個機型加作業系統的組合）。主機不存狀態、重建便宜時，重建本身就是那一條備用路徑，見 <a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口</a> 的〈不可變基礎設施下的臨時授權：通道平時關閉〉。備用路徑能不能當退路，看它跟原本那條路徑共用哪些元件：共用的元件失效時兩條一起斷，例如 Oracle Cloud 的 managed SSH session 與 Run Command 都經由主機上的 Oracle Cloud Agent，代理程式停掉時兩者一起失效。所以要把兩條各自依賴的元件逐項列出來，確認建立之前最沒有把握的那一項（這次的機型加作業系統是否在支援範圍、代理程式會不會正常運作）只出現在其中一條上。</p>
<p>列的時候要把整條路徑走完，不只看名字裡的服務。Oracle Cloud 的 managed SSH session 名字裡只有 Bastion，而 plugin 做的是把公鑰臨時放進 <code>authorized_keys</code>，登入照樣經過主機的 sshd；官方〈Managed SSH Session Creation Failed〉把 sshd 設定與 <code>authorized_keys</code> 列在成因裡。三家常見的組法與它們實際依賴的元件：</p>
<ul>
<li><strong>Oracle Cloud</strong>：managed SSH session 依賴 Bastion plugin、機型與作業系統在支援範圍、代理程式連到服務端點的出站路徑；SSH port forwarding session 不經過 plugin，依賴建立時放進中繼資料、由 cloud-init 寫進 <code>authorized_keys</code> 的公鑰。兩條仍然共用 Bastion 服務與它允許連入的來源位址、建工作階段的權限、security list 放行 22 port 的規則、主機的 sshd。所以這一組擋得住 plugin 與支援範圍出錯，擋不住 sshd 改壞或 Bastion 允許的來源位址過期。</li>
<li><strong>Google Cloud</strong>：IAP TCP forwarding 加 OS Login 依賴 IAM 角色、防火牆放行 IAP 的來源位址 <code>35.235.240.0/20</code>、guest environment 處理的 OS Login，以及主機的 sshd。OS Login 啟用之後主機忽略中繼資料裡的公鑰，OS Login 開著時同一台主機上沒有另一條不經過 OS Login 的 SSH 路徑；對單台主機把中繼資料設成 <code>enable-oslogin=FALSE</code> 會恢復 guest agent 同步公鑰的路徑（主機層級的設定蓋過專案層級），組織政策強制 OS Login 時這條用不了，而且它仍然共用 IAP、防火牆、guest agent 與 sshd，算不上獨立的備用路徑。所以常備的備用路徑是序列主控台（serial console）加事先建好的救援帳號：它經由 Google 的序列埠閘道連線，用中繼資料裡的 SSH 公鑰驗證連線者，不經過主機自己的網路設定與 sshd，在登入提示用救援帳號的密碼登入。</li>
<li><strong>AWS</strong>：Systems Manager Session Manager 依賴 SSM Agent、Systems Manager 操作這台主機的權限與出站路徑，不經過 sshd；EC2 Instance Connect 或建立時選的 key pair 依賴 sshd、security group 放行 22 port，以及連得到主機的網路（公開 IP、私有連線或 EC2 Instance Connect Endpoint），EC2 Instance Connect 另外要登入者的 IAM 權限。這兩條共用的元件最少。官方另有 EC2 Serial Console，支援 Nitro 系統上的主機，要先完成存取設定、主機裡要有設了密碼的帳號。</li>
</ul>
<p>三家都有一條不經過 sshd 的救援入口：序列主控台加設了密碼的救援帳號。這個帳號要在主機還登得進去時建，或建立時用初始化腳本建；Oracle Cloud 與 AWS 的平台映像，預設使用者只用金鑰登入、沒有密碼。救援帳號怎麼建、序列主控台平時為什麼關著，見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> 卡。</p>
<p>兩條路徑都要在停用公開 IP 的 SSH 這類其他入口之前，各自實際登入過一次。只寫在設定裡、沒登入過的路徑，登入時才會發現少了哪一項條件；這跟切換管理通道時救援入口要先準備好並登入過是同一件事，見 <a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口</a> 的〈救援入口：管理通道斷掉時的備用路徑〉。</p>
<p>不放公鑰減少了主機裡長期有效的金鑰，代價是在 Oracle Cloud 上拿掉了不經過代理程式的那一條路徑。放在私有 subnet、沒有公開 IP 的主機，網路可達性這一層已經擋住外部的連線，用得了這把公鑰的只剩持有私鑰、而且有建工作階段的權限或已經在同一個 subnet 裡的人，增加的暴露很小，換到的是代理程式失效時的入口；代價是這把金鑰要管理、人員離開時要收回，而在 Oracle Cloud 上，中繼資料裡的公鑰建立後不能換。同時滿足「主機裡沒有長期有效的使用者公鑰」與「不依賴代理程式」的做法是 SSH 憑證：建立時用初始化腳本放 CA 的公鑰，登入時用 CA 簽的短效憑證，代價是要維運一個 CA；在 Oracle Cloud 上 CA 的公鑰同樣經由建立後不能改的初始化腳本放入，換 CA 要登入主機改或重建，做法見 <a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口</a> 的〈主機登入驗證的做法：SSH 金鑰怎麼發、怎麼收回〉。所以第一次在某個機型加作業系統的組合上建主機時，放一把自己的公鑰。這把公鑰之後要不要從 <code>authorized_keys</code> 拿掉，取決於另一條不共用代理程式的入口在不在：拿掉之後只剩代理程式那一條，回到單一路徑；先建好序列主控台的救援帳號並登入過，拿掉公鑰時才仍然有兩條，而救援帳號的密碼同樣是長期有效的憑證，要逐台輪替（見 Serial Console 卡的〈設計責任〉）（Oracle Cloud 上的做法與前提見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉最後一段）。</p>
<h2 id="容量稀缺時設定的取捨補救代價與最嚴格設定的驗證位置">容量稀缺時設定的取捨：補救代價與最嚴格設定的驗證位置</h2>
<p>機型的容量難取得時（免費額度裡的熱門機型、GPU 機型），建好就改不了的設定選錯的代價，除了重建，還多了一次重新取得容量。Oracle Cloud 的 Always Free A1 在 <code>ap-batam-1</code> 實測時，同一個帳號 2026-10-24 整天的建立請求都回容量不足，2026-10-25 用重送腳本送了 16 次才建成（重送的做法與限流的量測見 <a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/launch-instance-walkthrough/" data-link-title="Oracle Cloud 建立主機的實測紀錄：建立前的檢查、前置網路、主控台各分頁的 Always Free 設定值、等效的 CLI 指令、容量不足的重送、Bastion 工作階段逾時、Run Command 補公鑰的嘗試與重建後經由 port forwarding 登入" data-link-desc="要在 Oracle Cloud 建一台沒有公開 IP、經由 Bastion 登入的 Always Free A1 主機，想照一次實測重做、看建立頁每一欄的取值與換一種做法的結果時讀；也用在建立請求一直回容量不足、managed SSH session 建立逾時，或沒放公鑰的主機想用 Run Command 救援、Oracle Linux 10 的主機上找不到 Bastion plugin、想改用 port forwarding session 登入的時候">Oracle Cloud 建立主機的實測紀錄</a> 的〈重送建立請求：CLI 自己的重送、限流門檻與重送腳本〉）。</p>
<p>這時的取捨是選補救代價低的設定：作業系統與版本選登入服務完整支援的那一個、建立時放一把之後可以移除的公鑰、兩條登入路徑都備齊。最嚴格的設定（不放任何金鑰、只靠代理程式）要拆成兩部分驗：跟機型無關的那一部分（代理程式的設定、登入權限、防火牆規則）可以先在容易重建的機型上測；跟機型有關的支援範圍，照〈登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態〉讀文件對這次的機型確認，演練驗不到這一部分。必須在某個時間點拿到容量的情境，可以改用預留容量（capacity reservation），見 <a href="/blog/devops/infra/cloud-providers/vm-options/shape-and-image/" data-link-title="機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統" data-link-desc="雲端主機的機型決定的 CPU 架構、核心與記憶體，計費模式（隨用隨付、可被收回、預留容量、專屬主機）各自的條件，映像檔與機型之間的架構相容性，以及哪些項目建立後可以調整">機型與映像檔：CPU 架構、核心與記憶體、計費模式與作業系統</a> 的〈計費模式：隨用隨付、可被收回、預留容量與專屬主機〉。</p>
<p>搶容量的重送腳本把建立參數寫死在腳本裡，所以腳本送出的設定，是寫腳本那一刻的判斷。從寫好腳本到建成主機可能隔了好幾個小時，這段時間裡查到的新資訊（Known Issues 的一條、某個服務的前置條件）不會自己進到腳本裡；腳本裡的參數要在兩個時間點再對過這一篇的檢查（建好就改不了的設定、各服務的支援組合、兩條登入路徑各自依賴的元件）：重新啟動腳本的那一刻，以及腳本還在跑時查到會改變建立參數的新資訊的那一刻（這時先停掉腳本，改完參數再啟動）。一開始就選補救代價低的參數時，判斷過期的代價也跟著變小。</p>
<h2 id="各家查建立後不能改的欄位登入服務支援組合與代理程式狀態的位置">各家查建立後不能改的欄位、登入服務支援組合與代理程式狀態的位置</h2>
<table>
  <thead>
      <tr>
          <th>要查的事</th>
          <th>Oracle Cloud</th>
          <th>Google Cloud</th>
          <th>AWS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>哪些欄位建立後不能改</td>
          <td><code>oci compute instance update --help</code> 各欄位的說明；〈Replacing a Boot Volume〉的限制</td>
          <td>Compute Engine 文件修改各項屬性的頁面</td>
          <td>EC2 文件修改主機屬性的頁面與 <code>aws ec2 modify-instance-attribute help</code></td>
      </tr>
      <tr>
          <td>登入服務支援的組合</td>
          <td>Bastion 的〈Known Issues for Bastion〉；Run Command 說明頁的支援映像清單；依作業系統版本查可用 plugin 的 <code>oci instance-agent available-plugins get</code></td>
          <td>OS Login 與 IAP TCP forwarding 的說明頁</td>
          <td>EC2 Instance Connect 的〈Prerequisites〉；Session Manager 的前置條件</td>
      </tr>
      <tr>
          <td>代理程式與 plugin 的執行狀態</td>
          <td>主機頁面的 Oracle Cloud Agent 分頁，或 <code>oci instance-agent plugin list</code></td>
          <td><code>get-serial-port-output</code> 裡 guest agent 的啟動</td>
          <td>Systems Manager 的 Fleet Manager 列出的受管主機</td>
      </tr>
  </tbody>
</table>
<p>「代理程式與 plugin 的執行狀態」那一列查到的是元件有沒有啟動；〈登入路徑上各服務的支援組合：支援清單、Known Issues 與元件狀態〉那一節的支援清單與 Known Issues，仍然要另外讀。設定也不等於實際：Oracle Cloud 主機的 <code>agent-config</code> 寫著 Bastion 是 <code>ENABLED</code> 只是期望的狀態，<code>plugin list</code> 裡根本沒有 Bastion 這一項時，代表這個映像版本不提供這個 plugin（2026-10-25 的 Oracle Linux 10.2 主機就是這樣）。</p>
]]></content:encoded></item><item><title>Identity-Aware Proxy（身分感知代理）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/</guid><description>&lt;p>身分感知代理（identity-aware proxy）是一個放在網際網路與內部主機之間的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy&lt;/a>：它接收外部連線，先向組織的 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> 確認發起連線的帳號、再依授權規則決定是否替這條連線開到後端。通過的連線送到後端的方式有兩種：由代理從它自己的固定位址段連到主機的內部 IP（GCP 的 IAP），這時主機的防火牆只放行那段代理位址；或經由主機上的代理程式（connector，例如 &lt;code>cloudflared&lt;/code>）主動往外建立的反向連線送進去（Cloudflare Tunnel、Teleport agent），這時主機不開任何入站 port。兩種方式下主機都不必持有公開 IP。它是 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/zero-trust/" data-link-title="Zero Trust（零信任）" data-link-desc="判斷內網或 VPN 裡的服務能不能因為連線來自內部就省掉認證、或要評估一條連線該放行到什麼範圍時，查「每一次連線各自通過身分與授權」這個原則的內容與導入代價">Zero Trust（零信任）&lt;/a> 原則在存取入口上的一種實作：放行的依據是每一次連線的身分，網路位置不構成信任的理由。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>管理通道拆成網路可達性、通道授權、主機登入驗證三層來看時（模型見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），身分感知代理同時承擔網路可達性與通道授權：sshd 只對代理可見，而能不能開通道由代理用 IdP 帳號判斷——這個判斷就是通道授權，指的是決定「這個人能不能打開通往主機的連線」，與登入主機時驗證 SSH 金鑰是兩回事。主機登入驗證在多數實作裡仍由主機上的 sshd 負責；Teleport 的節點這類實作用自己內建的 SSH 伺服器與代理簽發的憑證，主機登入驗證跟著換掉。由 sshd 負責時，金鑰的發放方式另見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a>。&lt;/p>
&lt;p>它與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> 都讓內部主機不必各自對外，差別在對外接收連線的元件由誰維運、用什麼判斷身分：跳板機是自己維運的主機、用 SSH 金鑰判斷；身分感知代理是廠商提供或自己架設的代理服務、用 IdP 帳號判斷。它與一般反向代理的差別在轉送前多了一次身分驗證與授權。&lt;/p>
&lt;p>常見的實作有 GCP 的 IAP（Identity-Aware Proxy，對 HTTPS 應用與 TCP 連線各有一種模式，TCP forwarding 的來源位址段是 &lt;code>35.235.240.0/20&lt;/code>，到得了同一家雲端裡的主機，以及經 Cloud Interconnect 或 Cloud VPN 接進來、而且 Cloud Router 對地端通告了這段位址的地端主機）、Cloudflare Access、Teleport 的 proxy。Cloudflare Access 與 Teleport 用反向連線把流量送進主機，主機上的 connector 怎麼往外連、斷線時會怎樣見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/outbound-tunnel/" data-link-title="Outbound Tunnel" data-link-desc="反向隧道把出站連線轉成可達入口、與傳統 port-forward 的責任倒轉">Outbound Tunnel（出站隧道）&lt;/a>。這個模式的公開源頭是 Google 2014 年起發表的 BeyondCorp 系列論文。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>由代理連到內部 IP 的模式（GCP IAP）：&lt;/p>
&lt;ul>
&lt;li>主機的入站規則只放行某一段固定的代理位址，沒有任何來源是 &lt;code>0.0.0.0/0&lt;/code> 的管理 port 規則&lt;/li>
&lt;li>主機上的認證紀錄裡，SSH 連線的來源 IP 全部落在代理的位址段，看不出是哪個人——要查人得看代理或 IdP 的 audit log&lt;/li>
&lt;/ul>
&lt;p>經由代理程式反向連線的模式（Cloudflare Tunnel、Teleport agent）：&lt;/p>
&lt;ul>
&lt;li>主機上有一個常駐的代理程式（例如 &lt;code>cloudflared&lt;/code>、&lt;code>teleport&lt;/code>），主機的入站規則完全沒有管理 port&lt;/li>
&lt;li>代理程式把連線轉送到 sshd 的實作（&lt;code>cloudflared&lt;/code>），認證紀錄裡的 SSH 來源是 &lt;code>127.0.0.1&lt;/code> 或代理程式所在的本機位址；Teleport 節點用自己的 SSH 伺服器，sshd 的紀錄裡不會出現這些連線&lt;/li>
&lt;/ul>
&lt;p>兩種模式共同的樣子是主機沒有公開 IP，管理員仍然用 &lt;code>ssh&lt;/code> 或廠商 CLI 連得進去。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>導入身分感知代理之後，主機自己不再判斷連線者是誰，下面這些判斷移到代理與 IdP 上：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>IdP 帳號的安全強度&lt;/strong>：存取權限轉移到帳號上之後，帳號被釣魚等於通道被打開，所以 IdP 端要強制多因素驗證，管理員帳號優先用防釣魚的硬體金鑰或 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/passkey/" data-link-title="Passkey" data-link-desc="要用金鑰對取代密碼、或判斷已導入的 passkey 實際擋掉了什麼時，用來定位它的保護落在哪一段、上限由哪條路徑決定">Passkey&lt;/a>&lt;/li>
&lt;li>&lt;strong>授權範圍&lt;/strong>：授權綁給負責維運的群組，並限定可以連的 port 與主機，避免整個組織的帳號都拿得到通道&lt;/li>
&lt;li>&lt;strong>代理的 session 長度&lt;/strong>：IdP 停用一個帳號之後，代理已經簽給他的 session 或憑證要到期或被撤銷才失效（Cloudflare Access 的 session 最長可以設到一個月，&lt;code>gcloud&lt;/code> 在本機保存 refresh token），所以收回延遲的上限是代理端設定的 session 長度；處理離職或帳號外洩時另外在代理端撤銷 session。登入後留在本機的 token 檔可以被複製重放，不會再過一次多因素驗證，session 越短、或綁定裝置，被複製之後能用的時間越短&lt;/li>
&lt;li>&lt;strong>代理的可用性與延遲&lt;/strong>：所有位元組都經過代理轉送，代理服務中斷時管理通道一起中斷，大量傳檔也比直連慢；救援時的備用路徑見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）&lt;/a>&lt;/li>
&lt;li>&lt;strong>稽核紀錄的位置與開啟時機&lt;/strong>：主機端只看得到代理的位址，「誰在什麼時候連進哪台主機」要從代理或 IdP 的 audit log 取得。這份紀錄只記錄開啟之後的連線，事後要調查一次入侵時，開啟之前的連線查不回來，所以在切換到代理的同一天開啟，開啟之後實際連一次、查到這次連線的紀錄才算開對（見&lt;a href="https://tarrragon.github.io/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證&lt;/a>）&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 同樣收斂對外入口，由自己維運的主機轉接&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — 放行代理位址段的那一條入站規則所在的位置；AWS 叫 security group，GCP 對應的是 VPC 防火牆規則&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> — 代理用來確認帳號的系統&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道&lt;/a> — GCP IAP 的連線路徑、IAM 授權範圍與切換順序；實際指令在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/#%e7%b6%b2%e8%b7%af%e5%8f%af%e9%81%94%e6%80%a7%e4%b8%bb%e6%a9%9f%e5%88%86%e5%b8%83%e8%88%87%e4%bb%a3%e7%90%86%e6%8a%8a%e9%80%a3%e7%b7%9a%e9%80%81%e9%80%b2%e4%b8%bb%e6%a9%9f%e7%9a%84%e6%96%b9%e5%bc%8f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a> — 從代理位址段連入與代理程式反向連線兩種送達方式，依主機分布的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>身分感知代理（identity-aware proxy）是一個放在網際網路與內部主機之間的 <a href="/blog/devops/infra/knowledge-cards/reverse-proxy/" data-link-title="Reverse Proxy" data-link-desc="代替後端服務接收外部請求、再轉發到內部服務的中介層，承擔 TLS 終結、負載平衡與路由分流">Reverse Proxy</a>：它接收外部連線，先向組織的 <a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> 確認發起連線的帳號、再依授權規則決定是否替這條連線開到後端。通過的連線送到後端的方式有兩種：由代理從它自己的固定位址段連到主機的內部 IP（GCP 的 IAP），這時主機的防火牆只放行那段代理位址；或經由主機上的代理程式（connector，例如 <code>cloudflared</code>）主動往外建立的反向連線送進去（Cloudflare Tunnel、Teleport agent），這時主機不開任何入站 port。兩種方式下主機都不必持有公開 IP。它是 <a href="/blog/backend/knowledge-cards/zero-trust/" data-link-title="Zero Trust（零信任）" data-link-desc="判斷內網或 VPN 裡的服務能不能因為連線來自內部就省掉認證、或要評估一條連線該放行到什麼範圍時，查「每一次連線各自通過身分與授權」這個原則的內容與導入代價">Zero Trust（零信任）</a> 原則在存取入口上的一種實作：放行的依據是每一次連線的身分，網路位置不構成信任的理由。</p>
<h2 id="概念位置">概念位置</h2>
<p>管理通道拆成網路可達性、通道授權、主機登入驗證三層來看時（模型見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），身分感知代理同時承擔網路可達性與通道授權：sshd 只對代理可見，而能不能開通道由代理用 IdP 帳號判斷——這個判斷就是通道授權，指的是決定「這個人能不能打開通往主機的連線」，與登入主機時驗證 SSH 金鑰是兩回事。主機登入驗證在多數實作裡仍由主機上的 sshd 負責；Teleport 的節點這類實作用自己內建的 SSH 伺服器與代理簽發的憑證，主機登入驗證跟著換掉。由 sshd 負責時，金鑰的發放方式另見 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> 與 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a>。</p>
<p>它與 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> 都讓內部主機不必各自對外，差別在對外接收連線的元件由誰維運、用什麼判斷身分：跳板機是自己維運的主機、用 SSH 金鑰判斷；身分感知代理是廠商提供或自己架設的代理服務、用 IdP 帳號判斷。它與一般反向代理的差別在轉送前多了一次身分驗證與授權。</p>
<p>常見的實作有 GCP 的 IAP（Identity-Aware Proxy，對 HTTPS 應用與 TCP 連線各有一種模式，TCP forwarding 的來源位址段是 <code>35.235.240.0/20</code>，到得了同一家雲端裡的主機，以及經 Cloud Interconnect 或 Cloud VPN 接進來、而且 Cloud Router 對地端通告了這段位址的地端主機）、Cloudflare Access、Teleport 的 proxy。Cloudflare Access 與 Teleport 用反向連線把流量送進主機，主機上的 connector 怎麼往外連、斷線時會怎樣見 <a href="/blog/backend/knowledge-cards/outbound-tunnel/" data-link-title="Outbound Tunnel" data-link-desc="反向隧道把出站連線轉成可達入口、與傳統 port-forward 的責任倒轉">Outbound Tunnel（出站隧道）</a>。這個模式的公開源頭是 Google 2014 年起發表的 BeyondCorp 系列論文。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>由代理連到內部 IP 的模式（GCP IAP）：</p>
<ul>
<li>主機的入站規則只放行某一段固定的代理位址，沒有任何來源是 <code>0.0.0.0/0</code> 的管理 port 規則</li>
<li>主機上的認證紀錄裡，SSH 連線的來源 IP 全部落在代理的位址段，看不出是哪個人——要查人得看代理或 IdP 的 audit log</li>
</ul>
<p>經由代理程式反向連線的模式（Cloudflare Tunnel、Teleport agent）：</p>
<ul>
<li>主機上有一個常駐的代理程式（例如 <code>cloudflared</code>、<code>teleport</code>），主機的入站規則完全沒有管理 port</li>
<li>代理程式把連線轉送到 sshd 的實作（<code>cloudflared</code>），認證紀錄裡的 SSH 來源是 <code>127.0.0.1</code> 或代理程式所在的本機位址；Teleport 節點用自己的 SSH 伺服器，sshd 的紀錄裡不會出現這些連線</li>
</ul>
<p>兩種模式共同的樣子是主機沒有公開 IP，管理員仍然用 <code>ssh</code> 或廠商 CLI 連得進去。</p>
<h2 id="設計責任">設計責任</h2>
<p>導入身分感知代理之後，主機自己不再判斷連線者是誰，下面這些判斷移到代理與 IdP 上：</p>
<ul>
<li><strong>IdP 帳號的安全強度</strong>：存取權限轉移到帳號上之後，帳號被釣魚等於通道被打開，所以 IdP 端要強制多因素驗證，管理員帳號優先用防釣魚的硬體金鑰或 <a href="/blog/backend/knowledge-cards/passkey/" data-link-title="Passkey" data-link-desc="要用金鑰對取代密碼、或判斷已導入的 passkey 實際擋掉了什麼時，用來定位它的保護落在哪一段、上限由哪條路徑決定">Passkey</a></li>
<li><strong>授權範圍</strong>：授權綁給負責維運的群組，並限定可以連的 port 與主機，避免整個組織的帳號都拿得到通道</li>
<li><strong>代理的 session 長度</strong>：IdP 停用一個帳號之後，代理已經簽給他的 session 或憑證要到期或被撤銷才失效（Cloudflare Access 的 session 最長可以設到一個月，<code>gcloud</code> 在本機保存 refresh token），所以收回延遲的上限是代理端設定的 session 長度；處理離職或帳號外洩時另外在代理端撤銷 session。登入後留在本機的 token 檔可以被複製重放，不會再過一次多因素驗證，session 越短、或綁定裝置，被複製之後能用的時間越短</li>
<li><strong>代理的可用性與延遲</strong>：所有位元組都經過代理轉送，代理服務中斷時管理通道一起中斷，大量傳檔也比直連慢；救援時的備用路徑見 <a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a></li>
<li><strong>稽核紀錄的位置與開啟時機</strong>：主機端只看得到代理的位址，「誰在什麼時候連進哪台主機」要從代理或 IdP 的 audit log 取得。這份紀錄只記錄開啟之後的連線，事後要調查一次入侵時，開啟之前的連線查不回來，所以在切換到代理的同一天開啟，開啟之後實際連一次、查到這次連線的紀錄才算開對（見<a href="/blog/report/protection-proves-itself-by-firing/" data-link-title="平常不動作的防護機制的部署驗證：設定被接受、元件狀態正常、執行失敗被吞掉與測試從中途注入這些提早出現的成功訊號，驗證查詢與處置指令的假陰性，以及合成觸發本身有損害時的分段驗證" data-link-desc="部署停損開關、稽核紀錄、告警或自動處置這類平常不動作的防護機制之後，要判斷它算不算部署完成時讀；也用在驗收紀錄寫著「部署成功」「狀態 ACTIVE」「設定已套用」、或驗證查詢回傳零筆就被當成結論的時候">平常不動作的防護機制的部署驗證</a>）</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 同樣收斂對外入口，由自己維運的主機轉接</li>
<li><a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — 放行代理位址段的那一條入站規則所在的位置；AWS 叫 security group，GCP 對應的是 VPC 防火牆規則</li>
<li><a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> — 代理用來確認帳號的系統</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/gcp-iap-ssh-access/" data-link-title="GCP VM 的 SSH 與 RDP 管理通道：從預設的全開規則改走 IAP TCP forwarding" data-link-desc="GCP 預設網路自帶的防火牆規則與它們留下的暴露面、IAP TCP forwarding 的連線路徑與日常連線指令、切換到 IAP 的順序為什麼這樣排、IAP 通道連不上時的依層判讀，以及 IAM 授權範圍、OS Login 的啟用條件與工作階段的結束、拿掉外部 IP 之後的出站路徑、Organization Policy、階層式防火牆、稽核紀錄與 Security Command Center 的 OPEN_SSH_PORT finding 在 GCP 上的處理">GCP VM 的 SSH 與 RDP 管理通道</a> — GCP IAP 的連線路徑、IAM 授權範圍與切換順序；實際指令在 <a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a></li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e7%b6%b2%e8%b7%af%e5%8f%af%e9%81%94%e6%80%a7%e4%b8%bb%e6%a9%9f%e5%88%86%e5%b8%83%e8%88%87%e4%bb%a3%e7%90%86%e6%8a%8a%e9%80%a3%e7%b7%9a%e9%80%81%e9%80%b2%e4%b8%bb%e6%a9%9f%e7%9a%84%e6%96%b9%e5%bc%8f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 從代理位址段連入與代理程式反向連線兩種送達方式，依主機分布的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>Bastion Host（跳板機）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/</guid><description>&lt;p>管理員要登入 private &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> 裡的主機時，跳板機（bastion host，也稱 jump host；其他中文資料常寫作「堡壘主機」）是常見的中繼點：一台放在公開網段、只負責轉接管理連線的主機。其他主機的防火牆只接受來自跳板機的 SSH，管理員先連上跳板機，再從那裡連到目的主機。OpenSSH 用 &lt;code>ssh -J &amp;lt;bastion-host&amp;gt; &amp;lt;target-host&amp;gt;&lt;/code>（設定檔裡的 &lt;code>ProxyJump&lt;/code>）一次完成兩段連線，私鑰留在管理員自己的電腦上，不必複製到跳板機。&lt;/p>
&lt;p>跳板機處理的是主機以 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 登入管理的情境：每台主機上跑著接受 SSH 連線的服務程式 sshd、預設監聽 22 port。只部署在 PaaS 或 serverless 平台、沒有任何可以 SSH 登入的主機時，平台自己的主控台與 CLI 就是管理入口，用不到跳板機。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>把管理通道拆成網路可達性、通道授權、主機登入驗證三層時（見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），跳板機是網路可達性的做法之一：它把「每台主機都對外開 22 port」收斂成「只有一台對外開」。它同時兼任通道授權——決定誰能打開通往主機的連線——判斷依據是跳板機上的 SSH 金鑰，或跳板機信任的 SSH CA 所簽的憑證；跳板機與目的主機接受同一把金鑰時，一把私鑰外洩就同時通過通道授權與主機登入驗證。&lt;/p>
&lt;p>跳板機放在 public subnet，目的主機留在 private subnet，兩者之間的放行用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> 的來源引用表達：目的主機的入站規則把來源寫成「跳板機所屬的那個 security group」而不是一個 IP，跳板機換了 IP 或多加一台，規則都不必改。&lt;/p>
&lt;p>暴露面收斂成一台，但沒有消失：跳板機仍然對網際網路開著管理 port，它的 sshd 漏洞與金鑰管理就是整個環境的入口。不讓任何自管主機對外的做法是 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a>，對外接收連線的那一層改由代理服務承擔。雲端廠商也提供受管的跳板服務（例如 Azure Bastion、Oracle Cloud 的 Bastion），主機的修補由廠商負責，連線方式與身分驗證依各家服務而定，位置介於自建跳板機與身分感知代理之間。Oracle Cloud 的 Bastion 建立時要指定一個目標 subnet（主機所在的 subnet，或主機所在 subnet 接受其流量的另一個 subnet），經由它登入的主機不需要公開 IP，誰能開工作階段由 IAM 政策決定，設定方式見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 的〈不放金鑰時的登入服務〉。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>private subnet 主機的 security group 只允許來自某一個 security group（或某一台主機）的 22 port&lt;/li>
&lt;li>管理員的 &lt;code>~/.ssh/config&lt;/code> 裡有 &lt;code>ProxyJump&lt;/code> 設定，或團隊文件寫著「先連 bastion」&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>自建跳板機時，整個環境對外的 SSH 暴露都集中在這一台上，所以下面這幾件事的標準比一般主機高：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>跳板機的來源範圍&lt;/strong>：管理員有固定的對外 IP 時，跳板機的 22 port 只放行那幾個 IP；遠端工作、IP 會變動時，白名單縮不下來，跳板機的 22 port 實質上對全網開放，認證紀錄裡會持續出現來自陌生 IP 的失敗登入，它的 sshd 修補速度就是整個環境的暴露期。只放行管理員 IP 時，ISP 重新分配了那個 IP，管理員就連不進跳板機，舊 IP 的新使用者反而在放行範圍裡；這一類會在主機外面改變的設定怎麼定期比對，見 &lt;a href="https://tarrragon.github.io/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算&lt;/a>〈會到期與會改變的外部設定〉&lt;/li>
&lt;li>&lt;strong>私鑰不落地&lt;/strong>：agent forwarding 是讓遠端主機借用管理員本機 ssh-agent 裡的私鑰去做下一段認證的功能（&lt;code>ssh -A&lt;/code> 或設定檔的 &lt;code>ForwardAgent yes&lt;/code>），開著它時，任何在跳板機上取得 root 的人都能借那把私鑰連到其他主機；把私鑰直接放上跳板機的後果相同。&lt;code>ProxyJump&lt;/code> 讓第二段認證也在管理員電腦上完成，不需要 agent forwarding。這種從一台主機往其他主機擴散的攻擊見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">Lateral Movement（橫向移動）&lt;/a>&lt;/li>
&lt;li>&lt;strong>金鑰不共用、不可複製&lt;/strong>：跳板機與目的主機各用不同的金鑰或憑證，讓一把私鑰外洩只通過一層；目的主機的金鑰發放可以改用 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a>；跳板機與目的主機信任同一把 SSH CA 時，用不同的 principal 把兩層分開，做法見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入&lt;/a>。管理員的私鑰放在硬體安全金鑰裡（OpenSSH 8.2 起的 &lt;code>ed25519-sk&lt;/code>）時，私鑰檔無法被複製走&lt;/li>
&lt;li>&lt;strong>修補與監控&lt;/strong>：跳板機要比其他主機更快套用 sshd 的安全更新，並保留連線紀錄&lt;/li>
&lt;li>&lt;strong>操作紀錄&lt;/strong>：外包人員登入正式環境、或稽核要求逐條指令留紀錄時，需要集中錄製管理員在目的主機上執行了什麼；所有管理連線都經過跳板機，所以它是放錄製工具的位置。這個屬性與廠商無關，一套設定涵蓋多雲與地端混用的環境。組織有 IdP 時，讓跳板機只接受 IdP 簽發的短期 SSH 憑證，錄製與隨帳號收回兩者都保留&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 跳板機轉接的協定與金鑰認證&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 改用代理服務承擔對外接收連線&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）&lt;/a> — 跳板機本身連不上時的備用登入路徑&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/#%e9%80%9a%e9%81%93%e6%8e%88%e6%ac%8a%e7%9a%84%e8%ba%ab%e5%88%86%e4%be%86%e6%ba%90%e5%ad%98%e5%8f%96%e8%80%85%e8%88%87%e4%b8%bb%e6%a9%9f%e6%93%81%e6%9c%89%e8%80%85%e7%9a%84%e9%97%9c%e4%bf%82%e8%ba%ab%e5%88%86%e6%89%80%e5%9c%a8%e7%9a%84%e5%b8%b3%e8%99%9f%e7%b3%bb%e7%b5%b1" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a> — 存取者是組織成員、外部合作方、個人或機器時，跳板機與其他做法的選擇條件&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>管理員要登入 private <a href="/blog/devops/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> 裡的主機時，跳板機（bastion host，也稱 jump host；其他中文資料常寫作「堡壘主機」）是常見的中繼點：一台放在公開網段、只負責轉接管理連線的主機。其他主機的防火牆只接受來自跳板機的 SSH，管理員先連上跳板機，再從那裡連到目的主機。OpenSSH 用 <code>ssh -J &lt;bastion-host&gt; &lt;target-host&gt;</code>（設定檔裡的 <code>ProxyJump</code>）一次完成兩段連線，私鑰留在管理員自己的電腦上，不必複製到跳板機。</p>
<p>跳板機處理的是主機以 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 登入管理的情境：每台主機上跑著接受 SSH 連線的服務程式 sshd、預設監聽 22 port。只部署在 PaaS 或 serverless 平台、沒有任何可以 SSH 登入的主機時，平台自己的主控台與 CLI 就是管理入口，用不到跳板機。</p>
<h2 id="概念位置">概念位置</h2>
<p>把管理通道拆成網路可達性、通道授權、主機登入驗證三層時（見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），跳板機是網路可達性的做法之一：它把「每台主機都對外開 22 port」收斂成「只有一台對外開」。它同時兼任通道授權——決定誰能打開通往主機的連線——判斷依據是跳板機上的 SSH 金鑰，或跳板機信任的 SSH CA 所簽的憑證；跳板機與目的主機接受同一把金鑰時，一把私鑰外洩就同時通過通道授權與主機登入驗證。</p>
<p>跳板機放在 public subnet，目的主機留在 private subnet，兩者之間的放行用 <a href="/blog/devops/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> 的來源引用表達：目的主機的入站規則把來源寫成「跳板機所屬的那個 security group」而不是一個 IP，跳板機換了 IP 或多加一台，規則都不必改。</p>
<p>暴露面收斂成一台，但沒有消失：跳板機仍然對網際網路開著管理 port，它的 sshd 漏洞與金鑰管理就是整個環境的入口。不讓任何自管主機對外的做法是 <a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a>，對外接收連線的那一層改由代理服務承擔。雲端廠商也提供受管的跳板服務（例如 Azure Bastion、Oracle Cloud 的 Bastion），主機的修補由廠商負責，連線方式與身分驗證依各家服務而定，位置介於自建跳板機與身分感知代理之間。Oracle Cloud 的 Bastion 建立時要指定一個目標 subnet（主機所在的 subnet，或主機所在 subnet 接受其流量的另一個 subnet），經由它登入的主機不需要公開 IP，誰能開工作階段由 IAM 政策決定，設定方式見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈不放金鑰時的登入服務〉。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>private subnet 主機的 security group 只允許來自某一個 security group（或某一台主機）的 22 port</li>
<li>管理員的 <code>~/.ssh/config</code> 裡有 <code>ProxyJump</code> 設定，或團隊文件寫著「先連 bastion」</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>自建跳板機時，整個環境對外的 SSH 暴露都集中在這一台上，所以下面這幾件事的標準比一般主機高：</p>
<ul>
<li><strong>跳板機的來源範圍</strong>：管理員有固定的對外 IP 時，跳板機的 22 port 只放行那幾個 IP；遠端工作、IP 會變動時，白名單縮不下來，跳板機的 22 port 實質上對全網開放，認證紀錄裡會持續出現來自陌生 IP 的失敗登入，它的 sshd 修補速度就是整個環境的暴露期。只放行管理員 IP 時，ISP 重新分配了那個 IP，管理員就連不進跳板機，舊 IP 的新使用者反而在放行範圍裡；這一類會在主機外面改變的設定怎麼定期比對，見 <a href="/blog/devops/operations/09-host-lifecycle/long-running-host-maintenance/" data-link-title="長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算" data-link-desc="單台 Linux 主機（VPS、雲端主機）長期上線時，沒有人操作也會改變的狀態與維護對它們做的事：log 與資料的寫入者和清理機制（logrotate、journald、容器 log）、磁碟寫滿時各元件的反應、每份 log 要讀的欄位、入口在主機與雲端各層的盤點（ss）、各條更新管道的安全更新與重開機（dnf-automatic、unattended-upgrades、needs-restarting）、會到期與會改變的設定、檢查間隔的推算，以及接手一台已上線的主機時先處理哪幾項">長期上線主機的維護項目：會累積的資料、定期讀的 log、對外入口的盤點、安全更新、會到期的設定與檢查間隔的推算</a>〈會到期與會改變的外部設定〉</li>
<li><strong>私鑰不落地</strong>：agent forwarding 是讓遠端主機借用管理員本機 ssh-agent 裡的私鑰去做下一段認證的功能（<code>ssh -A</code> 或設定檔的 <code>ForwardAgent yes</code>），開著它時，任何在跳板機上取得 root 的人都能借那把私鑰連到其他主機；把私鑰直接放上跳板機的後果相同。<code>ProxyJump</code> 讓第二段認證也在管理員電腦上完成，不需要 agent forwarding。這種從一台主機往其他主機擴散的攻擊見 <a href="/blog/backend/knowledge-cards/lateral-movement/" data-link-title="Lateral Movement（橫向移動）" data-link-desc="評估一台主機被入侵之後攻擊者還連得到哪些機器、內網或 VPN 裡的可達範圍要收到多窄時，查攻擊者以已經拿下的機器為起點往內擴散的方式">Lateral Movement（橫向移動）</a></li>
<li><strong>金鑰不共用、不可複製</strong>：跳板機與目的主機各用不同的金鑰或憑證，讓一把私鑰外洩只通過一層；目的主機的金鑰發放可以改用 <a href="/blog/devops/infra/knowledge-cards/ssh-certificate/" data-link-title="SSH Certificate（SSH 憑證）" data-link-desc="主機多、人員常異動而逐台收回 authorized_keys 跟不上，或要讓 SSH 登入權限自動到期時，查 SSH CA 簽發短期憑證的運作方式與導入時要決定的事">SSH Certificate（SSH 憑證）</a> 或 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a>；跳板機與目的主機信任同一把 SSH CA 時，用不同的 principal 把兩層分開，做法見 <a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a>。管理員的私鑰放在硬體安全金鑰裡（OpenSSH 8.2 起的 <code>ed25519-sk</code>）時，私鑰檔無法被複製走</li>
<li><strong>修補與監控</strong>：跳板機要比其他主機更快套用 sshd 的安全更新，並保留連線紀錄</li>
<li><strong>操作紀錄</strong>：外包人員登入正式環境、或稽核要求逐條指令留紀錄時，需要集中錄製管理員在目的主機上執行了什麼；所有管理連線都經過跳板機，所以它是放錄製工具的位置。這個屬性與廠商無關，一套設定涵蓋多雲與地端混用的環境。組織有 IdP 時，讓跳板機只接受 IdP 簽發的短期 SSH 憑證，錄製與隨帳號收回兩者都保留</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 跳板機轉接的協定與金鑰認證</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 改用代理服務承擔對外接收連線</li>
<li><a href="/blog/devops/infra/knowledge-cards/serial-console/" data-link-title="Serial Console（序列主控台）" data-link-desc="SSH 因為防火牆、sshd 設定或磁碟滿而連不進主機，或要規劃管理通道斷掉時的備用路徑時，查序列主控台與頻外管理介面的用法與它本身的暴露面">Serial Console（序列主控台）</a> — 跳板機本身連不上時的備用登入路徑</li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e9%80%9a%e9%81%93%e6%8e%88%e6%ac%8a%e7%9a%84%e8%ba%ab%e5%88%86%e4%be%86%e6%ba%90%e5%ad%98%e5%8f%96%e8%80%85%e8%88%87%e4%b8%bb%e6%a9%9f%e6%93%81%e6%9c%89%e8%80%85%e7%9a%84%e9%97%9c%e4%bf%82%e8%ba%ab%e5%88%86%e6%89%80%e5%9c%a8%e7%9a%84%e5%b8%b3%e8%99%9f%e7%b3%bb%e7%b5%b1" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 存取者是組織成員、外部合作方、個人或機器時，跳板機與其他做法的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>SSH Certificate（SSH 憑證）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh-certificate/</guid><description>&lt;p>SSH 憑證（SSH certificate）是由一把 SSH CA 私鑰簽過名的 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 使用者公鑰，簽名裡帶著有效期間、允許登入的帳號名稱（principal）與識別字串。主機不再逐一記錄每個人的公鑰，只信任 CA 的公鑰：sshd 設定 &lt;code>TrustedUserCAKeys&lt;/code> 指向 CA 公鑰之後，任何帶著有效 CA 簽名、而且還在有效期內的憑證都能登入它列出的帳號。OpenSSH 從 5.4 版起內建這個機制，格式是 OpenSSH 自己的，與網站用的 X.509 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/certificate/" data-link-title="Certificate（數位憑證）" data-link-desc="文件裡的「憑證」要分辨指的是數位憑證還是存取憑證，或要判斷 TLS、mTLS、程式碼簽章用的憑證外洩之後要撤銷什麼時，查數位憑證由哪些部分組成、哪一部分是秘密">憑證&lt;/a>不相通。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>SSH 憑證處理的是登入主機用的金鑰怎麼發、怎麼失效——&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>把這一層叫主機登入驗證。它與另外兩種金鑰發放方式的差別在權限怎麼失效：&lt;/p>
&lt;ul>
&lt;li>靜態 &lt;code>authorized_keys&lt;/code>（見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a>）：沒有到期時間，收回要逐台刪除&lt;/li>
&lt;li>由中央服務決定可用的公鑰（見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login&lt;/a>）：帳號或角色移除，下一次登入就失敗&lt;/li>
&lt;li>SSH 憑證：憑證到期自動失效，離職或權限變更最晚在一個有效期之後生效&lt;/li>
&lt;/ul>
&lt;p>簽發服務通常接在 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider&lt;/a> 後面：管理員用 IdP 帳號登入簽發服務，換到一張短期憑證。管理通道前面那一層的通道授權（決定誰能打開通往主機的連線，例如身分感知代理檢查 IdP 帳號）若也用同一個 IdP，兩層就共用同一個身分來源，停用一個帳號同時擋下兩層。HashiCorp Vault 的 SSH secrets engine 與 Teleport 都以這種方式運作。三種做法收回的都是下一次登入，已經建立的工作階段要另外在主機上中斷。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>主機的 &lt;code>sshd_config&lt;/code> 有 &lt;code>TrustedUserCAKeys&lt;/code>，而 &lt;code>~/.ssh/authorized_keys&lt;/code> 是空的或不存在&lt;/li>
&lt;li>管理員的 &lt;code>~/.ssh/&lt;/code> 裡在金鑰對旁邊多了一個同名加 &lt;code>-cert.pub&lt;/code> 結尾的檔（例如 &lt;code>id_ed25519-cert.pub&lt;/code>），&lt;code>ssh&lt;/code> 連線時會自動帶上它；用 &lt;code>ssh-keygen -L -f &amp;lt;cert-file&amp;gt;&lt;/code> 讀得出它的有效期間與 principal&lt;/li>
&lt;li>每天第一次 SSH 之前要先跑一個登入或簽發指令&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>CA 私鑰取代了每台主機上的公鑰清單，所以導入 SSH 憑證時的決定都圍繞著 CA 與憑證內容：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>CA 私鑰的保管&lt;/strong>：CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以放在 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault&lt;/a> 或硬體安全模組這類受控位置，不放在任何人的電腦上&lt;/li>
&lt;li>&lt;strong>有效期長度&lt;/strong>：有效期等於能接受的最晚收回延遲——離職或權限移除之後，那張憑證最多還能用一個有效期。延遲上限越短，有效期越短，管理員重新簽發也越頻繁；管理員手動簽發時常設成一個工作日左右，讓每天開工時簽一次；簽發由用戶端在連線時自動完成時（Teleport 的 &lt;code>tsh&lt;/code>、smallstep 的 &lt;code>step ssh&lt;/code>），有效期可以縮到幾小時以內，收回延遲跟著縮短&lt;/li>
&lt;li>&lt;strong>principal 的對應&lt;/strong>：憑證列出的帳號名稱與主機上的帳號怎麼對應，決定一張憑證能登入哪些主機的哪些帳號。主機上只有一個共用帳號（例如雲端映像預設的 &lt;code>ubuntu&lt;/code>）時，每張憑證的 principal 都是 &lt;code>ubuntu&lt;/code>，主機端分不出是誰登入，要靠憑證的識別字串（簽發時的 &lt;code>-I&lt;/code>）出現在 sshd 的認證紀錄裡來辨識人&lt;/li>
&lt;li>&lt;strong>提前撤銷&lt;/strong>：憑證還沒到期就要失效時（例如私鑰外洩），sshd 用 &lt;code>RevokedKeys&lt;/code> 指向撤銷清單，這份清單要能派送到所有主機&lt;/li>
&lt;li>&lt;strong>主機憑證&lt;/strong>：同一個機制也能反過來簽主機的金鑰（&lt;code>HostCertificate&lt;/code>），讓用戶端驗證主機身分。主機經常重建（autoscaling、重新部署）時，每次重建主機指紋都會變，用戶端會反覆看到指紋變更的警告而養成直接接受的習慣；改用主機憑證後用戶端只信任 CA，前提是用戶端的 &lt;code>known_hosts&lt;/code> 已加入 &lt;code>@cert-authority&lt;/code> 與 CA 公鑰&lt;/li>
&lt;/ul>
&lt;p>簽發的指令形式是 &lt;code>ssh-keygen -s &amp;lt;ca-private-key&amp;gt; -I &amp;lt;identity&amp;gt; -n &amp;lt;principals&amp;gt; -V &amp;lt;validity&amp;gt; &amp;lt;user-public-key&amp;gt;&lt;/code>：&lt;code>-I&lt;/code> 是寫進憑證、會出現在主機認證紀錄裡的識別字串（通常填 IdP 帳號），&lt;code>-n&lt;/code> 是允許登入的帳號名稱、多個用逗號分隔，&lt;code>-V&lt;/code> 是有效期間（&lt;code>+8h&lt;/code> 表示從現在起八小時）。實際運作時由簽發服務代為執行，簽出的憑證寫在使用者公鑰旁邊的 &lt;code>*-cert.pub&lt;/code>。&lt;/p></description><content:encoded><![CDATA[<p>SSH 憑證（SSH certificate）是由一把 SSH CA 私鑰簽過名的 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 使用者公鑰，簽名裡帶著有效期間、允許登入的帳號名稱（principal）與識別字串。主機不再逐一記錄每個人的公鑰，只信任 CA 的公鑰：sshd 設定 <code>TrustedUserCAKeys</code> 指向 CA 公鑰之後，任何帶著有效 CA 簽名、而且還在有效期內的憑證都能登入它列出的帳號。OpenSSH 從 5.4 版起內建這個機制，格式是 OpenSSH 自己的，與網站用的 X.509 <a href="/blog/backend/knowledge-cards/certificate/" data-link-title="Certificate（數位憑證）" data-link-desc="文件裡的「憑證」要分辨指的是數位憑證還是存取憑證，或要判斷 TLS、mTLS、程式碼簽章用的憑證外洩之後要撤銷什麼時，查數位憑證由哪些部分組成、哪一部分是秘密">憑證</a>不相通。</p>
<h2 id="概念位置">概念位置</h2>
<p>SSH 憑證處理的是登入主機用的金鑰怎麼發、怎麼失效——<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>把這一層叫主機登入驗證。它與另外兩種金鑰發放方式的差別在權限怎麼失效：</p>
<ul>
<li>靜態 <code>authorized_keys</code>（見 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a>）：沒有到期時間，收回要逐台刪除</li>
<li>由中央服務決定可用的公鑰（見 <a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a>）：帳號或角色移除，下一次登入就失敗</li>
<li>SSH 憑證：憑證到期自動失效，離職或權限變更最晚在一個有效期之後生效</li>
</ul>
<p>簽發服務通常接在 <a href="/blog/backend/knowledge-cards/identity-provider/" data-link-title="Identity Provider" data-link-desc="登入要交給外部系統時，用來定位對方承擔了什麼、沒承擔什麼，以及這邊還剩下哪些責任">Identity Provider</a> 後面：管理員用 IdP 帳號登入簽發服務，換到一張短期憑證。管理通道前面那一層的通道授權（決定誰能打開通往主機的連線，例如身分感知代理檢查 IdP 帳號）若也用同一個 IdP，兩層就共用同一個身分來源，停用一個帳號同時擋下兩層。HashiCorp Vault 的 SSH secrets engine 與 Teleport 都以這種方式運作。三種做法收回的都是下一次登入，已經建立的工作階段要另外在主機上中斷。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>主機的 <code>sshd_config</code> 有 <code>TrustedUserCAKeys</code>，而 <code>~/.ssh/authorized_keys</code> 是空的或不存在</li>
<li>管理員的 <code>~/.ssh/</code> 裡在金鑰對旁邊多了一個同名加 <code>-cert.pub</code> 結尾的檔（例如 <code>id_ed25519-cert.pub</code>），<code>ssh</code> 連線時會自動帶上它；用 <code>ssh-keygen -L -f &lt;cert-file&gt;</code> 讀得出它的有效期間與 principal</li>
<li>每天第一次 SSH 之前要先跑一個登入或簽發指令</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>CA 私鑰取代了每台主機上的公鑰清單，所以導入 SSH 憑證時的決定都圍繞著 CA 與憑證內容：</p>
<ul>
<li><strong>CA 私鑰的保管</strong>：CA 私鑰能簽出登入所有主機的憑證，它外洩的影響等於所有主機的所有金鑰一起外洩，所以放在 <a href="/blog/devops/infra/knowledge-cards/vault/" data-link-title="HashiCorp Vault" data-link-desc="機密管理系統，集中存放密碼、API key、TLS 私鑰，提供存取控制、稽核和自動輪替">HashiCorp Vault</a> 或硬體安全模組這類受控位置，不放在任何人的電腦上</li>
<li><strong>有效期長度</strong>：有效期等於能接受的最晚收回延遲——離職或權限移除之後，那張憑證最多還能用一個有效期。延遲上限越短，有效期越短，管理員重新簽發也越頻繁；管理員手動簽發時常設成一個工作日左右，讓每天開工時簽一次；簽發由用戶端在連線時自動完成時（Teleport 的 <code>tsh</code>、smallstep 的 <code>step ssh</code>），有效期可以縮到幾小時以內，收回延遲跟著縮短</li>
<li><strong>principal 的對應</strong>：憑證列出的帳號名稱與主機上的帳號怎麼對應，決定一張憑證能登入哪些主機的哪些帳號。主機上只有一個共用帳號（例如雲端映像預設的 <code>ubuntu</code>）時，每張憑證的 principal 都是 <code>ubuntu</code>，主機端分不出是誰登入，要靠憑證的識別字串（簽發時的 <code>-I</code>）出現在 sshd 的認證紀錄裡來辨識人</li>
<li><strong>提前撤銷</strong>：憑證還沒到期就要失效時（例如私鑰外洩），sshd 用 <code>RevokedKeys</code> 指向撤銷清單，這份清單要能派送到所有主機</li>
<li><strong>主機憑證</strong>：同一個機制也能反過來簽主機的金鑰（<code>HostCertificate</code>），讓用戶端驗證主機身分。主機經常重建（autoscaling、重新部署）時，每次重建主機指紋都會變，用戶端會反覆看到指紋變更的警告而養成直接接受的習慣；改用主機憑證後用戶端只信任 CA，前提是用戶端的 <code>known_hosts</code> 已加入 <code>@cert-authority</code> 與 CA 公鑰</li>
</ul>
<p>簽發的指令形式是 <code>ssh-keygen -s &lt;ca-private-key&gt; -I &lt;identity&gt; -n &lt;principals&gt; -V &lt;validity&gt; &lt;user-public-key&gt;</code>：<code>-I</code> 是寫進憑證、會出現在主機認證紀錄裡的識別字串（通常填 IdP 帳號），<code>-n</code> 是允許登入的帳號名稱、多個用逗號分隔，<code>-V</code> 是有效期間（<code>+8h</code> 表示從現在起八小時）。實際運作時由簽發服務代為執行，簽出的憑證寫在使用者公鑰旁邊的 <code>*-cert.pub</code>。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 靜態金鑰認證與 <code>authorized_keys</code></li>
<li><a href="/blog/devops/infra/knowledge-cards/os-login/" data-link-title="OS Login" data-link-desc="GCP 專案要讓 SSH 登入權限跟著 Google 帳號與 IAM 走、啟用 OS Login 之後管理員或 CI 連不進 VM、或盤點專案 metadata 裡殘留的 SSH 公鑰時，查 OS Login 的運作方式、IAM role 與它和 metadata 金鑰的差別">OS Login</a> — 另一種讓登入權限隨帳號收回的做法</li>
<li><a href="/blog/devops/infra/knowledge-cards/ssh-host-key/" data-link-title="SSH Host Key 與 known_hosts（主機金鑰）" data-link-desc="ssh 第一次連線問要不要信任指紋、出現 REMOTE HOST IDENTIFICATION HAS CHANGED 警告，或主機重建之後連不上時，查 host key 怎麼證明主機身分、known_hosts 記了什麼，以及首次信任的風險">SSH Host Key 與 known_hosts（主機金鑰）</a> — 主機憑證取代的逐台指紋確認</li>
<li><a href="/blog/devops/infra/03-network-foundation/openssh-ca-setup/" data-link-title="OpenSSH 內建 CA 的 SSH 憑證導入：CA 金鑰、sshd 設定、簽發、principal、撤銷與主機憑證" data-link-desc="用 OpenSSH 自帶的 ssh-keygen 與 sshd 建立 SSH 憑證的完整流程：使用者 CA 與主機 CA 的金鑰怎麼產生與保管、sshd 的 TrustedUserCAKeys 與 AuthorizedPrincipalsFile、簽發時的識別字串與有效期、憑證外洩時用 KRL 撤銷，以及用主機憑證取代逐台確認指紋，以及導入順序與兩個會讓所有人登不進去的設定：AuthorizedPrincipalsFile 的讀取權限、RevokedKeys 指的檔案不存在">OpenSSH 內建 CA 的 SSH 憑證導入</a> — 產生 CA、設定 sshd、簽發與撤銷的指令，以及 AuthorizedPrincipalsFile 讀取權限與 RevokedKeys 檔案不存在這兩個會讓所有人登不進去的設定</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 跳板機與目的主機不共用金鑰時，目的主機這一段可以改用憑證</li>
<li><a href="/blog/devops/infra/03-network-foundation/management-access-channel/#%e4%b8%bb%e6%a9%9f%e7%99%bb%e5%85%a5%e9%a9%97%e8%ad%89%e7%99%bb%e5%85%a5%e6%ac%8a%e9%99%90%e7%9a%84%e6%94%b6%e5%9b%9e%e9%9d%a0%e4%ba%ba%e5%b7%a5%e5%88%aa%e9%99%a4%e5%b8%b3%e8%99%9f%e9%80%a3%e5%8b%95%e6%88%96%e5%88%b0%e6%9c%9f" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a> — 人員流動、主機數量與帳號系統不同時，三種金鑰發放方式的選擇條件</li>
</ul>
]]></content:encoded></item><item><title>Serial Console（序列主控台）</title><link>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/devops/infra/knowledge-cards/serial-console/</guid><description>&lt;p>序列主控台（serial console）承擔的是救援：當網路設定或 sshd 出錯而 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> 連不進去時，它提供一條不經過主機作業系統的網路設定與 sshd 的登入路徑。實體伺服器上它是一條接到序列埠的線；雲端 VM 把它做成虛擬序列埠，由廠商的網頁管理介面或 CLI 轉送，例如 GCP 的 Serial Console（&lt;code>gcloud compute connect-to-serial-port&lt;/code>）、AWS 的 EC2 Serial Console，以及 Oracle Cloud 的 serial console connection（先替主機建一個序列主控台連線（instance console connection），再從自己的電腦用 SSH 連上，官方說明本機連線要用 RSA 金鑰）。連上之後看到的是作業系統的文字主控台；只看開機輸出不必登入，要在登入提示登進去時，用主機上設了密碼的本機帳號（Oracle Cloud 官方說明平台映像的 &lt;code>opc&lt;/code> 沒有預設密碼）。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>序列主控台與平時的管理通道設計方向相反：管理通道要隨時可用，救援入口平時關閉、只在需要時打開，因為它繞過了 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> 或 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> 這些平時的控制點。管理通道的網路可達性、通道授權或主機登入驗證任何一層設定錯了（這三層的分法見&lt;a href="https://tarrragon.github.io/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計&lt;/a>），序列主控台都還連得上，因為它不經過那三層的設定。它有自己的授權：雲端上是雲端帳號的 IAM 權限與開啟它的開關，登入時用主機上的本機帳號密碼，所以雲端 IAM 或開關設錯時，它一樣進不去。&lt;/p>
&lt;p>不同環境的救援入口形態不同。地端實體伺服器用的是頻外管理控制器（BMC，out-of-band management，其他中文資料常寫作「帶外管理」）：IPMI、Dell iDRAC、HPE iLO 這類嵌在主機板上的管理控制器，有自己的網路埠，在作業系統沒開機時也能看主控台、重開機、掛載安裝媒體。VPS 服務商多半在網頁後台提供一個在瀏覽器裡顯示主機畫面的主控台（常叫 VNC console），用法與雲端的序列主控台相同：不經過主機的網路設定，用本機帳號密碼登入。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;ul>
&lt;li>SSH 連線逾時或被拒，而雲端的網頁管理介面或監控顯示主機仍在執行——網路設定、防火牆或 sshd 出錯時的典型樣貌&lt;/li>
&lt;li>開機過程卡住（檔案系統檢查、&lt;code>/etc/fstab&lt;/code> 寫錯）時，序列主控台看得到開機訊息，SSH 在那個階段還沒有啟動&lt;/li>
&lt;li>雲端帳號或專案的設定裡，序列主控台存取是關閉的（GCP 預設關閉互動登入，用 metadata &lt;code>serial-port-enable=TRUE&lt;/code> 對單一 VM 開啟；AWS 要在帳號層級先允許）&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>救援入口在出事當下才用得到，而帳號、權限與演練在主機正常時準備最快，所以規劃時要先決定：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>救援帳號&lt;/strong>：序列主控台用本機帳號密碼登入，而用金鑰登入的主機通常沒有設密碼的帳號，所以救援帳號要在主機正常時就建好。密碼逐台隨機產生、存放在受控的密碼庫，sshd 維持只收金鑰；這組密碼不隨 IdP 收回，持有它的人離開時要逐台輪替。沒有事先建帳號時，雲端 VM 還有要重開機或停機、因此比較慢的路（GCP 的 startup-script 每次開機以 root 執行；AWS 的 user-data 預設只在第一次開機執行，要改成每次開機執行的 cloud-init 格式；GCP、AWS 與 Oracle Cloud 都能把開機磁碟掛到另一台 VM 修改；官方另為 Oracle Linux 附了經由序列主控台開進維護模式、重設 &lt;code>opc&lt;/code> 金鑰的範例），地端主機只能重開機進單人模式。不必重開機的路是業者在主機裡執行指令的服務：AWS 的 SSM Run Command 與 Oracle Cloud 的 Run Command，兩者都要主機上的代理程式在運作，代理程式停了或連不到業者的服務端點時用不上；Oracle Cloud 這幾個入口各自的前提見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務&lt;/a> 的〈建立之後加金鑰與金鑰遺失時的入口〉&lt;/li>
&lt;li>&lt;strong>演練&lt;/strong>：建好救援帳號之後實際用序列主控台登入一次、確認 sudo 可用，再關回去。沒演練過的救援入口在出事當下才發現密碼不對或權限不足；平時不存在、事故時才開的權限叫 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/break-glass-access/" data-link-title="Break-Glass Access" data-link-desc="說明緊急情況下臨時授予的高權限存取，如何用工單、時限與事後審查治理">Break-Glass Access&lt;/a>，事後回顧的做法在那張卡&lt;/li>
&lt;li>&lt;strong>開啟範圍與時機&lt;/strong>：平時保持關閉，救援時只對單一主機開啟，用完關回去；存取權限只授予持有救援責任的少數帳號（GCP 上開啟 metadata 與連線都需要 &lt;code>roles/compute.instanceAdmin.v1&lt;/code>，而這也是一般 VM 管理會用到的角色，光靠它收不窄）。GCP 用 Organization Policy 的 &lt;code>compute.disableSerialPortAccess&lt;/code> 在組織層級預設關閉時，只有能對專案解除這條政策的人開得了序列主控台，救援名單要包含這些人&lt;/li>
&lt;li>&lt;strong>頻外管理介面的網路&lt;/strong>：IPMI 這類控制器歷史上有多個認證弱點，它的網路埠接在獨立的管理網段，不接到一般網段或網際網路&lt;/li>
&lt;li>&lt;strong>操作紀錄&lt;/strong>：序列主控台的連線繞過了平時的稽核點與錄製位置，雲端廠商的 audit log 查得到誰在什麼時候開了主控台，查不到在裡面執行了什麼；有逐指令稽核要求時，救援時做了什麼要另外記錄&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH&lt;/a> — 平時的管理通道，序列主控台在它斷掉時接手&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）&lt;/a> — 平時的通道控制點，救援入口繞過它&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）&lt;/a> — 另一種平時的通道控制點&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e6%ba%96%e5%82%99%e6%95%91%e6%8f%b4%e5%85%a5%e5%8f%a3serial-console-%e8%88%87%e6%9c%ac%e6%a9%9f%e6%95%91%e6%8f%b4%e5%b8%b3%e8%99%9f" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄&lt;/a> — 在 GCP 上建救援帳號、開啟與關閉 Serial Console 的指令&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建&lt;/a>〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉 — 在 Oracle Cloud 上建序列主控台連線、把官方的連線字串改寫成 ProxyJump 設定，以及連上之後看到的開機輸出&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>序列主控台（serial console）承擔的是救援：當網路設定或 sshd 出錯而 <a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> 連不進去時，它提供一條不經過主機作業系統的網路設定與 sshd 的登入路徑。實體伺服器上它是一條接到序列埠的線；雲端 VM 把它做成虛擬序列埠，由廠商的網頁管理介面或 CLI 轉送，例如 GCP 的 Serial Console（<code>gcloud compute connect-to-serial-port</code>）、AWS 的 EC2 Serial Console，以及 Oracle Cloud 的 serial console connection（先替主機建一個序列主控台連線（instance console connection），再從自己的電腦用 SSH 連上，官方說明本機連線要用 RSA 金鑰）。連上之後看到的是作業系統的文字主控台；只看開機輸出不必登入，要在登入提示登進去時，用主機上設了密碼的本機帳號（Oracle Cloud 官方說明平台映像的 <code>opc</code> 沒有預設密碼）。</p>
<h2 id="概念位置">概念位置</h2>
<p>序列主控台與平時的管理通道設計方向相反：管理通道要隨時可用，救援入口平時關閉、只在需要時打開，因為它繞過了 <a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> 或 <a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> 這些平時的控制點。管理通道的網路可達性、通道授權或主機登入驗證任何一層設定錯了（這三層的分法見<a href="/blog/devops/infra/03-network-foundation/management-access-channel/" data-link-title="伺服器管理通道的設計：網路可達性、通道授權、主機登入驗證三層的做法與選擇，以及救援入口" data-link-desc="與雲端廠商無關、適用於雲端 VM、地端主機與 VPS 的管理員登入通道設計：拆成網路可達性、通道授權與主機登入驗證的模型，各層的做法（來源白名單、跳板機、VPN、身分感知代理、代理程式反向連線、SSH 憑證）、更換 port 與 fail2ban 改不了的部分，依存取者身分、連線送達方式、收回方式選擇組合的依據，以及移除公開 IP 之後的出站路徑、切換順序、救援入口、組織護欄與稽核">伺服器管理通道的設計</a>），序列主控台都還連得上，因為它不經過那三層的設定。它有自己的授權：雲端上是雲端帳號的 IAM 權限與開啟它的開關，登入時用主機上的本機帳號密碼，所以雲端 IAM 或開關設錯時，它一樣進不去。</p>
<p>不同環境的救援入口形態不同。地端實體伺服器用的是頻外管理控制器（BMC，out-of-band management，其他中文資料常寫作「帶外管理」）：IPMI、Dell iDRAC、HPE iLO 這類嵌在主機板上的管理控制器，有自己的網路埠，在作業系統沒開機時也能看主控台、重開機、掛載安裝媒體。VPS 服務商多半在網頁後台提供一個在瀏覽器裡顯示主機畫面的主控台（常叫 VNC console），用法與雲端的序列主控台相同：不經過主機的網路設定，用本機帳號密碼登入。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<ul>
<li>SSH 連線逾時或被拒，而雲端的網頁管理介面或監控顯示主機仍在執行——網路設定、防火牆或 sshd 出錯時的典型樣貌</li>
<li>開機過程卡住（檔案系統檢查、<code>/etc/fstab</code> 寫錯）時，序列主控台看得到開機訊息，SSH 在那個階段還沒有啟動</li>
<li>雲端帳號或專案的設定裡，序列主控台存取是關閉的（GCP 預設關閉互動登入，用 metadata <code>serial-port-enable=TRUE</code> 對單一 VM 開啟；AWS 要在帳號層級先允許）</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>救援入口在出事當下才用得到，而帳號、權限與演練在主機正常時準備最快，所以規劃時要先決定：</p>
<ul>
<li><strong>救援帳號</strong>：序列主控台用本機帳號密碼登入，而用金鑰登入的主機通常沒有設密碼的帳號，所以救援帳號要在主機正常時就建好。密碼逐台隨機產生、存放在受控的密碼庫，sshd 維持只收金鑰；這組密碼不隨 IdP 收回，持有它的人離開時要逐台輪替。沒有事先建帳號時，雲端 VM 還有要重開機或停機、因此比較慢的路（GCP 的 startup-script 每次開機以 root 執行；AWS 的 user-data 預設只在第一次開機執行，要改成每次開機執行的 cloud-init 格式；GCP、AWS 與 Oracle Cloud 都能把開機磁碟掛到另一台 VM 修改；官方另為 Oracle Linux 附了經由序列主控台開進維護模式、重設 <code>opc</code> 金鑰的範例），地端主機只能重開機進單人模式。不必重開機的路是業者在主機裡執行指令的服務：AWS 的 SSM Run Command 與 Oracle Cloud 的 Run Command，兩者都要主機上的代理程式在運作，代理程式停了或連不到業者的服務端點時用不上；Oracle Cloud 這幾個入口各自的前提見 <a href="/blog/devops/infra/cloud-providers/vm-options/login/" data-link-title="登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務" data-link-desc="雲端主機建立頁 SSH 金鑰欄位的幾個選項（讓業者產生、上傳、貼上、不放金鑰）各自把什麼放進主機、私鑰由誰保管，映像檔決定的預設使用者名稱，建立之後還能不能加金鑰、金鑰遺失時剩下哪些入口，以及完全不放金鑰時靠業者的哪些服務登入">登入：SSH 金鑰、預設使用者、建立後加金鑰與不放金鑰時的登入服務</a> 的〈建立之後加金鑰與金鑰遺失時的入口〉</li>
<li><strong>演練</strong>：建好救援帳號之後實際用序列主控台登入一次、確認 sudo 可用，再關回去。沒演練過的救援入口在出事當下才發現密碼不對或權限不足；平時不存在、事故時才開的權限叫 <a href="/blog/backend/knowledge-cards/break-glass-access/" data-link-title="Break-Glass Access" data-link-desc="說明緊急情況下臨時授予的高權限存取，如何用工單、時限與事後審查治理">Break-Glass Access</a>，事後回顧的做法在那張卡</li>
<li><strong>開啟範圍與時機</strong>：平時保持關閉，救援時只對單一主機開啟，用完關回去；存取權限只授予持有救援責任的少數帳號（GCP 上開啟 metadata 與連線都需要 <code>roles/compute.instanceAdmin.v1</code>，而這也是一般 VM 管理會用到的角色，光靠它收不窄）。GCP 用 Organization Policy 的 <code>compute.disableSerialPortAccess</code> 在組織層級預設關閉時，只有能對專案解除這條政策的人開得了序列主控台，救援名單要包含這些人</li>
<li><strong>頻外管理介面的網路</strong>：IPMI 這類控制器歷史上有多個認證弱點，它的網路埠接在獨立的管理網段，不接到一般網段或網際網路</li>
<li><strong>操作紀錄</strong>：序列主控台的連線繞過了平時的稽核點與錄製位置，雲端廠商的 audit log 查得到誰在什麼時候開了主控台，查不到在裡面執行了什麼；有逐指令稽核要求時，救援時做了什麼要另外記錄</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/devops/infra/knowledge-cards/ssh/" data-link-title="SSH" data-link-desc="Secure Shell — 加密的遠端 shell 連線，有 SSH 等於有 CLI 工具鏈，沒有就只能靠 FTP 和 web 面板">SSH</a> — 平時的管理通道，序列主控台在它斷掉時接手</li>
<li><a href="/blog/devops/infra/knowledge-cards/identity-aware-proxy/" data-link-title="Identity-Aware Proxy（身分感知代理）" data-link-desc="要讓管理員或使用者不經公開入口連到內部主機與服務、評估把存取控制從來源 IP 改成帳號身分時，查身分感知代理的連線路徑、前提與代價">Identity-Aware Proxy（身分感知代理）</a> — 平時的通道控制點，救援入口繞過它</li>
<li><a href="/blog/devops/infra/knowledge-cards/bastion-host/" data-link-title="Bastion Host（跳板機）" data-link-desc="內部主機不想各自對外開管理 port、要設定 ProxyJump 經由跳板機連線、判斷 agent forwarding 的風險，或要評估自建跳板機與改用身分感知代理的取捨時，查跳板機的連線方式、暴露面與維運責任">Bastion Host（跳板機）</a> — 另一種平時的通道控制點</li>
<li><a href="/blog/devops/infra/cloud-providers/gcp/hands-on/iap-ssh-migration-walkthrough/#%e6%ba%96%e5%82%99%e6%95%91%e6%8f%b4%e5%85%a5%e5%8f%a3serial-console-%e8%88%87%e6%9c%ac%e6%a9%9f%e6%95%91%e6%8f%b4%e5%b8%b3%e8%99%9f" data-link-title="GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄：盤點、救援入口、IAP 規則與 IAM、停用舊規則驗證、OS Login 啟用與移除外部 IP" data-link-desc="要把 GCP 專案裡對 0.0.0.0/0 開放 22 與 3389 port 的預設規則換成只放行 IAP，想照一次實跑過的順序做、看每一步沒做或換一種做法的結果時讀；也用在停用舊規則之後連不上、OS Login 啟用後有人登不進去，或拿掉外部 IP 之後要確認出站走了 Cloud NAT 的時候">GCP VM 的 SSH 與 RDP 改走 IAP 的實測紀錄</a> — 在 GCP 上建救援帳號、開啟與關閉 Serial Console 的指令</li>
<li><a href="/blog/devops/infra/cloud-providers/oracle-cloud/hands-on/private-ubuntu-egress-console-rebuild/" data-link-title="Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建" data-link-desc="要在 Oracle Cloud 沒有公開 IP 的私有子網路裡跑一台 Ubuntu 主機、讓它裝得到套件更新，或要經由序列主控台看主機的開機過程，或主機被閒置回收、停止或刪除之後要照原樣重建，或想知道 NAT gateway 要不要另外付費、開機磁碟扣掉多少區塊磁碟額度、合計超過 200 GB 時放不進額度的那一顆怎麼算、怎麼用 quota 擋下超額的建立、線上加大開機磁碟超過額度時怎麼算、空出額度之後付費磁碟多久轉成免費時，查實際跑過的指令、錯誤原文、重建腳本與每一步的讀法">Oracle Cloud 私有子網路 Ubuntu 主機的實測紀錄：套件來源的出站（NAT gateway、路由與 NSG）、序列主控台的連線，以及被回收之後的重建</a>〈序列主控台：RSA 金鑰、建立連線、ProxyJump 設定與看到的開機輸出〉 — 在 Oracle Cloud 上建序列主控台連線、把官方的連線字串改寫成 ProxyJump 設定，以及連上之後看到的開機輸出</li>
</ul>
]]></content:encoded></item><item><title>Kubernetes RBAC（角色型存取控制）</title><link>https://tarrragon.github.io/blog/backend/knowledge-cards/kubernetes-rbac/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/knowledge-cards/kubernetes-rbac/</guid><description>&lt;p>Kubernetes RBAC（Role-Based Access Control）是 API server 判斷「這個已通過認證的身分，能不能對這個物件做這個動作」的機制：權限寫成「哪些動詞（verb，例如 &lt;code>get&lt;/code>、&lt;code>list&lt;/code>、&lt;code>create&lt;/code>、&lt;code>delete&lt;/code>）可以作用在哪些資源（resource，例如 &lt;code>pods&lt;/code>、&lt;code>secrets&lt;/code>、&lt;code>nodes&lt;/code>）」，收在 Role 或 ClusterRole 裡，再用 RoleBinding 或 ClusterRoleBinding 把它綁給使用者、群組或 service account。可先對照 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/pod/" data-link-title="Pod" data-link-desc="在 Kubernetes 的輸出或設定裡看到 Pod，要知道它和容器的關係、為什麼它的 IP 與名稱會變、以及誰負責替補它時">Pod&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">Namespace（命名空間）&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>RBAC 位在認證之後：誰在呼叫 API（使用者、群組、service account）由認證決定，雲端託管的叢集通常把這一步交給雲端的 IAM；RBAC 決定這個身分在叢集裡能做什麼。Role 與 RoleBinding 只在一個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">namespace&lt;/a> 裡生效，ClusterRole 與 ClusterRoleBinding 作用於整個叢集或叢集層級的資源（節點、namespace 本身）。最小權限的原則見 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege&lt;/a>。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;code>kubectl&lt;/code> 回 &lt;code>Error from server (Forbidden): pods is forbidden: User &amp;quot;...&amp;quot; cannot create resource &amp;quot;pods&amp;quot; in API group &amp;quot;&amp;quot; in the namespace &amp;quot;...&amp;quot;&lt;/code>：認證通過、RBAC 拒絕，訊息裡寫出了被拒的動詞、資源與 namespace。&lt;/li>
&lt;li>&lt;code>kubectl auth can-i create pods --namespace=&amp;lt;namespace&amp;gt;&lt;/code> 回 &lt;code>yes&lt;/code> 或 &lt;code>no&lt;/code>，用來確認自己的帳號有沒有某個權限；管理員加 &lt;code>--as=&amp;lt;user&amp;gt;&lt;/code> 替別人查。&lt;/li>
&lt;li>能在某個 namespace &lt;code>create&lt;/code> &lt;code>pods&lt;/code> 的身分，能用 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/privileged-container/" data-link-title="Privileged Container（特權容器）" data-link-desc="在 Pod 設定裡看到 securityContext.privileged=true、要判斷某個除錯或系統元件為什麼需要它，或評估誰能建這種 Pod 等於持有節點 root 權限時，查特權容器能做什麼與它的權限邊界">Privileged Container&lt;/a> 或主機目錄掛載（hostPath，&lt;code>kubectl debug node&lt;/code> 預設的 &lt;code>general&lt;/code> profile 就是這樣）取得節點的 root 權限（除非 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/pod-security-admission/" data-link-title="Pod Security Admission（Pod 安全准入）" data-link-desc="Pod 因為安全設定被拒絕建立、要限制某個 namespace 不能跑 privileged 或掛載主機目錄的 Pod，或評估 kubectl debug 這類需要高權限的工具在叢集裡能不能用時，查 Pod Security Admission 的三個等級與 namespace label">Pod Security Admission&lt;/a> 擋下），所以 &lt;code>pods&lt;/code> 的 &lt;code>create&lt;/code> 是敏感權限，不只是「能部署」。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>權限綁給群組而不是個人，人員異動在群組裡處理；ClusterRoleBinding 綁 &lt;code>cluster-admin&lt;/code> 的對象要逐一列得出來，因為它涵蓋叢集裡的一切。內建的 &lt;code>edit&lt;/code>、&lt;code>admin&lt;/code> 這類 ClusterRole 範圍很寬，綁在 namespace 層的 RoleBinding 上可以把範圍收在那個 namespace。service account 的權限常被忽略：跑在 Pod 裡的程式用的就是它，權限過大時，那個 Pod 被入侵等於持有那份權限。&lt;/p>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/pod-security-admission/" data-link-title="Pod Security Admission（Pod 安全准入）" data-link-desc="Pod 因為安全設定被拒絕建立、要限制某個 namespace 不能跑 privileged 或掛載主機目錄的 Pod，或評估 kubectl debug 這類需要高權限的工具在叢集裡能不能用時，查 Pod Security Admission 的三個等級與 namespace label">Pod Security Admission&lt;/a> — RBAC 允許建 Pod 之後，決定這個 Pod 的安全設定能不能被接受&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/privileged-container/" data-link-title="Privileged Container（特權容器）" data-link-desc="在 Pod 設定裡看到 securityContext.privileged=true、要判斷某個除錯或系統元件為什麼需要它，或評估誰能建這種 Pod 等於持有節點 root 權限時，查特權容器能做什麼與它的權限邊界">Privileged Container&lt;/a> — 能建 Pod 的權限最需要防的那一種 Pod&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege&lt;/a> — RBAC 設計的原則&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Kubernetes RBAC（Role-Based Access Control）是 API server 判斷「這個已通過認證的身分，能不能對這個物件做這個動作」的機制：權限寫成「哪些動詞（verb，例如 <code>get</code>、<code>list</code>、<code>create</code>、<code>delete</code>）可以作用在哪些資源（resource，例如 <code>pods</code>、<code>secrets</code>、<code>nodes</code>）」，收在 Role 或 ClusterRole 裡，再用 RoleBinding 或 ClusterRoleBinding 把它綁給使用者、群組或 service account。可先對照 <a href="/blog/backend/knowledge-cards/pod/" data-link-title="Pod" data-link-desc="在 Kubernetes 的輸出或設定裡看到 Pod，要知道它和容器的關係、為什麼它的 IP 與名稱會變、以及誰負責替補它時">Pod</a> 與 <a href="/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">Namespace（命名空間）</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>RBAC 位在認證之後：誰在呼叫 API（使用者、群組、service account）由認證決定，雲端託管的叢集通常把這一步交給雲端的 IAM；RBAC 決定這個身分在叢集裡能做什麼。Role 與 RoleBinding 只在一個 <a href="/blog/backend/knowledge-cards/namespace/" data-link-title="Namespace（命名空間）" data-link-desc="看到 kubectl 的 -n、kube-system、default，或跨 namespace 的 Service 名稱連不到時：namespace 劃分物件名稱的範圍、預設用哪一個、對 DNS 名稱與權限的影響">namespace</a> 裡生效，ClusterRole 與 ClusterRoleBinding 作用於整個叢集或叢集層級的資源（節點、namespace 本身）。最小權限的原則見 <a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege</a>。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><code>kubectl</code> 回 <code>Error from server (Forbidden): pods is forbidden: User &quot;...&quot; cannot create resource &quot;pods&quot; in API group &quot;&quot; in the namespace &quot;...&quot;</code>：認證通過、RBAC 拒絕，訊息裡寫出了被拒的動詞、資源與 namespace。</li>
<li><code>kubectl auth can-i create pods --namespace=&lt;namespace&gt;</code> 回 <code>yes</code> 或 <code>no</code>，用來確認自己的帳號有沒有某個權限；管理員加 <code>--as=&lt;user&gt;</code> 替別人查。</li>
<li>能在某個 namespace <code>create</code> <code>pods</code> 的身分，能用 <a href="/blog/backend/knowledge-cards/privileged-container/" data-link-title="Privileged Container（特權容器）" data-link-desc="在 Pod 設定裡看到 securityContext.privileged=true、要判斷某個除錯或系統元件為什麼需要它，或評估誰能建這種 Pod 等於持有節點 root 權限時，查特權容器能做什麼與它的權限邊界">Privileged Container</a> 或主機目錄掛載（hostPath，<code>kubectl debug node</code> 預設的 <code>general</code> profile 就是這樣）取得節點的 root 權限（除非 <a href="/blog/backend/knowledge-cards/pod-security-admission/" data-link-title="Pod Security Admission（Pod 安全准入）" data-link-desc="Pod 因為安全設定被拒絕建立、要限制某個 namespace 不能跑 privileged 或掛載主機目錄的 Pod，或評估 kubectl debug 這類需要高權限的工具在叢集裡能不能用時，查 Pod Security Admission 的三個等級與 namespace label">Pod Security Admission</a> 擋下），所以 <code>pods</code> 的 <code>create</code> 是敏感權限，不只是「能部署」。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>權限綁給群組而不是個人，人員異動在群組裡處理；ClusterRoleBinding 綁 <code>cluster-admin</code> 的對象要逐一列得出來，因為它涵蓋叢集裡的一切。內建的 <code>edit</code>、<code>admin</code> 這類 ClusterRole 範圍很寬，綁在 namespace 層的 RoleBinding 上可以把範圍收在那個 namespace。service account 的權限常被忽略：跑在 Pod 裡的程式用的就是它，權限過大時，那個 Pod 被入侵等於持有那份權限。</p>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/backend/knowledge-cards/pod-security-admission/" data-link-title="Pod Security Admission（Pod 安全准入）" data-link-desc="Pod 因為安全設定被拒絕建立、要限制某個 namespace 不能跑 privileged 或掛載主機目錄的 Pod，或評估 kubectl debug 這類需要高權限的工具在叢集裡能不能用時，查 Pod Security Admission 的三個等級與 namespace label">Pod Security Admission</a> — RBAC 允許建 Pod 之後，決定這個 Pod 的安全設定能不能被接受</li>
<li><a href="/blog/backend/knowledge-cards/privileged-container/" data-link-title="Privileged Container（特權容器）" data-link-desc="在 Pod 設定裡看到 securityContext.privileged=true、要判斷某個除錯或系統元件為什麼需要它，或評估誰能建這種 Pod 等於持有節點 root 權限時，查特權容器能做什麼與它的權限邊界">Privileged Container</a> — 能建 Pod 的權限最需要防的那一種 Pod</li>
<li><a href="/blog/backend/knowledge-cards/least-privilege/" data-link-title="Least Privilege" data-link-desc="說明身份、服務與人員只應取得完成工作所需的最小權限">Least Privilege</a> — RBAC 設計的原則</li>
</ul>
]]></content:encoded></item></channel></rss>