SSH鍵認証のみの使用では、必ずしも完全な安全とは言えません。もしローカルPCがマルウェアに感染したり、誤って秘密鍵(Private Key)をGitHubにプッシュしてしまった場合、サーバーは即座に危険に晒されます。SSHレイヤーで2要素認証(2FA)を有効化することは、このリスクを阻止するための最も迅速かつ確実な方法です。
Quick start: 5分で完了するSSH 2FA設定
以下の手順は、Ubuntu 20.04、22.04、および24.04 LTSにそのまま適用できます。
ステップ1:Google AuthenticatorのPAMモジュールをインストール
パッケージリストを更新し、対応するPAMライブラリをインストールします:
sudo apt update
sudo apt install libpam-google-authenticator -y
ステップ2:シークレットキーとQRコードの生成
2FAを適用したいユーザー自身で以下のコマンドを実行します(sudoは付けないでください):
google-authenticator
対話形式でいくつかの設定確認が表示されます。以下のように順番に選択してください:
- Do you want authentication tokens to be time-based (y/n)? → 時間ベースのTOTPコードを使用するため、
yを選択します。 - 画面にQRコード、シークレットキー、および5つの緊急用スクラッチコード(emergency scratch codes、各8桁)が表示されます。Google Authenticator、2FAS、またはAegisアプリを開いてすぐにQRコードをスキャンしてください。また、5つの緊急コードをパスワードマネージャー等に保存しておくのを忘れないでください。
- 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)? → リプレイ攻撃を防ぐため、
yを選択します。これにより、6桁のコードは30秒間に1回のみ使用可能になります。 - Increase the time-skew window (y/n)? →
nを選択します。判定の許容時間をデフォルト(30秒)のまま維持し、攻撃者に与える猶予を広げないようにします。 - Enable rate-limiting (y/n)? → レートリミット(30秒間に最大3回までのログイン試行制限)を有効にするため、
yを選択します。
ステップ3:PAM(Pluggable Authentication Modules)の設定
SSHデーモンのPAM設定ファイルを開きます:
sudo nano /etc/pam.d/sshd
ファイルの末尾に次の行を追加します:
auth required pam_google_authenticator.so nullok
ヒント: nullokパラメータは非常に重要です。まだgoogle-authenticatorコマンドを実行していないユーザーでもパスワードや鍵で通常通りログインできるため、ユーザーごとに順次導入していく際のアカウント締め出し事故を防ぐことができます。
ステップ4:SSHデーモン設定の更新
続けてOpenSSHの設定ファイルを開きます:
sudo nano /etc/ssh/sshd_config
以下の2つの設定項目を探し、値をyesに変更します:
KbdInteractiveAuthentication yes
UsePAM yes
(注:Ubuntu 22.04より前の古いバージョンでは、このディレクティブはChallengeResponseAuthentication yesとなっています)。
ステップ5:SSHサービスの再起動と動作確認
sudo systemctl restart ssh
重要な注意点: 現在開いているターミナルタブはまだ絶対に閉じないでください。新しいウィンドウを開いてログインテストを行います:
ssh username@your_server_ip
作業シェルが開く前に、サーバーからVerification code:の入力が求められます。
動作の仕組みを簡単に理解する
SSH経由の2FA認証プロセスは、主に2つのレイヤーで処理されます:
- OpenSSH Server (sshd): 接続を受け取り、
KbdInteractiveAuthenticationメカニズムを介して認証処理をPAMに引き渡します。 - PAM Module (libpam-google-authenticator): ホームディレクトリ内の
~/.google_authenticatorファイルを読み込みます。モジュールは静的シークレットキーと現在時刻のタイムスタンプ(30秒周期)をハッシュ化し、TOTPアルゴリズム(RFC 6238)に基づいてOTPコードを算出します。モバイルアプリに表示された6桁のコードと一致すれば、SSHセッションが承認されます。
高度な設定:SSH鍵と2FAコードの両方を必須化する
デフォルトでは、SSH鍵による認証が成功すると、OpenSSHはOTPコードの入力をスキップします。サーバーを最高レベルのセキュリティにするには、秘密鍵とTOTPコードの両方の提示を必須にする必要があります。
まず、/etc/pam.d/sshdを開きます。SSH鍵認証時にUbuntuアカウントのパスワード入力を求められないようにするため、@include common-authの行をコメントアウトします:
# @include common-auth
auth required pam_google_authenticator.so
次に、/etc/ssh/sshd_configを編集して、必須となる認証シーケンスを定義します:
PubkeyAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
構文エラーを防ぐため、設定を適用する前にSSH設定のシンタックスチェックを行います:
sudo sshd -t && sudo systemctl restart ssh
これにより、クライアントはまずSSH鍵認証を成功させる必要があります。その直後にサーバーがVerification code:のプロンプトを表示します。いずれかの要素が欠けている場合、接続は即座に切断されます。
内部ネットワークまたはVPN経由アクセス時の2FA免除
インターネット経由のSSH接続には2FAを必須としつつ、社内のWireGuardやOpenVPNに接続済みの場合はOTPをスキップさせたい場合は、/etc/ssh/sshd_configでMatchブロックを使用します:
# 外部からのすべての接続に対して鍵認証 + 2FAをデフォルトで必須化
AuthenticationMethods publickey,keyboard-interactive
# 内部VPNのIPレンジに対してはOTPを免除(SSH鍵のみで可)
Match Address 10.8.0.0/24,192.168.1.0/24
AuthenticationMethods publickey
運用の実践ノウハウとサーバーからの締め出し防止策
- 常に救済用セッションを1つ維持しておく:
/etc/pam.d/や/etc/ssh/sshd_configを編集する際は、root権限を持つSSHセッションを開いたままにしておきます。新しいタブでログインに失敗しても、既存のセッションから即座に修正できます。 - NTPによるシステム時刻の同期: TOTPは30秒単位のタイムスタンプに基づいて計算されます。サーバーとスマートフォンの時刻が30秒以上ずれると、正しいOTPコードを入力してもエラーになります。以下のコマンドで時刻同期の状態を確認してください:
timedatectl status
# 確認項目:"NTP service: active" および "System clock synchronized: yes" であること
- 5つの緊急用スクラッチコード(Emergency Scratch Codes)を安全に保管する: スマートフォンの紛失やアプリ破損時、この5つのコードが唯一の復旧手段となります。各コードは1回のみ使用可能です。1PasswordやBitwarden、チームのVault等に必ず保存してください。
- 万が一締め出された場合の2FA解除手順: スマートフォンを紛失し、バックアップコードもない場合は、VPSプロバイダーのWebコンソール(AWS EC2 Serial Console、DigitalOcean Droplet Console、Proxmoxなど)からログインし、設定ファイルを削除します:
sudo rm /home/username/.google_authenticator

