FreeRADIUS + Google Authenticator + Microsoft ADを使ったClientVPNのMFA構成

FreeRADIUSを使って、Microsoft ADで認証するClientVPNの環境にMFAを導入してみました。Microsoft ADを使ったMFAの事例が少なかったので少しハマりました。
2020.04.23

ClientVPNでは、ユーザーに対して多要素認証(MFA)を利用することができます。
今回は、MFAを使うために必要なRADIUSサーバを、FreeRADIUSで構築してみたいと思います。

構築手順としては、下記の情報を元にしています。こちらは古いAmazon Linuxを使っているため、今回はAmazon Linux2で作成しています。
また、下記では「WorkSpaces Connect」(現在のAD Connector)を使っていますが、今回はMicrosoft ADを使います。

AWS Solutions Architect ブログ: Google Authenticator を使って Amazon WorkSpaces に多要素認証ログイン

検証する構成

AWSのDirectory Serviceでは、「Microsoft AD」と「AD Connector」がMFAをサポートしています。
そのうち、今回検証するのは「Microsoft AD」を使うパターンです。Active Directoryはオンプレに持たず、AWS上で完結する構成です。

102-FreeRADIUS-MSAD

実際には、下記のように検証に必要なリソースだけ作成します。

103-FreeRADIUS-MSAD-real

注意点

本記事で作成した構成の場合、ドメインに所属する全てのユーザーに対して、ClientVPN接続時にMFAが有効となります。
そのため、状況次第では他の構成やSaaSの利用を検討する必要があります。例えば「グループ単位でMFAの有効/無効を分けたい」、「特定ユーザーはスマホを持ってないのでSMSによるOTP認証を使いたい」といった要件の場合です。

全てのケースに対応できるか条件次第ですが、OneLoginやDuo SecurityといったIDaaSで実現可能な場合がありますので、選択肢として持っておくとよいかと思います。

FreeRADIUSの構築

それでは構築していきましょう。基本的には冒頭のブログの通りに進めていきます。

まずは「Amazon Linux2」でインスタンスを作成しておきましょう。このインスタンスはプライベートサブネットに配置するので、踏み台などを経由して作業するようにします。

必要なパッケージインストール

サーバにログインして、必要なパッケージをインストールします。

sudo yum -y update
sudo yum -y install freeradius freeradius-utils git gcc pam-devel \
qrencode qrencode-libs qrencode-devel

Google Authenticatorのインストール

Google Authenticatorは、epelリポジトリからインストールします。

sudo amazon-linux-extras install -y epel
sudo yum -y install google-authenticator

OS設定

RADIUS 経由の認証を許可するグループを作成します。

sudo groupadd radius-enabled

次にホスト名を設定します。
スマホのMFAアプリでは「ユーザー名@ホスト名」という形で表示されるので分かりやすい名前を付けます。外部から名前解決できる必要はありません。(スマホアプリ側で任意に設定できるので必須ではありません。必要に応じて設定してください)

sudo hostnamectl set-hostname (ホスト名)

例
sudo hostnamectl set-hostname myradius.mfa.example

/etc/hostsにも追記します。

(RADIUSインスタンスのプライベートIP) (ホスト名)

例
10.60.5.195 myradius.mfa.example

FreeRADIUSの設定

rootユーザで起動するように/etc/raddb/radiusd.confを修正します。

user = radiusd
group = radiusd

↓

user = root
group = root

PAM を使うように/etc/raddb/usersを変更します。下記をファイルの最後に追記しましょう。

DEFAULT    Group != "radius-enabled", Auth-Type := Reject
       Reply-Message = "Your account has been disabled."
DEFAULT        Auth-Type := PAM

/etc/raddb/sites-available/defaultを修正します。513行目にある下記をコメントアウトします。

#       pam
↓
        pam

Google Authenticator のPAM モジュールを使うように/etc/pam.d/radiusdを修正します。

#%PAM-1.0
auth       include      password-auth
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
session    include      password-auth

