landscape

landscape

Deep think, Easy go.

29 Mar 2022

Ubuntu 20.04 SSHで公開鍵認証+Google Authenticator

SSHに公開鍵認証をつけているが、二要素認証としてgoogle-authenticator-libpamを設定してみる。

環境

$ uname -a
Linux ursula-T3600 5.13.0-30-generic #33~20.04.1-Ubuntu SMP Mon Feb 7 14:25:10 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/*release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.4 LTS"
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.4 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

google-authenticator-libpamのインストール

Ubuntuのaptリポジトリからインストール

sudo apt update
sudo apt-get install libpam-google-authenticator

実行結果

パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の追加パッケージがインストールされます:
  libqrencode4
以下のパッケージが新たにインストールされます:
  libpam-google-authenticator libqrencode4
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 76 個。
57.3 kB のアーカイブを取得する必要があります。
この操作後に追加で 190 kB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://jp.archive.ubuntu.com/ubuntu focal/universe amd64 libqrencode4 amd64 4.0.2-2 [23.6 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu focal/universe amd64 libpam-google-authenticator amd64 20170702-2 [33.7 kB]
57.3 kB を 0秒 で取得しました (526 kB/s)               
以前に未選択のパッケージ libqrencode4:amd64 を選択しています。
(データベースを読み込んでいます ... 現在 369106 個のファイルとディレクトリがインストールされています。)
.../libqrencode4_4.0.2-2_amd64.deb を展開する準備をしています ...
libqrencode4:amd64 (4.0.2-2) を展開しています...
以前に未選択のパッケージ libpam-google-authenticator を選択しています。
.../libpam-google-authenticator_20170702-2_amd64.deb を展開する準備をしています ...
libpam-google-authenticator (20170702-2) を展開しています...
libqrencode4:amd64 (4.0.2-2) を設定しています ...
libpam-google-authenticator (20170702-2) を設定しています ...
man-db (2.9.1-1) のトリガを処理しています ...
libc-bin (2.31-0ubuntu9.7) のトリガを処理しています ...

google-authenticatorの設定

google-authenticator 

対話式に質問に答えていくとQRコードが表示されるため、スマートフォンのアプリのGoogle Authenticatorで読み込む

Do you want authentication tokens to be time-based (y/n) y
Warning: pasting the following URL into your browser exposes the OTP secret to Google:
  https://www.google.com/chart?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Your new secret key is: XXXXXXXXXXXXXXXXXXXXXXXXXX
Your verification code is XXXXXX
Your emergency scratch codes are:
  XXXXXXXX
  XXXXXXXX
  XXXXXXXX
  XXXXXXXX
  XXXXXXXX

その後、いくつか設定に関して対話式で質問がくる。全部yesにしたが、DeepLで翻訳しておく。

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

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
# デフォルトでは、モバイルアプリによって30秒ごとに新しいトークンが生成されます。
# クライアントとサーバー間で起こりうる時間のズレを補正するために
# 現在時刻の前後の追加トークンを許可します。これにより
# 認証サーバーとクライアントの間で最大30秒のタイムスキューが発生します。もし
# 時刻の同期がうまくいかない場合は、デフォルトの3個の許可コード(1つ前のコード、現在のコード、次のコード)から、
# 17個の許可コード(8個前のコード、現在のコード、および次の8コード)までウィンドウを大きくすることができます。
# これにより、クライアントとサーバーの間で最大4分のタイムスキューを許容することができます。
# そうしますか?

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回までしかログインを試行できないように制限されています。
# レート制限を有効化しますか?

sshdの設定

$ cat  /etc/ssh/sshd_config

PasswordAuthentication no

...省略
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
#ChallengeResponseAuthentication no
ChallengeResponseAuthentication yes # 変えた

...省略

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive # 追加した

...省略

PAM設定(失敗)

いろんなサイトにはPAM設定の末尾に以下の行を設定するといいとあるので追加してみた。

sudo sh -c 'echo "auth required pam_google_authenticator.so echo_verification_code" >> sshd'

しかし、これを設定したら、参考サイトにあるように公開鍵認証+チャレンジレスポンス+Google Authenticatorの三段階認証になってしまった。

パスワード認証時はパスワード認証+Google Authenticatorの二段階認証になるので問題ないが、公開鍵認証ではチャレンジレスポンスをやめたい。


