要点:authorized_keys では、ログインを許可するすべての公開鍵を各サーバーに列挙し、アクセスは誰かがその行を削除するまで続きます。OpenSSH 証明書では、サーバーは 1 つのユーザー CA(TrustedUserCAKeys)を信頼します。ユーザーは自分の鍵に ssh-keygen -s で署名してもらいます。その際、ID(-I)、その鍵でログインできるアカウント(プリンシパル、-n)、有効期間(-V)が証明書に付け加えられます。クライアントは、各ホストのフィンガープリントを初回接続時に受け入れる代わりに、1 つのホスト CA(known_hosts の @cert-authority)を信頼します。有効期間の短い証明書を使えばオフボーディング(離任したメンバーのアクセス削除)は自動的に済み、残りは鍵失効リスト(RevokedKeys)が担います。その代わり、CA を自分で運用し、その鍵を守らなければなりません。
authorized_keys にできないこと
~/.ssh/authorized_keys はシンプルで、運用すべき仕組みが何もありません。どこでも使われているのはそのためです。ただし、人とサーバーが増えるにつれて限界が見えてきます。
- アクセスに期限がない:2023年に業務委託先のために追加した鍵は、誰かが削除しない限り、コピーされたすべてのサーバーで 2026年になっても使えます(行ごとの
expiry-time=はありますが、誰かが設定しなければなりません)。 - ID がない:行の中身は鍵とコメントだけです。それがどの人、チーム、ジョブのものかは、記録があるとしてもスプレッドシートの中です。
- 配布は自分で担う:誰かを追加または削除するたびに、構成管理ツールか手作業で、すべてのサーバーに手を入れることになります。
- ホスト鍵は初回接続時に信頼する方式:各クライアントは各サーバーのフィンガープリントを最初の接続時に受け入れます。サーバーを再構築すると、あらゆるクライアントで
REMOTE HOST IDENTIFICATION HAS CHANGEDが表示されます。
OpenSSH 5.4(2010年)から使える OpenSSH 証明書は、この 4 つすべてを解決します。X.509 ではありません。形式は OpenSSH 独自のもので、公開鍵に ID と制約を加え、CA 鍵で署名したものです。その CA 鍵自体も通常の SSH キーです。2025年には、形式の仕様が OpenSSH のソースにある PROTOCOL.certkeys ファイルからインターネットドラフトに移り、現在は IETF の SSHM ワーキンググループで draft-ietf-sshm-cert として扱われています。
2 つの CA、2 つの方向
答える問いが異なるため、別々の署名鍵が 2 つ必要です。
- ユーザー CA は「この鍵でこのアカウントとしてログインしてよいか」に答えます。サーバーがこれを信頼します。
- ホスト CA は「これは本当に
web01か」に答えます。クライアントがこれを信頼します。
$ ssh-keygen -q -t ed25519 -f user_ca -C "Example user CA" -N ""
$ ssh-keygen -q -t ed25519 -f host_ca -C "Example host CA" -N ""
-N "" は検証環境を単純にするためのものです。本番の CA 鍵は、HSM、ハードウェアトークン、または署名サービスに置くべきです(HashiCorp Vault の SSH シークレットエンジン、smallstep、Teleport、クラウドプロバイダーの同等のサービスは、いずれもこれを提供しています)。ユーザー CA の鍵を持つ者は誰でも、サーバーが許可する任意のアカウントとしてログインできます。
ユーザー証明書
Alice の既存の公開鍵に、アカウント alice と deploy を対象として、有効期間 8 時間で署名します。
$ ssh-keygen -s user_ca -I alice@example.test -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub
Signed user key /home/alice-laptop/id_ed25519-cert.pub: id "alice@example.test" serial 1001 for alice,deploy valid from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
-Iは鍵 ID です。sshd はログインのたびにこれをログに記録するので、ホスト名ではなく、人やジョブを示す値にしてください。-nはプリンシパル、つまりこの証明書が名乗ることのできる名前を列挙します。ssh-keygen のマニュアルには、プリンシパルのない証明書は「すべてのユーザーまたはホストに対して有効」と書かれていますが、sshd 10.0 はTrustedUserCAKeysの下でそのようなユーザー証明書を拒否します(Certificate lacks principal list)。必ず-nを指定してください。-V -5m:+8hは、有効期間を 5 分前(時刻のずれへの対策)から 8 時間後までに設定します。-zはシリアル番号を設定します。失効の際には、この番号で証明書を指定できます。
ssh-keygen -L で、署名された内容を確認できます。
/home/alice-laptop/id_ed25519-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s
Signing CA: ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60 (using ssh-ed25519)
Key ID: "alice@example.test"
Serial: 1001
Valid: from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
Principals:
alice
deploy
Critical Options: (none)
Extensions:
permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
デフォルトでは、ユーザー証明書は 5 つの拡張をすべて許可します。-O clear でそれらを取り除き、必要なものだけを改めて追加できます(たとえば -O clear -O permit-pty)。クリティカルオプションは証明書の使い方を制限します。force-command はクライアントが要求したコマンドが何であれそれを置き換え、source-address は証明書を使える接続元を制限します。source-address=10.0.0.0/8 を指定すると、127.0.0.1 からのログインは拒否されます。許可された接続元からであれば、クライアントが何を要求しても強制コマンドが実行されます。
Critical Options:
force-command echo backup only
source-address 10.0.0.0/8
$ ssh deploy@web01.example.test
deploy@web01.example.test: Permission denied (publickey).
cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).
Refused by certificate options
...
$ ssh deploy@web01.example.test "rm -rf /tmp/anything"
backup only
Starting session: forced-command (key-option) 'echo backup only' for deploy from 127.0.0.1 port 48976 id 0
ssh クライアントは、証明書が秘密鍵の隣に id_ed25519-cert.pub として置かれていれば、自動的に見つけます。そうでない場合は CertificateFile で指定してください。ssh-add は両方をエージェントに読み込みます。
sshd の設定
サーバー側の設定は数行だけです。次のドロップインファイルは sshd -t のチェックに通り、このページのすべてのテストで使用しました。
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedKeys /etc/ssh/revoked_keys
PasswordAuthentication no
LogLevel VERBOSE
TrustedUserCAKeysには 1 つ以上の CA 公開鍵を記載します。CA のローテーション中は、新旧両方を記載してください。AuthorizedPrincipalsFileはアカウントとプリンシパルを対応付けます。ここでは/etc/ssh/auth_principals/deployにdeployという 1 行だけが書かれているため、deployとしてログインできるのは、プリンシパルdeployを記載した証明書だけです。このオプションがない場合、sshd はアカウント名そのものがプリンシパルに含まれていることを要求します。対応付けを使うほうが柔軟です。oncallのようなプリンシパルを多数のアカウントで許可でき、AuthorizedPrincipalsCommandを使えばディレクトリから検索することもできます。RevokedKeysは失効リストを指定します(後述)。LogLevel VERBOSEは、ログインごとに証明書の鍵 ID とシリアル番号を記録します。これが、authorized_keysにはなかった監査証跡です。
Alice の証明書によるログインを、両側から見ると次のようになります。
Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:IuzvI4QsLmSx1+6p3yrvlRx2wdl+LUCY1zXqSzssLAc, serial 0 ID "web01" CA ssh-ed25519 SHA256:+iie/YHPulJReHN2GQ8UvrorBVrtayD2T1MGxeg/QCQ valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
Host 'web01.example.test' is known and matches the ED25519-CERT host certificate.
Offering public key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Server accepts key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Authenticated to web01.example.test ([127.0.0.1]:22) using "publickey".
Accepted publickey for deploy from 127.0.0.1 port 57432 ssh2: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s ID alice@example.test (serial 1001) CA ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60
sshd_config を編集できない場合でも、1 つのアカウントだけなら、そのアカウント自身の authorized_keys で CA を信頼させることができます。principals= オプションは、プリンシパルファイルと同じように機能します。
cert-authority,principals="deploy-team" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK63...
$ ssh deploy@web01.example.test 'echo logged in as $(whoami)'
logged in as deploy
ホスト証明書
各サーバーのホスト鍵に、ホスト CA で署名します。-h を付けるとホスト証明書になり、プリンシパルはクライアントが接続に使う名前になります。
$ ssh-keygen -s host_ca -I web01 -h -n web01.example.test,web01 -V -5m:+52w /etc/ssh/ssh_host_ed25519_key.pub
Signed host key /etc/ssh/ssh_host_ed25519_key-cert.pub: id "web01" serial 0 for web01.example.test,web01 valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
次に、生成された証明書を HostCertificate で指定し(上の sshd の設定ファイルのとおり)、クライアントには、ホストごとのフィンガープリントの代わりに known_hosts(または /etc/ssh/ssh_known_hosts)の 1 行を渡します。
@cert-authority *.example.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBtSsqoqxwcl31DTJg3kTPsv9eLuuw5gU1BcvYybODlu Example host CA
これで、新しいサーバーや再構築したサーバーも、クライアントが使った名前が証明書に記載されている限り、確認のプロンプトなしで最初の接続から信頼されます。よく驚かされるのは、この最後の条件です。プリンシパルに含まれない名前で接続すると、クライアントは通常のホスト鍵による確認に切り替わり、厳格なチェックが有効な場合はそこで停止します。
$ ssh deploy@db01.example.test
Certificate invalid: name is not a listed principal
No ED25519 host key is known for db01.example.test and you have requested strict checking.
Host key verification failed.
クライアントが使うすべての名前を記載してください。短縮名、CNAME、IP で接続する人がいる場合は IP アドレス、ロードバランサーの名前も含めます。ssh-keyscan -c はサーバーの証明書を取得して確認できるようにし、known_hosts の @revoked 行は特定のホスト鍵や CA を拒否します。
失敗したときの表示
ユーザー証明書に問題があっても、クライアントが表示するのは Permission denied (publickey). だけです。原因は、LogLevel VERBOSE で出力されるサーバーのログに記録されます。
| 状況 | sshd のログ(OpenSSH 10.0p2) |
|---|---|
| アカウントが証明書のプリンシパルにない(対応付け後) | Certificate does not contain an authorized principal |
-n なしで発行した証明書、AuthorizedPrincipalsFile なし | Certificate lacks principal list |
-V の有効期間を過ぎた証明書 | Certificate invalid: expired |
| KRL に載っているシリアル番号または鍵 | Authentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked_keys |
source-address が一致しない | cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1). |
証明書の問題に見えて、実はそうではないものが 2 つあります。
- 時刻のずれ:有効期間は、サーバーの時計に照らして確認されます。証明書は数分前から有効になるように発行し(
-V -5m:...)、サーバーの時刻は NTP で同期させてください。 - 失敗の繰り返しによるペナルティ:OpenSSH 9.8 以降の sshd は、認証に失敗し続けるアドレスにペナルティを科します(
PerSourcePenalties、デフォルトで有効)。1 台のマシンから証明書をテストしていたところ、1 回の失敗の後、次の接続が認証の前に切断されました。
srclimit_penalise: 127.0.0.1/32: activating ipv4 penalty of 19 seconds for penalty: failed authentication
$ ssh deploy@web01.example.test
Connection closed by 127.0.0.1 port 22
drop connection #0 from [127.0.0.1]:55080 on [127.0.0.1]:22 penalty: failed authentication
失敗が続いた直後に ssh が Connection closed と報告する場合は、ペナルティの期間が過ぎるのを待ってから設定を再び変更してください。
KRL による失効
失効の主な仕組みは、有効期間を短くすることです。有効期間 8 時間の証明書は、放っておいても無効になります。それ以外の場合のために、OpenSSH には鍵失効リスト(KRL、OpenSSH 6.2 以降)があります。シリアル番号、鍵 ID、または鍵で失効を指定できる、コンパクトなバイナリ形式です。シリアル番号 1001 の Alice の証明書を失効させるには、次のようにします。
$ printf 'serial: 1001\n' > /tmp/krl-spec
$ ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub /tmp/krl-spec
Revoking from /tmp/krl-spec
$ ssh-keygen -Q -f /etc/ssh/revoked_keys id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub (alice@laptop): REVOKED
sshd は認証のたびに RevokedKeys を読み込むので、再起動しなくても次のログインは失敗します。既存の KRL を置き換えずに更新するには -u を追加します。KRL は、CA 鍵の配布と同じツールで配布してください。KRL が古いままのサーバーは、失効を知らされていないものを受け入れてしまいます。これも、証明書の有効期間を短く保ち、KRL を残りの数時間を補う安全網として扱うべき理由の 1 つです。
並べて比較
| authorized_keys | 証明書 | |
|---|---|---|
| アクセスの付与 | 各サーバーに鍵をコピー | 鍵に一度だけ署名 |
| アクセスの削除 | 各サーバーで鍵を削除 | 期限切れを待つか、KRL に追加 |
| 有効期限 | 行ごとに任意(expiry-time=) | すべての証明書に有効期間がある |
| ログ上の ID | 鍵のフィンガープリント | 鍵 ID とシリアル番号 |
| 制限 | 行ごとの from=、command=、restrict | 証明書ごとの source-address、force-command、拡張 |
| ホストの信頼 | ホストごとのフィンガープリント(TOFU) | @cert-authority の 1 行 |
| 最も重要な秘密情報 | 一元的なものはなし | CA の秘密鍵 |
| 運用すべき仕組み | なし | 発行、CA の保護、KRL の配布 |
authorized_keys の行ごとの制限も実際に機能します。同じサーバーで試した結果です。
restrict,from="10.0.0.0/8",command="echo backup only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
/home/deploy/.ssh/authorized_keys:1: Authentication tried for deploy with correct key but not from a permitted host (host=127.0.0.1, ip=127.0.0.1, required=10.0.0.0/8).
expiry-time="20261001" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
サーバーと人が数えるほどなら、restrict と expiry-time を付けた authorized_keys で十分です。証明書が真価を発揮するのは、オンボーディング、オフボーディング、ホストの再構築が毎週のように発生するようになったとき、あるいは、どの鍵かだけでなく誰がログインしたかを知る必要があるときです。
authorized_keys からの移行
全サーバーを一斉に切り替える日を設ける必要はありません。sshd は authorized_keys と信頼された CA を並行してチェックするので、移行期間中はどちらの方式でもログインできます。
- まず CA の信頼を展開する:
TrustedUserCAKeys、プリンシパルファイル、空の KRL をすべてのサーバーに配布し、authorized_keysはそのままにしておきます。 - 最初の 1 チームに証明書を発行し、数週間使ってもらいます。これで、各ログインが
ID ... (serial ...)付きでログに記録されるようになります。 - 取り残しを見つける:
authorized_keys経由のログインは、Accepted publickey for deploy from 127.0.0.1 port 58770 ssh2: ED25519 SHA256:tT5X32Wds5InYaLSh7mV+QRqAtxA4KXwlKV3hkf0YkIのように、証明書の ID なしで記録されます。その多くは自動化された処理で、CI ランナー、バックアップジョブ、監視などです。それぞれに専用の、有効期間が短いか用途を厳しく制限した証明書を発行してください。 - 正当な用途で使われなくなったら、
AuthorizedKeysFile noneで鍵ファイルを無効にします。署名サービスが停止した日に備えて、コンソールアクセスやオフラインで保管した別の緊急用 CA など、非常用のアクセス経路を残しておいてください。 - ホスト側は別に進める:ホスト鍵に署名し、クライアントのグローバルな
/etc/ssh/ssh_known_hostsに@cert-authority行を追加します。ホストごとの古いknown_hostsエントリは残しておいてかまいません。それらはホスト鍵が変わるまで機能し続けます。
実際の運用
- ID プロバイダーから証明書を発行する:署名サービスがユーザーの SSO ログイン(OIDC)を確認し、そのユーザーのプリンシパルを記載した、数時間有効な証明書を返します。CA 鍵に人が触れることはなく、退職すれば、どのサーバーにも手を加えることなく翌日までに SSH アクセスが失われます。
- 可能ならユーザー鍵をハードウェアに置く:
ssh-keygen -t ed25519-skで、FIDO2 で保護された鍵を作成できます。証明書は、他の鍵と同じようにこの鍵でも使えます。 - CA は重複期間を設けてローテーションする:新しいユーザー CA を
TrustedUserCAKeysの 2 行目として、または新しいホスト CA を 2 つ目の@cert-authority行として追加します。新しい CA から発行を始めてから、古い CA を削除します。 - ホスト側も監視する:証明書の有効期限が切れる前に、ホスト鍵に署名し直してください。ホスト証明書が期限切れになると、クライアントは通常のホスト鍵による確認に切り替わりますが、たいていそのホスト鍵を持っていません。
Certificate invalid: expired
No ED25519 host key is known for web01.example.test and you have requested strict checking.
Host key verification failed.
何も実行せずに鍵や証明書のファイルを確認するには、SSH キーインスペクターが使えます。OpenSSH の鍵をデコードし、種類、サイズ、フィンガープリントを表示します。SSH サーバー監査ツールは、ホスト鍵と証明書のアルゴリズムやポスト量子鍵交換を含め、サーバーが何を提供しているかを確認します。
出典
- OpenSSH のマニュアルページ:ssh-keygen(1)(CERTIFICATES と KEY REVOCATION LISTS のセクション)、sshd_config(5)(
TrustedUserCAKeys、AuthorizedPrincipalsFile、RevokedKeys、HostCertificate、PerSourcePenalties)、sshd(8)(authorized_keys と known_hosts の形式) - draft-ietf-sshm-cert:SSH 証明書の形式(OpenSSH の
PROTOCOL.certkeysの後継)、PROTOCOL.krl:KRL の形式 - OpenSSH のリリースノート:5.4(証明書)、6.2(KRL)、9.8(
PerSourcePenalties)
このページの出力はすべて、2026年10月20日に debian:trixie-20261005-slim 上の OpenSSH 10.0p2(Debian 13 のパッケージ)で得たもので、テスト用の鍵はコンテナ内で生成しました。