Amazon Linux 2023 で Google Authenticator を試してみた

2024.03.18

はじめに

チームのツールに Amazon Linux 2 で構築した SSH サーバーがあり、公開鍵認証と Google Authenticator の認証で SSH 接続しているのですが、いずれ Amazon Linux 2023 に置き換えねばなるまいということで、ログイン周りの検証を行いました。

Amazon Linux 2 との違い

  • pam_tally2.so の代わりに pam_faillock.so を使う
  • google-authenticator が標準リポジトリでサポートされた(AL2ではepel)

pam_faillock.so は PAM (Pluggable Authentication Modules for Linux) に組み込んで使うモジュールで、認証失敗時の振る舞いをコントロールします。google-authenticator は 2023 年 9 月のアップデートで入りました。神対応!

検証環境

Amazon Linux 2023 の docker image (2023.3.20240304.0) Arm 版

関連するモジュール

  • google-authenticator.aarch64 1.09-5.amzn2023 @amazonlinux
  • pam.aarch64 1.5.1-8.amzn2023.0.3 @amazonlinux
  • systemd-pam.aarch64 252.16-1.amzn2023.0.1 @amazonlinux
  • openssh.aarch64 8.7p1-8.amzn2023.0.8 @amazonlinux
  • openssh-clients.aarch64 8.7p1-8.amzn2023.0.8 @amazonlinux
  • openssh-server.aarch64 8.7p1-8.amzn2023.0.8 @amazonlinux

ログインユーザーの準備

OS ユーザー

ログインテスト用の OS ユーザーを作成します。

# groupadd classmethod
# useradd user1 -d /home/user1 -s /bin/bash -g classmethod

~/.google_authenticator

OS ユーザーのホームディレクトリに Google Authenticator の初期設定をします。

# su - user1
$ google-authenticator -t -d -w 17 -u -f
Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?...

<<巨大 QR コード>>
                                                                        
Your new secret key is: XXXXXXXXXXXXXXXXXXX
Enter code from app (-1 to skip):

-1 かワンタイムパスワード(6桁の数字)を入れたら、emergency コードが表示されて終わりです。QR コードは MFA のツールに登録しておきます。(Google Authenticator、1password等)

Code confirmation skipped
Your emergency scratch codes are:
  17696864
  89470026
  77017408
  51576606
  69475647

ホームディレクトリに以下のファイルができます。

$ ls -la
(中略)
-r-------- 1 user1 classmethod  118 Mar 14 18:25 .google_authenticator

~/.ssh/authorized_keys

OS ユーザーのホームディレクトリに公開鍵の設定をします。

# su - user1
$ mkdir .ssh
$ chmod 700 .ssh

<<.ssh/authorized_keys に user1 の公開鍵を編集 >>

$ chmod 600 .ssh/authorized_keys
$ ls -la .ssh
total 12
drwx------ 2 user1 classmethod 4096 Mar 12 09:21 .
drwx------ 3 user1 classmethod 4096 Mar 14 18:33 ..
-rw------- 1 user1 classmethod  107 Mar 14 18:40 authorized_keys

サーバーの準備

/etc/pam.d/google-auth

Google Authenticator 認証時のふるまいをコントロールするファイルを新規作成します。

#%PAM-1.0
auth     requisite                        pam_faillock.so preauth
auth     [success=1 ignore=1 default=bad] pam_google_authenticator.so nullok debug
auth     [default=die]                    pam_faillock.so authfail
auth     sufficient                       pam_faillock.so authsucc
  • 2行目
    • ユーザーがロック状態にあるかチェックします。ロック状態なら認証処理を中止します。
  • 3行目
    • pam_google_authenticator.so が success か ignore を返せば 1 行飛ばして 5 行目の処理に移り、それ以外は認証失敗として 4 行目の処理に移ります。ignore はユーザーのホームディレクトリにファイル 「.google_authenticator」 が存在しない場合に発生します。
  • 4行目
    • 認証失敗時に実行されます。認証失敗の記録を保存し、モジュールスタック自体を終了します。
  • 5行目
    • 認証成功時に実行されます。認証失敗の記録をクリアします。

/etc/pam.d/sshd

既存ファイルの 2 行目を上記ファイルに置き換えます。

