SSH + Google Authenticatorによる二段階(二要素)認証設定方法



●二段階(二要素)認証設定

 参考URL:5分でできる!SSH + Google Authenticator 二段階認証設定(CentOS7)
 参考URL:Linuxサーバーへのログインを2段階認証にしてみた

 Google Authenticatorを利用した二段階(二要素)認証設定に挑戦します。

 Google Authenticatorアプリのインストール

 手元のスマートフォンにGoogle Authenticator(認証コードの生成アプリ)をインストールします。

 Google Authenticator(iOS)
 Google Authenticator(Android)
 

 Google Authenticator PAMモジュールのインストール

 サーバ(CentOS7)に、Google Authenticator PAMモジュールをインストールします(EPELリポジトリからパッケージでインストールできます)。
 EPELリポジトリをインストールします。
$ sudo yum install epel-release
 Google Authenticator PAMモジュールをインストールします。
$ sudo yum install google-authenticator
 QRコードを表示するための準備
$ sudo yum install qrencode


●sshdの認証設定(パスワード認証の場合)

 sshdの認証に関する設定を行います。
 設定ミスがあるとサーバにSSHでログインできなくなってしまいます。
 設定のための作業用ウィンドウとは別のウィンドウで、あらかじめSSHログインしておいてください。
 SSHログイン時にパスワード認証に加えて、Google Authenticatorの認証コードが求められるように設定します。

 sshdの設定

 sshdのチャレンジレスポンス認証を有効にします(Google Authenticatorもチャレンジレスポンス認証の一種です)。
$ sudo vi /etc/ssh/sshd_config
ChallengeResponseAuthentication no
 ↓
ChallengeResponseAuthentication yes
sshdを再起動します。
sudo systemctl restart sshd


 PAMの設定

 続いて sshdの認証に Google Authenticator PAMモジュールを追加します。
 次の1行を最終行に追加します。
$ sudo vi /etc/pam.d/sshd

auth required pam_google_authenticator.so nullok echo_verification_code

 <モジュールオプションについて>

nullok
 Google Authenticator を設定していないユーザーが、認証コードなしでログインできることを許可します。SSHログインをパスワードで行なっている場合は、このオプションを付けないと SSHログインができなくなりますのでご注意ください。Google Authenticator の設定が終わったら、この nullok オプションは削除しても構いません。

echo_verification_code
 入力時に、認証コードを表示するようにします。認証コードの有効期間は30秒ですので表示しても問題ありません。認証コードの入力ミスを減らせますので、付けておくことをオススメします。

 Google Authenticatorの設定

 Google Authenticatorを設定します。
 サーバにGoogle Authenticatorを設定するユーザでSSHログインして、下記コマンドを実行すると対話形式でGoogle Authenticatorの設定が開始します。
$ su - test
$ google-authenticator
以下のように色々聞かれますが、すべて「y」(はい)でOKです。

Do you want authentication tokens to be time-based (y/n) y

(y の場合は時間ベースの認証コードを、n の場合はカウンターベースの認証コードを生成します。)

Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/ \
test@www.bigbang.mydns.jp%3Fsecret%$KIIU87G43K8EWN9HWPFL%26issuer%3Dwww.bigbang.mydns.jp

(ここにQRコードが表示される) ← スマートフォン等にQRコードを読み込み、Google Authenticatorに登録します。

Your new secret key is: VMK59PQ15GFSNF8SBF9FKQIFJ4
Enter code from app (-1 to skip): ****** ← 登録されたアカウントを選択し、6桁の数字を入力
Code confirmed
Your emergency scratch codes are:
  26932109
  60972382
  20753523
  27097112
  92810169

Do you want me to update your "/home/test/.google_authenticator" file? (y/n) y

(Google Authenticator の秘密鍵や設定情報を以下の場所に保存してもよいかの確認です。)

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y

(同じ認証コードを複数回使うことを禁止します。これにより30秒ごとに1回のログインに制限されます。)

