背景とPAMが重要である理由
SSH経由でLinuxマシンにログインしたり、sudoを実行したり、suでユーザーを切り替えたりするたびに、PAM(Pluggable Authentication Modules)が認証の門番としての役割を果たしています。Linuxでは、各デーモンに個別の認証ルーチンを実装させるのではなく、その処理をPAMに委ねています。PAMは、ユーザー向けサービスと背後にあるセキュリティバックエンドを仲介するモジュール式のブローカーとして機能します。
PAMは、認証ワークフローを4つの異なる機能(ファシリティ)に整理しています。
- auth: 主にパスワードやTOTP検証トークンの入力を促すことで、ユーザーの本人確認を行います。
- account: アクセス資格を確認します。パスワードの有効期限、利用可能な時間帯、アカウントのロック状態などを検証します。
- password: トークンのローテーションや複雑性の強制など、認証情報の更新を管理します。
- session: ユーザーストレージのマウント、ulimitの適用、監査ログの更新など、シェルセッションの開始・終了に伴うタスクを処理します。
SSH鍵のみに依存した運用は一般的な手法です。しかし、パスフレーズが設定されていない鍵が入ったノートPCを紛失した場合、インフラが危険に晒されることになります。PAMのauthスタックにTOTP(Time-based One-Time Password)モジュールを追加することで、この問題を解決できます。攻撃者は秘密鍵ファイルだけでなく、モバイル端末に表示される一時的な6桁のコードの両方を手に入れなければ侵入できなくなります。
エッジワークロードを実行する2 vCPUのUbuntuノード群において、この構成ではローカルでのトークン検証が約8〜12ミリ秒で完了しました。これにより、ハンドシェイクごとにリモートのディレクトリサーバーに問い合わせていた際に発生していた250ミリ秒のレイテンシスパイクを解消できました。
インストール
ここでは、オープンソースのGoogle Authenticator PAMパッケージを使用します。このパッケージはRFC 6238に準拠しており、Aegis、1Password、Google Authenticator、Bitwardenなどの標準的な認証アプリと確実に連携できます。
DebianおよびUbuntu
リポジトリのインデックスを更新し、PAMモジュールをインストールします。
sudo apt update
sudo apt install -y libpam-google-authenticator
RHEL、Rocky Linux、AlmaLinux
まずEPELリポジトリを有効化してから、パッケージをインストールします。
sudo dnf install -y epel-release
sudo dnf install -y google-authenticator
設定手順
SSHのセキュリティを強化するには、ユーザーシークレットの生成、PAMサービス定義の更新、OpenSSHデーモンの再設定という3つの明確な段階が必要です。
ステップ1:TOTPシークレットキーの生成
SSHアクセスを必要とする一般ユーザー(非rootユーザー)としてログインします。ターミナルで直接生成ウィザードを実行してください。
google-authenticator -t -d -f -r 3 -R 30 -q
各フラグの役割は以下の通りです。
-t: カウンタベースのトークン(HOTP)ではなく、標準のタイムベーストークン(TOTP)を設定します。-d: リプレイ攻撃を防ぐため、トークンの再利用をブロックします。-f: ファイルパスの確認を求めず、~/.google_authenticatorに直接状態を書き込みます。-r 3 -R 30: レート制限を適用します。30秒あたり最大3回までの認証試行に制限します。-q: 静かに実行し、シークレット生成時に対話型の確認をスキップします。
ユーティリティによってBase32シークレット、ASCII形式のQRコード、および5つの緊急用バックアップコードが出力されます。このシークレットをスマートフォンの認証アプリに追加してください。8桁の緊急用コードはKeePassXCなどのオフラインパスワードマネージャーに保管しておきましょう。スマートフォンが故障・紛失した際、アクセスを取り戻す唯一の手段となります。
ステップ2:SSH用のPAM設定
テキストエディタでSSHのPAMプロファイルを開きます。
sudo nano /etc/pam.d/sshd
ほとんどのデフォルト設定には、標準的なパスワードプロンプトが含まれています。DebianやUbuntuでは@include common-authを、RHELではauth substack password-authを探してください。Linuxユーザーパスワードを要求せず、SSH鍵+TOTPのみを認証要件とする場合は、該当するインクルード行をコメントアウトします。
# 標準のUNIXパスワードへのフォールバックを無効化
# @include common-auth
次に、認証モジュールをファイルの末尾に追加します。
auth required pam_google_authenticator.so nullok
nullokパラメータを指定すると、ユーザーがTOTPトークンを生成する前でも通常通りログインできるようになります。これにより、導入移行時の意図しない締め出し(ロックアウト)を防げます。チーム全員がQRコードの登録を完了したら、nullokを削除して全ユーザーにMFAを強制してください。
ステップ3:OpenSSHで鍵認証+MFAを強制する
続いて、/etc/ssh/sshd_configのデーモン設定を編集します。
sudo nano /etc/ssh/sshd_config
OpenSSHとPAMを連携させるために、以下のディレクティブを設定します。
# PAMおよび対話型入力プロンプトを有効化
UsePAM yes
KbdInteractiveAuthentication yes
PasswordAuthentication no
# 最初に公開鍵認証を要求し、その後にPAM TOTPプロンプトを要求
AuthenticationMethods publickey,keyboard-interactive
注意:OpenSSHのバージョンが8.7より古い場合は、KbdInteractiveAuthentication yesの代わりにChallengeResponseAuthentication yesを使用してください。
ここで最も重要なのがAuthenticationMethodsの行です。OpenSSHは公開鍵を検証した後にのみ、認証コードを入力するための対話型プロンプト(keyboard-interactive)を表示するようになります。
変更を適用する前に構文チェックを行います。問題がなければサービスを再起動してください。
sudo sshd -t && sudo systemctl restart sshd
ステップ4:アカウントロックアウトによるブルートフォース攻撃の防止
MFAは個々の認証情報を保護しますが、頻繁なブルートフォース攻撃はCPUリソースを無駄に消費します。pam_faillockを使用すれば、攻撃を抑制できます。/etc/security/faillock.confを開きます。
# 15分間に4回試行が失敗した場合にユーザーをロックアウト
deny = 4
fail_interval = 900
unlock_time = 600
even_deny_root
この設定により、ログインに4回連続で失敗すると、アカウントが10分間(600秒間)凍結されます。クレデンシャルスタッフィングツールを動かすボットネットの侵入試行を即座に遮断できます。
動作確認とトラブルシューティング
現在のrootターミナルセッションは開いたままにしておいてください。設定をテストするために、作業端末で新しいターミナルタブを開きます。万が一問題が発生した場合でも、既存のシェルから修正できるため締め出しを防ぐことができます。
ステップ1:ログインシーケンスのテスト
詳細ログを出力するオプション(-v)を指定してSSH接続を開始します。
ssh -v [email protected]
クライアントが秘密鍵を送信し、部分的な成功シグナルを受け取った後、動的トークンの入力を求めるプロンプトが表示されます。
部分的に認証に成功しました。
認証コード:
6桁の認証コードを入力してEnterキーを押します。正常に完了すれば、すぐにシェルが起動します。
ステップ2:リアルタイムのPAMログを確認する
ログインの発生に合わせてPAMイベントを追跡します。UbuntuやDebianシステムでは、認証ログをリアルタイムで確認します。
sudo tail -f /var/log/auth.log | grep -E "sshd|pam"
RHEL、Fedora、Rocky Linuxの場合は、systemdジャーナルを確認します。
sudo journalctl -u sshd -f
コードの入力を誤ると、PAMに明確な失敗理由が記録されます。
pam_google_authenticator(sshd:auth): ユーザー deployer の認証コードが無効です
198.51.100.24 ポート 48122 ssh2 からの deployer の keyboard-interactive/pam 認証に失敗しました
ステップ3:ロックアウト状態の確認と解除
エンジニアがトークンを打ち間違えてロックアウトが発生した場合は、失敗ステータスを確認します。
sudo faillock --user deployer
失敗した各試行のタイムスタンプ付きログが表示されます。ロックを解除して即座にアクセスを復旧するには、次のように実行します。
sudo faillock --user deployer --reset
pam_faillockとTOTPの必須化を組み合わせることで、サードパーティのSaaS型IDブローカーに依存することなく、Linuxホスト上で強固な多層防御(Defense-in-depth)を実現できます。

