AWSで学ぶRADIUSサーバー：FreeRADIUS構築+CloudWatchで可視化

# はじめに
AWS 環境の EC2 インスタンス (Amazon Linux 2023) を利用して、オープンソースで最も広く利用されている RADIUS サーバー「FreeRADIUS」を構築する手順を解説します。
さらに発展編として、実運用で役立つ CloudWatch を使ったログの可視化までをやってみます。
### 全体構成図
![structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075786/eac2a0bc-b032-4beb-b90d-f6bd67572294.png)

同じ VPC 内に RADIUS サーバーとクライアントを配置し、クライアントからサーバーへ認証リクエストを送信するシンプルな構成です。

# 構築手順
## ステップ1: AWS環境の準備
まず、サーバーとクライアントとして利用する2台のEC2インスタンスを準備します。
以下の設定で2台のEC2インスタンスを作成します。

| 設定項目 | 値 | 補足 |
|----------|----|----- |
| 名前 | RADIUS-Server と RADIUS-Client | それぞれの役割が分かるように命名します。 |
| AMI | Amazon Linux 2023 AMI |-|
| インスタンスタイプ | t2.micro | デモ用途には十分なスペックです。 |
| ネットワーク | 同じVPC、同じパブリックサブネット | 2台のインスタンスが相互に通信できるようにします。 |
| パブリックIPの自動割り当て | 有効化 |SSH接続用|

次にRADIUS-SG という名前で新しいセキュリティグループを作成し、2台のインスタンスにアタッチします。以下のインバウンドルールを設定してください。

| タイプ | プロトコル | ポート範囲 | ソース | 説明 |
| :--- | :--- | :--- | :--- | :--- |
| SSH | TCP | 22 | マイIP | 管理用のSSH接続 |
| カスタムUDP | UDP | 1812 | RADIUS-SG のID | RADIUS認証通信 |
| カスタムUDP | UDP | 1813 | RADIUS-SG のID | RADIUSアカウンティング通信 |

* プライベートIPアドレスの確認
後ほどの設定で利用するため、作成した2台のインスタンスのプライベートIPアドレスを控えておきます。

## ステップ2: RADIUSサーバーの構築


1.  **FreeRADIUSのインストール**
 `RADIUS-Server` インスタンスにEC2 Instance ConnectでSSH接続し、FreeRADIUS をインストール・設定します。
    ```bash
    sudo dnf update -y
    sudo dnf install -y freeradius freeradius-utils
    ```
    タイムゾーンを日本時間 (Asia/Tokyo) に設定します。
    ```bash
    sudo timedatectl set-timezone Asia/Tokyo
    ```