↓

#%PAM-1.0
#auth       include     password-auth
#account    required     pam_nologin.so
#account    include     password-auth
#password   include     password-auth
#session    include     password-auth
auth requisite pam_google_authenticator.so
account required pam_permit.so
session required pam_permit.so

/etc/raddb/clients.confを編集してRADIUSクライアントの設定を行います。下記の内容をファイルの最後に追記します。

  • client 10.60.0.0/16指定のCIDRは実際に利用しているVPCのものを指定してください。
  • secretは任意のパスワードを指定してください。
client 10.60.0.0/16 {
        secret          = xxxxxxxxxxxxxxxx
        shortname       = from-vpc
}

FreeRADIUSの起動

準備ができたので起動したいと思います。まずは自動起動するようにします。

sudo systemctl enable radiusd.service

確認します。

systemctl is-enabled radiusd.service

enabledと表示されればOKです。ではプロセスを起動します。

sudo systemctl start radiusd.service

これで起動するかと思いましたが、下記のようなエラーが出て失敗しました。

Job for radiusd.service failed because the control process exited with error code. See "systemctl status radiusd.service" and "journalctl -xe" for details.

メッセージに従い、ログを確認しても有用なログはありませんでした。

Failed to start FreeRADIUS high performance RADIUS server..

仕方ないので、デバッグモードで詳細を確認してみます。

sudo radiusd -X

下記のようなメッセージが赤文字で出力されました。pamモジュールが見つからないエラーのようです。

/etc/raddb/sites-enabled/default[514]: Failed to find "pam" as a module or policy.
/etc/raddb/sites-enabled/default[514]: Please verify that the configuration exists in /etc/raddb/mods-enabled/pam.
/etc/raddb/sites-enabled/default[476]: Errors parsing authenticate section.

pamモジュールを有効にするためにシンボリックリンクを張ります。

sudo ln -s /etc/raddb/mods-available/pam /etc/raddb/mods-enabled/pam

それでは改めて起動してみます。

sudo systemctl start radiusd.service

確認します。

sudo systemctl status radiusd.service

正常に起動できていることが確認できます。

● radiusd.service - FreeRADIUS high performance RADIUS server.
   Loaded: loaded (/usr/lib/systemd/system/radiusd.service; enabled; vendor preset: disabled)
   Active: active (running) since 木 2020-04-16 12:32:05 UTC; 43s ago
  Process: 1790 ExecStart=/usr/sbin/radiusd -d /etc/raddb (code=exited, status=0/SUCCESS)
  Process: 1787 ExecStartPre=/usr/sbin/radiusd -C (code=exited, status=0/SUCCESS)
  Process: 1785 ExecStartPre=/bin/chown -R radiusd.radiusd /var/run/radiusd (code=exited, status=0/SUCCESS)
 Main PID: 1794 (radiusd)
   CGroup: /system.slice/radiusd.service
           └─1794 /usr/sbin/radiusd -d /etc/raddb

 4月 16 12:32:05 myradius.mfa.example systemd[1]: Starting FreeRADIUS high performance RADIUS server....
 4月 16 12:32:05 myradius.mfa.example systemd[1]: Started FreeRADIUS high performance RADIUS server..

ユーザ登録

実際に利用する際は、Active Directoryと同じ名前のユーザをRADIUSサーバ側にも登録する必要があります。
最初に作成したグループに所属するユーザーを登録しましょう。
今回はcmtest001というユーザーを登録してみます。

sudo useradd -g radius-enabled cmtest001

ユーザーが登録できたら、このユーザーでgoogle-authenticatorを実行してMFAに対応させます。

sudo -u cmtest001 google-authenticator

コマンドを実行すると、質問と一緒に大きなQRコードが表示されますが、ここでは気にせず質問に全て答えてしまいましょう。
質問には全て「y」で問題ありませんが、その内容を見ておきましょう。

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