PAMについては以下のサイトが勉強になりました。

https://christina04.hatenablog.com/entry/pluggable-authentication-module

ソースリポジトリのサンプルにはauth requiredで設定していたので、authでパスワードモジュールを実行しているPAMをpam_google_authenticator.soに差し替えれば良いと思う。

まずはsshdの全量を見てみます。

$ sudo vim /etc/pam.d/sshd 

# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
@include common-auth

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

# Standard Un*x authorization.
@include common-account

# SELinux needs to be the first session rule.  This ensures that any
# lingering context has been cleared.  Without this it is possible that a
# module could execute code in the wrong domain.
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close

# Set the loginuid process attribute.
session    required     pam_loginuid.so

# Create a new session keyring.
session    optional     pam_keyinit.so force revoke

# Standard Un*x session setup and teardown.
@include common-session

# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic
session    optional     pam_motd.so noupdate

# Print the status of the user's mailbox upon successful login.
session    optional     pam_mail.so standard noenv # [1]

# Set up user limits from /etc/security/limits.conf.
session    required     pam_limits.so

# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
session    required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were moved to
# /etc/default/locale, so read that as well.
session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale

# SELinux needs to intervene at login time to ensure that the process starts
# in the proper default security context.  Only sessions which are intended
# to run in the user's context should be run after this.
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so open

# Standard Un*x password updating.
@include common-password

次の4つのモジュールをインポートしているので中身を見てみる。 @include common-auth @include common-account @include common-session @include common-password

$ grep -v '#' /etc/pam.d/common-* | grep auth
/etc/pam.d/common-account:account	[success=1 new_authtok_reqd=done default=ignore]	pam_unix.so 
/etc/pam.d/common-auth:
/etc/pam.d/common-auth:auth	[success=1 default=ignore]	pam_unix.so nullok
/etc/pam.d/common-auth:auth	requisite			pam_deny.so
/etc/pam.d/common-auth:auth	required			pam_permit.so
/etc/pam.d/common-auth:auth	optional			pam_cap.so 

pam_unix.soがパスワード認証のモジュールかと思う。調べたらビンゴで/etc/pam.d/common-authがパスワード認証を行なっていることがわかった。


PAM設定

パスワード認証設定common-authをコピーしてGoogle Authenticator用のPAM設定google-authを作成する。

sudo cp -p /etc/pam.d/common-auth /etc/pam.d/google-auth

vim /etc/pam.d/google-auth

次のように修正する。

#
# /etc/pam.d/google-auth - authentication settings for google authenticator
#

# here are the per-package modules (the "Primary" block)
#auth	[success=1 default=ignore]	pam_unix.so nullok   <---コメントアウト
auth	[success=1 default=ignore]	pam_google_authenticator.so echo_verification_code   <---追加

# here's the fallback if no module succeeds
auth	requisite			pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth	required			pam_permit.so
# and here are more per-package modules (the "Additional" block)
auth	optional			pam_cap.so 
# end of pam-auth-update config

sshdのPAM設定にパスワード認証の代わりにGoogle Authenticator設定を適用する

sudo vim /etc/pam.d/sshd

次のように修正する。

# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
# @include common-auth  <---コメントアウト
@include google-auth   <---追加

...省略

これで設定を行うことができた。

確認

# 最初のPAM設定ではこのようにPassword: が出力されていた。
$ ssh myserver
Password: 
Verification code: XXXXXX

# 正しいPAM設定後はPassword: が出力されず、Verification codeを入力するとちゃんとログインできた。
$ ssh myserver
Verification code: XXXXXX
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.13.0-30-generic x86_64)


# 試しに認証エラーを起こしてみる
$ ssh myserver
Verification code: 
Verification code: 
Verification code: 
Received disconnect from 192.168.0.20 port 22:2: Too many authentication failures
Disconnected from 192.168.0.20 port 22

# ちゃんとロックアウトされる。
$ ssh myserver
jlandowner@myserver: Permission denied (keyboard-interactive).

参考サイト

https://github.com/google/google-authenticator-libpam

https://wiki.archlinux.jp/index.php/Google_Authenticator

https://blog.apar.jp/linux/12502/

https://christina04.hatenablog.com/entry/pluggable-authentication-module

https://int128.hatenablog.com/entry/20090726/1248622071