2.  **クライアント情報の登録 (clients.conf)**
RADIUS サーバーがどのクライアントからのリクエストを受け付けるかを設定します。
    `sudo vi /etc/raddb/clients.conf` を開き、ファイルの末尾に `RADIUS-Client` の情報を追記します。 `<RADIUS-ClientのプライベートIP>` は控えておいたIPアドレスに置き換えてください。

    ```
    # /etc/raddb/clients.conf
    # (...ファイルのもともとの内容...)
    # 自前のRADIUSクライアントを追加
    client radius_client {
        ipaddr = <RADIUS-ClientのプライベートIP>
        secret = hands-on  # サーバーとクライアント間の合い言葉
    }
この `secret` (共有シークレット) は非常に重要です。

３.  **ユーザー情報の登録 ( `users` )**
認証に利用するユーザーを登録します。
 `sudo vi /etc/raddb/users` を開き、ファイルの先頭にテストユーザーの情報を追記します。

    # /etc/raddb/users
    # テストユーザーを追加 (ユーザー名: testuser, パスワード: testpassword)
    testuser Cleartext-Password := "testpassword"
    # 複数人登録する場合は同様に追記する
    hanako.yamada   Cleartext-Password := "P@ssword_Yamada123"
    taro.suzuki     Cleartext-Password := "P@ssword_Suzuki456"
    # (...ファイルのもともとの内容...)

## ステップ3: EAP無効化
FreeRADIUS は、インストール直後の状態では高度な認証方式 (EAP) が有効になっています。しかし、これらの機能はSSL証明書などを必要とするため、シンプルなID/パスワード認証を行う際には、これが原因で起動エラーが発生します。

今回のデモでは、より簡単にするためにあらかじめEAPを無効化しておきます。
1.  **`eap` モジュールを無効化**
    EAP はセキュアな認証方式ですが、証明書がないと起動に失敗します。以下のコマンドで無効化します。
    ```bash
    sudo rm /etc/raddb/mods-enabled/eap
    ```
2.  **`inner-tunnel` サイトを無効化**
    `inner-tunnel` は EAP のトンネル処理で使われる設定ですが、今回は不要なので無効化します。
    ```bash
    sudo rm /etc/raddb/sites-enabled/inner-tunnel
    ```
3.  **`default` サイトの修正**

    モジュールを無効化しても、メインの設定ファイル ( `default` ) からの呼び出しが残っているため、それらもコメントアウトします。

    `sudo vi /etc/raddb/sites-enabled/default` で開いて、2箇所修正します。

    * **`authorize` セクションの修正（419行目）**
    ※eapだけでなく、{ }の部分もコメントアウトすることを忘れないでください。起動時に構文エラーとなります。

        ```conf
        # 編集前
        authorize {
            ...
            eap {
                   ok = return
            #       updated = return
            }
        }

        # 編集後
        authorize {
            ...
            #eap {
            #       ok = return
            #       updated = return
            #}
        }
        ```
* **`authenticate` セクションの修正(603行目)**

    ```conf
    # 編集前
    authenticate {
        ...
        eap
        ...
    }

    # 編集後
    authenticate {
        ...
    #   eap
        ...
    }
    ```
Tips：以下のコマンドで編集すべき行数を予め知ることができます。
        ```
        sudo grep -n '^\s*eap' /etc/raddb/sites-enabled/default
        ```
これで、シンプルなPAP認証（ID/パスワード認証）に必要な設定だけが残った状態になりました。
## ステップ4: サーバーの起動とクライアントの準備

1.  **FreeRADIUSサービスの起動**
    `RADIUS-Server` で以下のコマンドを実行し、サービスを起動・自動起動設定します。

    ```bash
    sudo systemctl start radiusd
    sudo systemctl enable radiusd
    ```
    以下のコマンドで `active (running)` となっていれば成功です。
    ```bash
    [ec2-user@ip-10-0-14-102 ~]# sudo systemctl status radiusd
    ● radiusd.service - FreeRADIUS high performance RADIUS server.
     Loaded: loaded (/usr/lib/systemd/system/radiusd.service; enabled; preset: disabled)
     Active: active (running) since Sun 2025-06-22 11:29:19 UTC; 12s ago
     Main PID: 31373 (radiusd)
      Tasks: 6 (limit: 1111)
     Memory: 40.5M
        CPU: 80ms
     CGroup: /system.slice/radiusd.service
             └─31373 /usr/sbin/radiusd -d /etc/raddb
    ```

2.  **クライアントツールのインストール**

    `RADIUS-Client` インスタンスに SSH で接続し、テストツールをインストールします。

    ```bash
    sudo dnf update -y
    sudo dnf install -y freeradius-utils
    ```
    タイムゾーンを日本時間 (Asia/Tokyo) に設定します。
    ```bash
    sudo timedatectl set-timezone Asia/Tokyo
    ```
## ステップ5: 認証テスト

`RADIUS-Client` から `radtest` コマンドを使い、認証テストを実行します。

1.  **認証成功テスト**
    正しい共有シークレット ( `hands-on` ) を使ってテストします。
    ```bash
    radtest testuser testpassword <RADIUS-ServerのプライベートIP>:1812 0 hands-on
    ```
    以下のように `Received Access-Accept` と返ってくれば認証成功です！
    ```text
    Sent Access-Request Id 95 from 0.0.0.0:55186 to 10.0.14.102:1812 length 78
        User-Name = "testuser"
        User-Password = "testpassword"
        NAS-IP-Address = 10.0.8.100
        NAS-Port = 0
        Message-Authenticator = 0x00
        Cleartext-Password = "testpassword"
    Received Access-Accept Id 95 from 10.0.14.102:1812 to 10.0.8.100:55186 length 38
        Message-Authenticator = 0xf224b42683d891fa7fcf3193349935ad
    ```
2.  **認証失敗テスト (共有シークレット不一致)**
    わざと間違った共有シークレット ( `hands-off` ) を使ってみましょう。
    ```bash
    radtest testuser testpassword <RADIUS-ServerのプライベートIP>:1812 0 hands-off
    ```
    今度はサーバーから何の応答もなく、最終的にタイムアウトエラーになります。
    ```bash
    Sent Access-Request Id 27 from 0.0.0.0:56334 to 10.0.14.102:1812 length 78
        User-Name = "testuser"
        User-Password = "testpassword"
        NAS-IP-Address = 10.0.8.100
        NAS-Port = 0
        Message-Authenticator = 0x00
        Cleartext-Password = "testpassword"
    Sent Access-Request Id 27 from 0.0.0.0:56334 to 10.0.14.102:1812 length 78
        User-Name = "testuser"
        User-Password = "testpassword"
        NAS-IP-Address = 10.0.8.100
        NAS-Port = 0
        Message-Authenticator = 0x00
        Cleartext-Password = "testpassword"
    Sent Access-Request Id 27 from 0.0.0.0:56334 to 10.0.14.102:1812 length 78
        User-Name = "testuser"
        User-Password = "testpassword"
        NAS-IP-Address = 10.0.8.100
        NAS-Port = 0
        Message-Authenticator = 0x00
        Cleartext-Password = "testpassword"
    (0) No reply from server for ID 27 socket 3.
    ```

登録されたクライアントからでも共有シークレットが違う場合、サーバーは不正なリクエストとみなし、パケットを破棄します。

# 【発展】AWSサービスのワンポイント：認証ログの可視化

## ステップ6: FreeRADIUSの設定ファイル `radiusd.conf` を編集
設定ファイルをテキストエディタで開きます。
```
sudo vi /etc/raddb/radiusd.conf
```
log セクション内の `auth`の設定(348行目)をnoからyesへ変更します。
```
#編集前
#  Log all (accept and reject) authentication results to the log file.
#
#  This is the same as setting "auth_accept = yes" and
#  "auth_reject = yes"
#
#  allowed values: {no, yes}
#
auth = no