$ diff sshd.backup sshd
2c2
< auth       substack     password-auth
---
> auth       substack     google-auth

/etc/security/faillock.conf

Google Authenticator 認証失敗の定義を決めます。
以下は 「900 秒 (15分) 以内に 6 回続けて認証失敗したら 3600 秒 (1時間) の間ロックアウトする」 という意味になります。audit はログ記録のため、silent はユーザー向けメッセージをオフにするために入れます。

audit
silent
deny = 6
fail_interval = 900
unlock_time = 3600

/etc/ssh/sshd_config

今回の公開鍵認証 + Google Authenticator 認証に必要な設定を入れておきます。

AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

以上の設定を終えたら sshd を再起動します。

# systemctl restart sshd

SSH 接続してみる

SSH 接続すると、公開鍵認証、Google Authenticator 認証の順に入力が求められます。
それぞれ正しいパスワードを入れるとログインできました。

$ ssh -i <秘密鍵> user1@localhost
Enter passphrase for key '<秘密鍵>': ***(正しいパスワード)***
(user1@localhost) Verification code: ***(正しいワンタイムパスワード)***
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
Last login: Mon Mar 18 08:12:53 2024 from 127.0.0.1
$

faillock によるロックアウトを試してみる

次に、ワンタイムパスワードを 6 回連続で間違えてみます。(faillock.conf の deny の数)

$ ssh -i <秘密鍵> user1@localhost
Enter passphrase for key '<秘密鍵>': ***(正しいパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
user1@localhost: Permission denied (keyboard-interactive).

$ ssh -i <秘密鍵> user1@localhost
Enter passphrase for key '<秘密鍵>': ***(正しいパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
(user1@localhost) Verification code: ***(正しくないワンタイムパスワード)***
user1@localhost: Permission denied (keyboard-interactive).

6 回間違えたためユーザーはロックアウト状態にあります。さらに接続しようとすると今度は preauth が効いてワンタイムパスワード要求もなく終了しました。

$ ssh -i <秘密鍵> user1@localhost
Enter passphrase for key '<秘密鍵>': ***(正しいパスワード)***
user1@localhost: Permission denied (keyboard-interactive).

ユーザーがロックアウトされているかどうかは faillock コマンドで確認することができます。Valid の V (Valid) はロック回数にカウントされているという意味です。deny の数に達していることが分かります。

$ sudo faillock --user user1
user1:
When                Type  Source                       Valid
2024-03-18 08:24:56 RHOST 127.0.0.1                        V
2024-03-18 08:24:58 RHOST 127.0.0.1                        V
2024-03-18 08:25:00 RHOST 127.0.0.1                        V
2024-03-18 08:25:15 RHOST 127.0.0.1                        V
2024-03-18 08:25:17 RHOST 127.0.0.1                        V
2024-03-18 08:25:19 RHOST 127.0.0.1                        V

unlock_time (3600秒) が経過するとまたワンタイムパスワード要求が復活しますが、またそこで入力を間違えると以下のように上書きされ、古いレコードは I (Invalid) になります。

$ sudo faillock --user user1
user1:
When                Type  Source                       Valid
2024-03-18 09:30:22 RHOST 127.0.0.1                        V
2024-03-18 08:24:58 RHOST 127.0.0.1                        I
2024-03-18 08:25:00 RHOST 127.0.0.1                        I
2024-03-18 08:25:15 RHOST 127.0.0.1                        I
2024-03-18 08:25:17 RHOST 127.0.0.1                        I
2024-03-18 08:25:19 RHOST 127.0.0.1                        I

正しいワンタイムパスワードを入れた場合はレコードがクリアされます。
faillock --user ユーザー名 --reset コマンドでもクリアできます。

$ sudo faillock --user user1
user1:
When                Type  Source                       Valid

おわりに

最初、ネットの記事(新旧・OSごちゃ混ぜ)を参考にしてたらだいぶ検証が迷走したのですが、わりとしっかり man に書いてありました。

  • man pam.d
  • man pam_faillock
  • man pam_google_authenticator

座右の銘

困ったときは再起動

に追加で

まずは man を読め

を刻み、これからも生きていこうと思います。