<?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>Certificate on Tarragon</title><link>https://tarrragon.github.io/blog/tags/certificate/</link><description>Recent content in Certificate on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 23 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/certificate/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>