#編集後
#  Log all (accept and reject) authentication results to the log file.
#
#  This is the same as setting "auth_accept = yes" and
#  "auth_reject = yes"
#
#  allowed values: {no, yes}
#
auth = yes
```
デフォルトの設定では取得できなかった成功時のログも吐き出し可能となりました。

## ステップ7: CloudWatch Agentのインストールと設定
1.  **IAMロールのアタッチ**
`RADIUS-Server` のEC2インスタンスに、CloudWatch Agentがログを送信するための権限が必要です。

    1-1. IAMコンソールで新しいロールを作成します。
1-2. 信頼されたエンティティタイプ: **AWSのサービス**
1-3. ユースケース: **EC2**
1-4. 許可ポリシーとして `CloudWatchAgentServerPolicy` をアタッチします。
1-5. ロール名（例: `EC2-CloudWatchAgent-Role`）を付けて作成します。
1-6. EC2ダッシュボードに戻り、`RADIUS-Server` インスタンスにこのIAMロールをアタッチします。

2.  **CloudWatch Agentのインストール**
    `RADIUS-Server` にSSHで接続し、以下のコマンドでAgentをインストールします。

    ```bash
    sudo dnf install -y amazon-cloudwatch-agent
    ```

3.  **Agent設定ファイルの作成**
    どのログファイルをCloudWatchに送信するかを定義する設定ファイルを作成します。FreeRADIUSは認証結果を `/var/log/radius/radius.log` に出力するので、これを対象とします。
    `sudo vi /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json` を実行し、以下の内容を貼り付けます。

```json
{
        "agent": {
                "metrics_collection_interval": 60,
                "run_as_user": "root",
                "debug": true
        },
        "logs": {
                "logs_collected": {
                        "files": {
                                "collect_list": [
                                        {
                                                "file_path": "/var/log/radius/radius.log",
                                                "log_group_class": "STANDARD",
                                                "log_group_name": "/aws/ec2/freeradius-logs",
                                                "log_stream_name": "{instance_id}",
                                                "retention_in_days": 7,
                                                "timestamp_format": "%a %b %d %H:%M:%S %Y",
                                                "timezone": "LOCAL"
                                        }
                                ]
                        }
                }
        },
        "metrics": {
                "aggregation_dimensions": [
                        [
                                "InstanceId"
                        ]
                ],
                "append_dimensions": {
                        "AutoScalingGroupName": "${aws:AutoScalingGroupName}",
                        "ImageId": "${aws:ImageId}",
                        "InstanceId": "${aws:InstanceId}",
                        "InstanceType": "${aws:InstanceType}"
                },
                "metrics_collected": {
                        "disk": {
                                "measurement": [
                                        "used_percent"
                                ],
                                "metrics_collection_interval": 60,
                                "resources": [
                                        "*"
                                ]
                        },
                        "mem": {
                                "measurement": [
                                        "mem_used_percent"
                                ],
                                "metrics_collection_interval": 60
                        },
                        "statsd": {
                                "metrics_aggregation_interval": 60,
                                "metrics_collection_interval": 10,
                                "service_address": ":8125"
                        }
                }
        }
}
```

 `log_group_name`: CloudWatch Logs内でログを格納するグループ名です。分かりやすい名前をつけましょう。
参考までに上記jsonは以下の内容となっています。変更したいパラメータ等あれば、自由に調整してください。
### CloudWatch Agent パラメータ設定

* エージェント共通 (agent)

| パラメータ | 設定値 | 説明 |
|-----------|--------|------|
| `run_as_user` | `root` | エージェントを実行するユーザー。rootで実行することでファイルアクセス権限の問題を回避します。 |
| `metrics_collection_interval` | `60` (デフォルト値) | メトリクスを収集する基本間隔（秒）。 |
| `debug` | `TRUE` | デバッグモードを有効化。詳細な動作ログが出力され、トラブルシューティングに役立ちます。本番環境では`false`を推奨します。 |

* ログ収集 (logs)

| パラメータ | 設定値 | 説明 |
|-----------|--------|------|
| `file_path` | `/var/log/radius/radius.log` | 収集対象のログファイルへのフルパス。 |
| `log_group_name` | `/aws/ec2/freeradius-logs` | ログを格納するCloudWatch Logsのロググループ名。 |
| `log_stream_name` | `{instance_id}` | ロググループ内のログストリーム名。`{instance_id}`とすることで、実行したEC2インスタンスのIDが自動的に設定されます。 |
| `retention_in_days` | `7` | CloudWatch Logsでのログ保持期間（日数）。7日経過後に自動で削除されます。 |
| `timestamp_format` | `%a %b %d %H:%M:%S %Y` | ログファイル内のタイムスタンプを解釈するための書式。 |
| `timezone` | `LOCAL` | ログのタイムスタンプに使用するタイムゾーン。LOCALはEC2インスタンスのOS設定に従います。 |
| `log_group_class` | `STANDARD` | ロググループのストレージクラス。標準のアクセス頻度に適しています。 |

* メトリクス共通 (metrics)

| パラメータ | 設定値 | 説明 |
|-----------|--------|------|
| `aggregation_dimensions` | `InstanceId` | 収集したメトリクスをInstanceId単位で集約します。 |
| `append_dimensions.AutoScalingGroupName` | `${aws:AutoScalingGroupName}` | 全てのメトリクスにAuto Scalingグループ名のディメンション（タグ）を付与します。 |
| `append_dimensions.ImageId` | `${aws:ImageId}` | 全てのメトリクスにAMIのIDのディメンション（タグ）を付与します。 |
| `append_dimensions.InstanceId` | `${aws:InstanceId}` | 全てのメトリクスにインスタンスIDのディメンション（タグ）を付与します。 |
| `append_dimensions.InstanceType` | `${aws:InstanceType}` | 全てのメトリクスにインスタンスタイプのディメンション（タグ）を付与します。 |

* 収集メトリクス (ディスク・メモリ)

| パラメータ | 設定値 | 説明 |
|-----------|--------|------|
| `measurement` | `used_percent` | 収集するディスクの指標。ディスク使用率（%）を収集します。 |
| `resources` | `*` | 収集対象のリソース。`*`は全てのマウント済みディスクパーティションを意味します。 |
| `metrics_collection_interval` | `60` | このメトリクスの収集間隔（秒）。 |
| `measurement` | `mem_used_percent` | 収集するメモリの指標。メモリ使用率（%）を収集します。 |
| `metrics_collection_interval` | `60` | このメトリクスの収集間隔（秒）。 |

* 収集メトリクス (StatsD)

| パラメータ | 設定値 | 説明 |
|-----------|--------|------|
| `service_address` | `:8125` | アプリケーションからカスタムメトリクスを受け付けるStatsDリスナーのアドレスとポート。 |
| `metrics_collection_interval` | `10` | StatsDプロトコルでデータを受け付ける間隔（秒）。 |
| `metrics_aggregation_interval` | `60` | 受け付けたデータを集約し、CloudWatchへ送信する間隔（秒）。 |


　4.  **CloudWatch Agentの起動**
以下のコマンドで設定ファイルを読み込み、Agentを起動します。

```bash
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s
```

これで、`RADIUS-Server` で認証が発生するたびに、そのログが自動でCloudWatch Logsに送信されるようになります。
左がCloudWatchエージェントが取得した情報で、右側がRADIUSサーバ内の `radius.log`　です。かなり見にくいですが、きちんとログを収集できることがわかります。
![cwagentlog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075786/b28e2ddb-6367-4f95-a5c3-aee5f5d9bcbf.png)




## ステップ8: CloudWatch Logs Insightsでのログ分析

`radtest` コマンドで何度か認証を成功・失敗させてログを生成した後、AWSコンソールのCloudWatch画面を開きます。

1. 左側のメニューから「ログ」>「Logs Insights」を選択します。
2. 「ロググループを選択」で、先ほど設定した `RadiusServer/radius.log` を選びます。
3. クエリエディタに、SQLライクなクエリを入力してログを分析できます。

### クエリ
* 最新の認証ログ取得

```sql
fields @timestamp, @message
| sort by @timestamp desc
| limit 10
```
* 認証成功(Access-Accept)と失敗(Access-Reject)の件数を集計
```sql
fields @timestamp, @message
| filter @message like /Login OK/ or @message like /error/
| stats count(*) as event_count by
    if(@message like /Login OK/, 'Login OK', 'Error') as event_type