最初は「時間ベースのトークンにするか」という質問です。

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

次は、「作成したユーザーのホームディレクトリにある.google_authenticatorファイルを更新しますか?」という質問です。
このファイルには、先程のQRコード/キーに関する情報が記載されているので、更新しなければ認証に通りません。

ちなみに、.google_authenticatorはこんな内容です。(数字、文字列はサンプルです)

TOISETWER75ON19GEGEFT2RGLJ
" RATE_LIMIT 3 30
" WINDOW_SIZE 17
" DISALLOW_REUSE
" TOTP_AUTH
85610581
87856911
87618591
14501245
19286016

次の質問です。

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)

「同じ認証トークンを複数回使うのを禁止しますか?」という質問です。特に問題ないかと思います。

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)

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

(ちょっと自信がなかったので「Google翻訳」を使いました)

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)

「サーバがブルートフォースに対策してなければ、認証モジュールでレートリミットをかけることができます。デフォルトで30秒に3回の制限です。レートリミットしますか?」という質問です。

MFAアプリに登録

質問に全て答えたら、スルーしていたQRコードの対応を行います。これを手元のMFAアプリでスキャンしましょう。
私は「Authy」というアプリを使っているので、そちらでスキャンしました。

同時に出力されるURLにアクセスしても同じQRコードが表示されます。(赤枠の部分)

01-qr

利用者に先程のURLを共有してスキャンしてもらうか、スキャンが難しい場合は下記のキーを手動で入力することでMFAアプリに登録することもできます。

02-key

Authyの場合だと画面下にある「Enter key manually」をクリックすると

03-authy

こんな感じでキーを手動で登録できます。

04-authy

いずれかの方法でMFAアプリに入力できたら、このように「ユーザー名@ホスト名」で登録されました。
表示名やロゴは後から変更できます。

06-authy

認証テスト

ここまでできたら、単体で認証のテストを行います。コマンドの書式は以下です。

radtest [ユーザ名] [MFAコード] [RADIUSサーバのプライベートIP]:1812 10 [クライアントの共有シークレット]

MFAコードにはスマホのアプリに表示される6桁の数字を入れます。共有シークレットに記号を入れていてシェルからの入力に失敗する時は'"で括って指定します。下記は例です。

radtest cmtest001 948712 10.60.5.195:1812 10 'L*pzK,G68s&67*he'

問題なければ下記のようにReceived Access-Acceptと出力されます。

Sent Access-Request Id 209 from 0.0.0.0:49270 to 10.60.5.195:1812 length 79
    User-Name = "cmtest001"
    User-Password = "596580"
    NAS-IP-Address = 10.60.5.195
    NAS-Port = 10
    Message-Authenticator = 0x00
    Cleartext-Password = "596580"
Received Access-Accept Id 209 from 10.60.5.195:1812 to 0.0.0.0:0 length 20

Microsoft ADの設定

次にAWS側の設定を行います。ディレクトリサービスとしてMicrosoft ADを使います。

Microsoft ADの作成

オンプレADではなくMicrosoft ADを使う場合は、ここでディレクトリを作成します。 ディレクトリタイプに「Microsoft AD」を選択して作成します。

11-select-msad

ディレクトリ情報を記入していきます。

項目 内容
ディレクトリのタイプ 利用する規模感に応じて選択。今回は検証なので「Standard」を選択。
ディレクトリのDNS名 ディレクトリのActive Directoryで使用するドメイン名。FreeRADIUSの設定で指定したドメインと違っていて問題なし。適当なものを指定。
ディレクトリのNetBIOS名 任意
ディレクトリの説明 任意
Adminパスワード Microsoft ADで利用できる管理者アカウントのパスワード。(Administratorは利用不可)

12-directory-info

ディレクトリを作成するVPCとサブネットを指定します。インターネットに露出しないプライベートなサブネットに作成しましょう。
(FreeRADIUSのサーバと同じサブネットである必要はありません)

13-select-network