By default, a new token is generated every 30 seconds by the mobile app.
In order to compensate for possible time-skew between the client and the server,
we allow an extra token before and after the current time. This allows for a
time skew of up to 30 seconds between authentication server and client. If you
experience problems with poor time synchronization, you can increase the window
from its default size of 3 permitted codes (one previous code, the current
code, the next code) to 17 permitted codes (the 8 previous codes, the current
code, and the 8 next codes). This will permit for a time skew of up to 4 minutes
between client and server.
Do you want to do so? (y/n) y

(クライアント(Google Authenticatorをインストールしたスマートフォン)とサーバで最大4分の時間のずれを許容します。
スマートフォンの時間がずれることは稀にありますので y にしておくことをオススメします。)

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting? (y/n) y

(30秒ごとに3回までのログイン試行に制限します。

すなわち認証コードを3回間違えるとその認証コードは無効になるということです。 y にする場合は、あわせてGoogle Authenticator PAMモジュールのecho_verification_codeオプションを付けておきましょう。

 上記で表示されたQRコード(バーコード)をGoogle Authenticatorアプリでスキャンして登録します。
 QRコードが表示されない場合は、表示されたURLにアクセスすればQRコードを表示できます。
 また、QRコードの下にGoogle Authenticatorアプリが使えなくなった時のための緊急コード(上記赤字部分)が表示されますので、安全な場所に控えておきます。

 Google Authenticatorの設定は、以上です。

 Google Authenticatorを設定したユーザでSSHログインすると、パスワード認証の後に、Google Authenticatorの認証コードが求められるようになります。
$ ssh www.bigbang.mydns.jp -l test
Password: 
Verification code: 123456 ← 認証コード


●sshdの認証設定(公開鍵認証の場合):他サービスに影響有り

 SSHの公開鍵認証を使っている場合は、Google Authenticatorの設定を行っても認証コードを求められませんので、sshdとPAMに追加の設定が必要になります。

 sshdの設定

 sshdの設定ファイルに次の1行を追加します(パスワード認証は無効になりますので注意してください)。
$ sudo vi /etc/ssh/sshd_config

AuthenticationMethods publickey,keyboard-interactive
 sshdを再起動します。
$ sudo systemctl restart sshd
 この設定により、SSHログインの際に公開鍵認証(publickey)に加えて、Google Authenticatorの認証コード(keyboard-interactive)の認証も求められるようになります。
 ただし、keyboard-interactiveにはパスワード認証も含まれますので、公開鍵認証 → パスワード認証 → 認証コード の三段階認証になります。

 PAMの設定

 パスワード認証を求められないようにするには、以下のように設定します。
 先ほど追加したGoogle Authenticator PAMモジュールを無効にします。
$ sudo vi /etc/pam.d/sshd

auth required pam_google_authenticator.so nullok echo_verification_code
  ↓
# auth required pam_google_authenticator.so nullok echo_verification_code
 続いて、パスワード認証を無効にして、その下の行にGoogle Authenticator PAMモジュールを追加します。
$ sudo vi /etc/pam.d/password-auth

auth sufficient pam_unix.so nullok try_first_pass
 ↓

#auth sufficient pam_unix.so nullok try_first_pass
---(以下を追加)---
auth sufficient pam_google_authenticator.so nullok echo_verification_code

※赤字部分のように変更すると他のサービス(FTP、MAIL)に影響が出ますので変更しないことを推奨します。
 したがって、公開鍵認証とGoogle Authenticatorの認証コードが求められます。

 以上で、SSHの公開鍵認証を使っている場合でも Google Authenticator の認証コードのみが求められるようになります。

●sshdの認証設定(公開鍵認証の場合):他サービスへの影響回避

 他のサービス(FTP、MAIL)に影響が出ないよう対策してみました。
 ただし、どうしても、公開鍵認証 → 認証コード の認証(理想は認証コードのみ)となってしまいます。
 また、sshd_configでMatchを利用してユーザ毎にパスワード認証、認証コードによる認証を細かく制御したかったのですが想定とおりの動作となりませんでした。

 sshdの設定

 特定の接続元からのアクセスに対し設定しました。
$ sudo vi /etc/ssh/sshd_config

※末尾に追加すること
Match Address 192.168.0.0/24
  PasswordAuthentication no  #####  ← パスワード認証が初めからnoの場合、設定不要
  AuthenticationMethods keyboard-interactive
 sshdを再起動します。
$ sudo systemctl restart sshd


 PAMの設定

 次の1行を最終行に追加します。
$ sudo vi /etc/pam.d/sshd

auth required pam_google_authenticator.so nullok echo_verification_code
 pam_google_authenticator.so及びnullokが設定されていますので、パスワード認証のあと認証コードが求められます。
 続いてpassword-authを設定します。
$ sudo vi /etc/pam.d/password-auth

auth        sufficient    pam_unix.so nullok try_first_pass #####  ← 元々設定されているはずです。
 以上で他のサービス(FTPやメール)に影響を与えること無く二段階(二要素)認証をできるようになりました。

●注意点

 二段階(二要素)認証を有効にした場合、ユーザに関係なく(root含む)Google Authenticator の認証コードを求められるようになります。
 これは二段階(二要素)認証を有効化したサーバに対し、パスフレーズ無し公開鍵を使用してファイルバックアップ等を実施していた場合でも認証コードを求められるようになり自動バックアップが出来なくなります。
 これを避けるにはユーザにより、公開鍵認証方式とするのか二段階(二要素)認証方式とするのか分ける必要があります。

 また、「auth sufficient pam_unix.so nullok try_first_pass」をコメントアウトするとFTPやメールアカウントにログインできなくなることも確認しました。
 SSHを使用するときのみ二段階(二要素)認証(pam_google_authenticator.so)を有効、他の認証は通常の認証(pam_unix.so)というように切り分けできるといいのですが。

●sshdの認証設定(パスワード認証、二段階(二要素)認証をユーザ毎に住み分ける):2023年2月現在、これを採用

 参考URL:二段階(二要素)認証と通常のパスワード認証をユーザによって分ける方法

 Rocky Liunx 8では想定したとおり動作しています(2023年2月現在)。
 Rocky Liunx 9では想定したとおり動作せず、sshd_configの記載を一部変更しました(2023年2月現在)。 

 「Google Authenticatorアプリのインストール」及び「Google Authenticator PAMモジュールのインストール」を参考にGoogle Authenticatorをインストールします。

 PAMの設定

 ユーザ及び接続元のネットワークアドレスによって下記のような認証方法を設定します。

 1.特定のアカウントでローカルネットワーク、スマートフォンから接続する場合、公開鍵認証を必須にするための設定
 2.特定のアカウントで会社から接続する場合、公開鍵認証→認証コードの順を必須にするための設定

 設定ファイルの名前をgoogle-authとして新規作成します。
$ sudo vi /etc/pam.d/google-auth

#%PAM-1.0
auth required pam_env.so
auth sufficient pam_google_authenticator.so try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet
auth required pam_deny.so
 SSHでの認証でgoogle-authを読み込むように、/etc/pam.d/sshdを設定します。

 Google Authenticatorによる認証後、更に、パスワード認証する場合は、パスワード認証(auth substack password-auth)の直前にGoogle Authenticatorの認証設定(auth substack google-auth)を記載します。
 今回、パスワード認証は無効にしますので、下記のようにコメントアウトします。
$ sudo vi /etc/pam.d/sshd

auth       substack     google-auth ← 挿入
#auth       substack     password-auth ← 上記挿入後、コメントアウト
※この設定後、sshd_configでPasswordAuthentication yesとしても、パスワード認証が出来なくなります。
 記載順序に注意が必要です。必ず、password-authよりも前にgoogle-authを追記してください。

 sshdの設定

 sshd_configでChallengeResponse認証を使うように設定します。
$ sudo vi /etc/ssh/sshd_config

ChallengeResponseAuthentication yes ← Rocky Liunx 9でこの設定は無かった
 sshdを再起動します。
$ sudo systemctl restart sshd
 rootはcronを使用してパスフレーズないでログインする場合があるため、例外を適用します。
 PermitRootLoginはwithout-passwordを設定したうえで、プライベートIPからアクセスされたときのみ公開鍵認証できるようにします。
$ sudo vi /etc/ssh/sshd_config

PermitRootLogin without-password

更に末尾に下記を追記
Match User root Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
  PubKeyAuthentication yes

$ sudo systemctl restart sshd


 Rocky Liunx 8とRocky Liunx 9での動作の違い

 Rocky Liunx 8.7とRocky Liunx 9.1でSSHログイン時の動作が異なったのでメモしておきます。

 Rocky Liunx 8での設定内容(2023年2月現在)
# 2段階認証の設定。sshd の認証設定(公開鍵認証の場合、下記を有効化する。)
AuthenticationMethods publickey,keyboard-interactive

# 公開鍵認証を有効とする
PubKeyAuthentication yes

# rootのログインを二段階認証ではなく公開鍵認証にするための設定
Match User root Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
  PermitRootLogin without-password
  AuthenticationMethods publickey

# hogehogeのアカウントでローカルネットワーク、スマートフォンから接続する場合、公開鍵認証を必須にするための設定
Match User hogehoge Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,XXX.XXX.0.0/YY
  PubKeyAuthentication yes
  AuthenticationMethods publickey

# hogehogeのアカウントで会社から接続する場合、認証コード及び公開鍵認証を必須にするための設定
Match User hogehoge Address XXX.XXX.XXX.0/19,XXX.XXX.0.0/10
# 下記3行の記載順だと認証コード、パスワード認証でのログインになる(とりあえず、Google Authenticatorを利用できるのでこのまま利用している)
  AuthenticationMethods publickey,keyboard-interactive
  PubKeyAuthentication yes
  PasswordAuthentication no

 Rocky Liunx 9での設定内容(2023年2月現在)

 Alma Liunx 9は想定したとおり動作することを確認しました。(2023年2月現在)
 Alma Liunx 9のsshd_configをRocky Liunx 9に上書きしましたが、挙動は変わりませんでした。
# 2段階認証の設定。sshd の認証設定(公開鍵認証の場合、下記を有効化する。)(20230220)
#AuthenticationMethods publickey,keyboard-interactive ← この設定を有効にすると、何故かsshdが起動しない。OSがRocky Linux 9の場合、設定は無効化。
AuthenticationMethods publickey ← OSがRocky Linux 9の場合、必要な設定。

# 公開鍵認証を有効とする(20230220)
PubKeyAuthentication yes

# rootのログインを二段階認証ではなく公開鍵認証にするための設定
Match User root Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
  PermitRootLogin without-password
  AuthenticationMethods publickey

# hogehogeのアカウントでローカルネットワーク、スマートフォンから接続する場合、公開鍵認証を必須にするための設定
Match User hogehoge Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,XXX.XXX.0.0/YY
  PubKeyAuthentication yes
  AuthenticationMethods publickey

# hogehogeのアカウントで会社から接続する場合、認証コード及び公開鍵認証を必須にするための設定
Match User hogehoge Address XXX.XXX.XXX.0/19,XXX.XXX.0.0/10
# 下記3行の記載順だと認証コード、パスワード認証でのログインになる
  #AuthenticationMethods publickey,keyboard-interactive ← この設定が何故か認識されない。Rocky Liunx 8では問題なく認識される。
  #AuthenticationMethods publickey ← この設定の場合、公開鍵認証でログインできた。
  AuthenticationMethods keyboard-interactive ← この設定でGoogle Authenticatorを利用してログインできた。OSがRocky Linux 9の場合、必要な設定。
  KbdInteractiveAuthentication yes ← Google Authenticatorを利用する場合、この設定も必要。OSがRocky Linux 9の場合、必要な設定。Rocky Liunx 8では、この設定はなかった。
  PubKeyAuthentication yes
  PasswordAuthentication no
 上記で設定したRocky Liunx 9のログイン動作は下記のようになっています。
 下記動作は、/etc/pam.d/sshdで「auth substack password-auth」を有効にしている場合の動作です。
 ・Verification Code:(1回目の数字を入力)
 ・Password:(パスワード認証用のパスワード入力)
 ・Verification Code:(2回目の数字を入力) ← 1回目とは異なる数字を入力

 /etc/pam.d/sshdで「auth substack password-auth」を無効にした場合の動作は下記のようになりました。確認対象OS:Rocky Linux release 9.1
 ・Passphrase:(秘密鍵のパスワード入力)
 ・Verification Code:(1回目の数字を入力)
 ・Verification Code:(2回目の数字を入力) ← 1回目とは異なる数字を入力

 /etc/pam.d/sshdで「auth substack password-auth」を無効にした場合の動作は下記のようになりました。確認対象OS:Rocky Linux release 9.2
 ・Verification Code:(1回目の数字を入力)
   (何か挙動が変わっていた。)

 二段階(二要素)認証を使用するユーザ(gauser)とパスワード認証を使用するユーザ(user)の設定

 gauserについては、google-authenticatorコマンドでシークレットキーを作成します。

 「Google Authenticatorの設定」を参照して設定します。
$ username=user
sudo su - $username -c "yes | google-authenticator"
  :
 (QRコード等が表示される)
  :
$ sudo ls -l /home/$username/.google_authenticator
# secret keyの確認
$ sudo head -1 /home/$username/.google_authenticator
# emergency scratch codesの確認
$ sudo tail -5 /home/$username/.google_authenticator
 userでログインするには、ChallengeResponse認証を指定してログイン試行します。すると認証コードを求められます。その後、元々のユーザのパスワードが求められます。
 userで通常のパスワードログインしようとしても、設定不足のためパスワードを入力してもエラーとなりログインできません。


 特定のユーザを二段階(二要素)認証を回避してパスワード認証させるための設定

 userが二段階(二要素)認証ではなく通常のパスワードでログインできるようにするため、/etc/pam.d/google-authの設定を一部変更します。
 以下の設定部分で/home/$username/.google_authenticatorを読もうとして、ファイル読み込みに失敗するため、この設定に行く前に/etc/pam.d/google-authの処理を抜けるようにしたい。
auth sufficient pam_google_authenticator.so try_first_pass
 PAMでは下記のとおりの処理の継続を制御できます。
  • requisite:処理を継続するには、requiredモジュールが"success(成功)"を返さなければならない。このモジュールが失敗すると、PAMライブラリは"failure(失敗)"を返し、スタック内のほかのモジュールはそれ以上実行されない
  • required:これも必須のモジュールであるが、失敗した場合、PAMのAPIは"failure"を返すものの、同じスタック内のモジュールの実行は継続される
  • sufficient:このモジュールが成功すると、PAMライブラリは、その他のモジュールを実行することなく直ちに"success"を返す。
  • optional:このモジュールの成否に関わらず、スタック内のモジュールの実行は継続される。すべてのモジュールの実行が完了した時点でPAMライブラリの成否が確定していない場合、成功したoptionalモジュールが1つでもあれば承認が与えられる
 そのため、ログインしようとしているユーザがuserであればgoogle-auth内の処理はそれ以上行わない(つまりsufficient)という記述を、pam_google_authenticator.soの前にしてあげればよい。
 pam_succeed_if.soはユーザIDやグループIDなどの条件を指定できるため、このモジュールを使用してユーザ名がuserであるかどうかをチェックします。 user01、user02のようにパスワード認証を使うユーザが複数いて、pauthuserから始まるユーザをワイルドカードを使って一括して設定したい場合、下記のようになります。
auth sufficient pam_succeed_if.so login =~ user*
 これをpam_google_authenticator.soの直前の行に記載する必要があるので、汎用的にするなら以下のように/etc/pam.d/google-authを変更すればよい。
$ sudo vi /etc/pam.d/google-auth
#%PAM-1.0
auth required pam_env.so
auth sufficient pam_succeed_if.so login =~ user*
auth sufficient pam_google_authenticator.so try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet
auth required pam_deny.so
 /etc/pam.d/google-authを書き換えた後に、gauserとuserでログインすると、それぞれ二段階(二要素)認証と普通のパスワード認証でログインできるようになります。

●sshdの認証設定(公開鍵認証、二段階(二要素)認証をユーザ毎に住み分ける)

 参考URL:二段階認証でLinuxサーバへログインする方法

 調整中