| sort by event_type asc
```
* 認証数の推移
```sql
fields @timestamp, @message
| filter @message like /Login OK/ or @message like /error/
| stats sum(if(@message like /Login OK/, 1, 0)) as LoginOK_Count, sum(if(@message like /error/, 1, 0)) as Error_Count by bin(1m) as interval
| sort by interval asc
```
## ステップ9: CloudWatch Dashboardでの可視化

Logs Insightsのクエリ結果は、そのままダッシュボードのウィジェットとして追加できます。

1. Logs Insightsで上記のクエリを実行した後、「アクション」>「ダッシュボードに追加」をクリックします。
2. 「新しいダッシュボードを作成」を選び、ダッシュボード名（例: `RADIUS-Monitoring`）を入力します。
3. ウィジェットの種類（折れ線グラフ、円グラフ、数値など）を選択し、「ダッシュボードに追加」をクリックします。

これを繰り返すことで、以下のようなウィジェットを持つダッシュボードを作成できます。
みなさんもお好きなメトリクスを収集して、オリジナルを作ってみてください！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075786/be59c8be-fe54-413c-987a-c36718e0ff36.png)



# おわりに
今回は AWS 環境で RADIUS サーバーを構築し、認証テストを行うまでの一連の流れを解説しました。
エラーがでそうなところは可能な限りフォローしましたが、こんなエラーがでる！等ありましたらご連絡ください～！