問題なければ作成します。

14-msad-confirm

Security Groupに注意

Microsoft ADは、それ自体がディレクトリサービスを提供するものなので、デフォルトではアウトバウンドのセキュリティグループがAD Connectorのものとは異なります。

Microsoft ADでMFAを利用する場合は、RADIUSサーバへの認証の通信が発生するので、このトラフィックを明示的に許可する必要があります。
また、RADIUSサーバ側でも、Microsoft ADからの「UDP 1812ポート」へのアクセスを許可します。

18-msad-sg

そのため、Microsoft ADのセキュリティグループにアウトバウンドの許可設定を追加しましょう。
まず、Directory Serviceのコンソールから「ディレクトリID」を確認します。

15-detail-directory

次に、VPCまたはEC2のコンソールからセキュリティグループの画面を開いて、確認したディレクトリIDで検索します。(d-xxxxxxx_controllersというグループ名になっています)
見つかったセキュリティグループに下記のように許可ルールを追加します。

項目 設定内容
タイプ カスタムUDPルール
プロトコル UDP
ポート範囲 1812
送信先 RADIUSサーバのセキュリティグループID

16-sg-outbound-msad

RADIUSサーバ側のセキュリティグループにも、許可ルールを追加します。

項目 設定内容
タイプ カスタムUDPルール
プロトコル UDP
ポート範囲 1812
ソース Microsoft ADのセキュリティグループID

17-radius-ec2-sg

MFAの有効化

作成したMicrosoft ADでMFA設定を有効化します。該当ディレクトリのコンソールで「ネットワークとセキュリティ」タブを表示します。

20-nw-and-sec

画面の一番下にMFAの設定箇所があるので、プルダウンから「有効化」をクリックします。

21-mfa-active

下記の通り設定します。

項目 設定内容
表示ラベル 分かりやすいものを指定
RADIUSサーバのDNS名またはIPアドレス FreeRADIUSサーバのプライベートIP。複数指定する場合はカンマ区切りで指定。
ポート 1812
共有シークレットコード /etc/raddb/clients.confで指定したsecretの文字列
確認済みの共有シークレットコード 上記と同じもの
プロトコル PAP
サーバタイムアウト 適当な秒数を指定
RADIUSリクエストの最大再試行数 適当な回数を指定

22-activate-mfa-msad

しばらくしてステータスが「完了」になればOKです。

23-mfa-config-complete

ClientVPNの設定

次にClientVPNの設定を行います。

「認証情報」のセクションで「ユーザーベースの認証を使用」にチェックを入れて、先程作成したMicrosoft ADのディレクトリIDを指定します。(相互認証と併用することも可能です)

30-make-clientvpn-endpoint

後は、特別な設定はありません。関連付けるサブネットを設定します。

31-clientvpn-associate

今回は検証なので、VPC全体に対して全てのアクセスを許可する認証ルールを入れます。
この辺りはご自身の環境、要件に合わせて設定してください。

32-clientvpn-auth

ClientVPNのエンドポイントの作成が完了したら、コンフィグファイルをダウンロードして、利用するVPNクライアントに設定を追加しておきましょう。

33-download-vpn-config

Microsoft AD環境での動作テスト

Microsoft ADに適当なユーザーを追加します。
本来は、ドメインに参加したEC2やリモートから「RSAT」などで接続してユーザ作成しますが、今回は横着をしてWorkSpacesのコンソールからユーザー作成だけ行います。

AWS Managed Microsoft AD のユーザーとグループを管理する - AWS Directory Service

マネジメントコンソールからユーザーを作成するために、WorkSpacesのコンソールにアクセスします。
コンソール上で「WorkSpacesの起動」をクリックします(実際にはWorkSpacesは作成しません)

40-workspaces-launch

次に先程作成したMicrosoft ADを選択肢ます。サブネットは適当で構いません。
(実際にWorkSpacesを展開するサブネットの指定になりますが、今回は作成せず途中でキャンセルするので適当でOKです)

