なぜSSH鍵認証だけでは不十分なのか?
多くのシステム管理者(sysadmin)はパスワード認証を無効化し(PasswordAuthentication no)、SSH鍵認証のみを利用しています。これは一般的なパスワード認証と比べてはるかに安全です。しかし、開発者のPCがトロイの木馬に感染したり、パスフレーズ未設定のid_ed25519ファイルがマルウェアに盗まれたり、誤って公開GitHubリポジトリに鍵をプッシュしてしまったりした場合、依然としてリスクは残ります。
攻撃者は秘密鍵(Private Key)さえ手に入れれば、そのままサーバーへ直接侵入できてしまいます。この脅威を未然に防ぐには、2要素認証(2FA)のレイヤーを追加するのが効果的です。これにより、ログイン時には「手元のマシンにあるSSH鍵」と「スマートフォン上でリアルタイムに生成される6桁のOTPコード」の両方が必須となります。
本記事では、CentOS Stream 9の社内サーバー環境において、Google AuthenticatorのPAMモジュールを用いて実際に構築した手順を紹介します。この仕組みはTOTP標準(RFC 6238)に準拠しており、Google Authenticator、Microsoft Authenticator、1Password、Authyなどの各種アプリと完全に互換性があります。
Google Authenticator PAMのインストール
google-authenticatorパッケージはCentOS Stream 9のデフォルトリポジトリには含まれておらず、EPEL(Extra Packages for Enterprise Linux)から提供されています。
ステップ1:EPELからパッケージをインストール
sudo権限で以下のコマンドを実行します:
# EPELリポジトリの追加
sudo dnf install -y epel-release
# Google Authenticator PAMモジュールとQRコード生成ライブラリのインストール
sudo dnf install -y google-authenticator qrencode-libs
ステップ2:ユーザーごとにシークレットキーとOTPコードを生成
注意:必ずSSHアクセスを許可したい対象ユーザーに切り替えてから初期化コマンドを実行してください。rootアカウントを使って一般ユーザー用のキーを生成するのは避けてください。
google-authenticator
Google Authenticatorの対話型スクリプトにより、5つの設定項目について質問されます:
- Do you want authentication tokens to be time-based (y/n)? →
yを入力してTOTP方式(30秒ごとにコード更新)を使用します。 - 画面にQRコードとともにSecret keyおよび5 emergency scratch codes(緊急用救済コード)が表示されます。すぐにスマートフォンのアプリでQRコードをスキャンし、端末紛失時に備えて5つの救済コードをBitwardenや1Passwordなどのパスワードマネージャーに保管してください。
- Do you want me to update your "~/.google_authenticator" file (y/n)? →
yを入力して設定ファイルをホームディレクトリに書き込みます。 - Do you want to disallow multiple uses of the same authentication token (y/n)? → リプレイ攻撃を防ぐため(同一OTPコードを1回のみ有効とするため)、
yを入力します。 - By default, a new token is generated every 30 seconds… (y/n)? → 標準の許容時間幅(最大±30秒のずれを許容する3コード分)を維持するため、
nを入力します。サーバーとスマートフォンの時刻同期が著しくずれる環境でのみyを選択してください。 - Do you want to enable rate-limiting (y/n)? →
yを入力して30秒間に最大3回までの試行制限を有効にし、OTPに対する総当たり攻撃(ブルートフォース)を防ぎます。
PAMおよびSSH Daemonの設定
~/.google_authenticatorが生成されたら、ログイン時にOTP入力を強制するようSSHDとPAMを設定します。
ステップ1:PAMにモジュールを登録
/etc/pam.d/sshdファイルを開きます:
sudo vi /etc/pam.d/sshd
ファイルの先頭(他のauthルールの前)に次の行を追加します:
auth required pam_google_authenticator.so nullok
実践のヒント:nullokオプションを付与しておくと、まだOTP初期化コマンドを実行していないユーザーでも、従来どおりSSH鍵のみでログイン可能です。チームメンバー全員が2FAコードの登録を完了した段階でnullokを削除し、すべてのユーザーに対してOTP認証を必須化してください。
ステップ2:CentOS 9でのOpenSSH設定
CentOS Stream 9(OpenSSH 8.7以降)では、メインの設定ファイルを直接編集するのではなく、/etc/ssh/sshd_config.d/ディレクトリ内に個別設定ファイルを作成することが推奨されます:
sudo vi /etc/ssh/sshd_config.d/2fa.conf
2fa.confファイルの内容:
# チャレンジレスポンス認証を有効化(SSHクライアントでOTP入力プロンプトを表示するため)
KbdInteractiveAuthentication yes
# PAMを有効化
UsePAM yes
# 2段階認証を強制:まず公開鍵認証を通過させ、その後にOTP入力を要求
AuthenticationMethods publickey,keyboard-interactive
AuthenticationMethods publickey,keyboard-interactiveを設定することで、クライアントが有効なSSH鍵を持っていない場合、サーバーはOTPプロンプトすら表示せず即座に接続を拒否します。
ステップ3:構文チェックとSSHDの再起動
構文エラーによるSSHDの起動失敗を防ぐため、サービスを再起動する前に必ず設定ファイルの構文テストを実施してください:
sudo sshd -t
エラーや警告が表示されなければ、サービスを再起動します:
sudo systemctl restart sshd
極めて重要な注意点:現在のSSH接続セッションは絶対に閉じないでください!ローカルPCで別のターミナルタブを開き、まず接続テストを行います。万が一設定に誤りがあっても、既存のセッションが維持されていればサーバーから締め出される(ロックアウトされる)ことなく即座に修正できます。
接続テストとログの監視
1. 実際のログインテスト
手元の端末でターミナルを開き、サーバーにSSH接続します:
ssh username@your-server-ip
認証の流れは以下のようになります:
- クライアントが自動的にSSH鍵認証に成功します。
- サーバーから
Verification code:というプロンプトが返されます。 - スマートフォンの認証アプリに表示されている6桁の数字を入力してEnterキーを押すと、シェルにアクセスできます。
2. 認証ログの確認
PAMモジュールが正しく動作しているかの確認や、誤ったコードが入力された原因を調査するには、journalctlでリアルタイムログを監視します:
sudo journalctl -u sshd -f
ログイン成功時には、以下のようなログが出力されます:
Accepted keyboard-interactive/pam for user from 192.168.1.50 port 52341 ssh2
OTPコードを間違えた場合は、Invalid verification codeやFailed keyboard-interactive/pamといったログが記録されます。このログ出力を利用して、3回連続で失敗したIPを自動遮断するようにFail2banを設定することも可能です。
3. スマートフォン紛失時や機種変更時の対処法
認証アプリを開けずOTPコードが取得できない場合:
- 初期設定時に保存した5つのEmergency scratch codes(8桁の緊急用コード)のうち1つを入力します。各コードは1回限り使用可能です。
- サーバーにログインできたら、既存の設定を削除して新しいQRコードを再生成します:
rm -f ~/.google_authenticator
google-authenticator
わずか5〜10分程度の設定作業ですが、Linuxサーバー環境のセキュリティレベルを飛躍的に向上させ、秘密鍵ファイルの漏洩による不正侵入リスクを完全に排除できます。