41-select-vpc

作成したいユーザ情報を入力します。
RADIUSサーバに登録したのと同じユーザ「cmtest001」を登録します。

42-add-user

登録できたら、「すべてのユーザーの表示」をクリックして、cmtest001ユーザが作成できていることを確認します。
ユーザーの作成が確認できたら、「キャンセル」をクリックしてウィザードを途中で終了させます。

43-cancel-user-2

次に、Directory Serviceのコンソールに戻ります。
先程作成したユーザー(cmtest001)にはパスワードがまだ設定されていません。そのため、ここで「cmtest001」のパスワードを設定します。

作成済みのMicrosoft ADの画面を開いて、「ユーザーパスワードのリセット」をクリックします。

44-modify-password

下記のようなウィンドウが開くので、パスワードをリセットしたいユーザー名、設定するパスワードを入力して「パスワードのリセット」をクリックします。

45-reset-password

これでパスワードの設定が完了しました。

46-success-reset-password

VPNクライアントから接続テスト

クライアントは、MacOSから「Tunnelblick 3.8.2 (build 5480)」で接続します。
(VPNのコンフィグファイルは既にインポート済みとします)

ユーザ名「cmtest001」とパスワードリセットで設定したパスワードを入力します。

50-tunnelblick-connect

次にMFAコードの入力を促されるので、スマホのMFAアプリで表示される6桁のコードを入力します。
(もしMFAの入力画面が現れなかったらTunnelblickをアップデートしてみてください)

51-enter-mfa-code

スマホのMFAコードの表示です。

53-app-mfa

接続完了後に下記のような警告が出たら「了解」をクリックして閉じてください。

52-warning-tunnelblick

VPN接続後、FreeRADIUSのサーバに直接プライベートIPで接続してみると、下記の通り無事にSSHできました。
ClientVPN経由でリソースにアクセスできるように、内容に応じて「接続される」側のセキュリティグループに許可ルールを追加しておきましょう)

60-ssh-privateip

検証後の環境削除について

検証が完了して作った環境を削除するときは、下記の点に気をつけてください。

  • 関連アプリケーションの無効化
  • ClientVPNの削除

関連アプリケーションの無効化

今回は横着してWorkSpaces側からADユーザを作成したので、ディレクトリサービス側でWorkSpacesが有効になっています。

70-enable-ws

コレを無効にするには、WorkSpacesの画面で対象ディレクトリに対して「登録解除」を行います。

71-disabled-workspaces

ステータスが無効になりました。

72-disabled-workspaces-2

ClientVPNエンドポイントの削除

次に、このディレクトリを認証に利用しているClientVPNのエンドポイントを削除します。
上記のアプリ一覧「AWSアプリおよびサービス」には、ClientVPNが記載されていません。そのため、下記のようなエラーが出てもClientVPNの存在が原因だと気付きづらいので注意しましょう。

73-delete-error

FreeRADIUSの冗長化

実運用を考えると、Multi-AZを前提としてFreeRADIUSサーバ自体を冗長化したいというニーズもあると思います。

この場合は、同じ設定のインスタンスをAMIやスナップショットからコピーして冗長構成にします。そして、Microsoft ADのMFA設定の中で「RADIUSサーバのIPをカンマ(,)で複数登録」すればよいかと思います。

また、/home/[User]以下のディレクトリをrsyncなどで定期的に同期します。一方向で同期させると構成の維持など運用が面倒な場合は、/home以下はEFSで共有させてもいいかもしれません。
(Multi-AZで展開するEFSの料金が別途発生するので、確認の上で採用ください)

最後に

Microsoft ADで行う時は、セキュリティグループのアウトバウンドにルールを追加しなくてはいけない点に気づかず、検証当初にかなり悩みました。
他にも細かいところで何度かつまづきましたが、気がついた点はこのブログに詰め込みました。この記事がどなたかのお役に立てれば幸いです。

以上です。