AWSにVPNServerをテンプレート化した

### はじめに
前回NATインスタンスをテンプレート化しました。今回はVPNServerをテンプレート化してみます。
AWSでは、以下のような３種類のVPNサービスが利用できますが、極力コストをかけたくないので、自前でテンプレートを用意します。
* AWS Client VPN（$0.15/時間・クライアント）
* AWS Site-to-Site VPN（グローバルIPを割り当てたルーターが必要、安価）
* AWS Direct Connect（専用回線なので高い、個人で利用することはほぼない）

https://qiita.com/mirutaro/items/d9d685a84b408da247eb

### 目指す構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/a70419f1-d77c-cb0f-a209-7a0efa4ff971.png)

VPNソフトウェアとしてsoftetherVPNを利用します。dockerイメージのsiomiz/softethervpnを使用させていただきます。非常に便利です。
また、設定した内容をEFSに保存します。テンプレートではVPNを使用するときだけEC2を作成し、使用しないときはEC2を削除するため、設定情報を外部に保管して、EC2をまたいで設定を維持できるようにします。

### 前準備
設定を保存するためのEFSを作成します。以下のブログを参考にさせていただきました。

https://engineers.weddingpark.co.jp/amazon-efs/

https://dev.classmethod.jp/articles/amazon-efs-iam-auth-deny-anonymous-access/

EFS用のセキュリティグループを作成します。VPNserverからインバウンド通信ポート番号2049を許可します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/765df08b-01cb-8cb7-db61-4724b24e3300.png)

EFSのファイルアクセスポリシーを設定して、VPNServerのEC2インスタンスプロファイルに設定したIamRoleからのみアクセスを許可する設定を行います。

```json:ファイルシステムポリシー
{
    "Version": "2012-10-17",
    "Id": "efs-policy-wizard-01be5f25-bb15-4014-ab63-cb5dda33d33e",
    "Statement": [
        {
            "Sid": "efs-statement-9e607ea3-d855-4067-b471-ec2a61203ba2",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::AWSアカウント:role/EC2に設定するインスタンスプロファイル"
            },
            "Action": [
                "elasticfilesystem:ClientRootAccess",
                "elasticfilesystem:ClientWrite",
                "elasticfilesystem:ClientMount"
            ],
            "Resource": "arn:aws:elasticfilesystem:ap-northeast-1:AWSアカウント:file-system/fs-xxxxxxxxxxxxxxxxxxxxxx",
            "Condition": {
                "Bool": {
                    "elasticfilesystem:AccessedViaMountTarget": "true"
                }
            }
        }
    ]
}
```

### テンプレート説明

ネットワーク設定部分です。本当はプライベートに配置して、NATトラバーサルを使用してNAT超えをしたかったのですが、うまくいかなかったため、パブリックに配置してグローバルIPアドレスを割当しています。どこからでもアクセスされたくないなので、事前に作成済みのセキュリティグループで自宅PCからのアクセスのみ許可しました。
```
NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: subnet-xxxxxxxxxxxxxx VPNServerを配置するパブリックサブネットのID
          GroupSet:
            - sg-xxxxxxxxxxxxxxxx VPNServer用のセキュリティグループ
```

efs-utilsをインストールして、EFSをIAM認証でマウントしています。
サーバのリブート自動マウントするようにしています。
```
          # efsマウント
          file_system_id_01=fs-xxxxxxxxxxxxxxx
          efs_mount_point=/mnt/efs
          mkdir -p $efs_mount_point
          echo "${file_system_id_01}:/ ${efs_mount_point} efs _netdev,noresvport,tls,iam 0 0" >> /etc/fstab
```
dockerをインストールしています。
```
          yum install -y docker
          systemctl enable docker
          service docker start
```
初期設定するためにsoftetherVPNのコンテナを起動します。コンテナのホスト名を指定しています。これはsoftetherのDDNS名を固定化する目的です。
```
          docker run -d -e SPW=任意のパスワード --hostname my-vpnserver-container --name softether-server --cap-add NET_ADMIN --network host --privileged siomiz/softethervpn
```

テンプレートの完全版です。
```yaml:sample.yaml
Parameters:
  EnableVPNServer:
    Description: Enable VPN Server Instance.
    Type: String
    Default: false
    AllowedValues: [true, false]

Conditions:
  EnableVPNServer:
    !Equals [true, !Ref EnableVPNServer]

Resources:
  VPNServer:
    Type: AWS::EC2::Instance
    Condition: EnableVPNServer
    Properties:
      KeyName: "EC2 キーペア名"
      ImageId: ami-0dfa284c9d7b2adad
      InstanceType: t3.nano
      IamInstanceProfile: "インスタンスプロファイル名"
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: subnet-xxxxxxxxxxxxxx VPNServerを配置するパブリックサブネットのID
          GroupSet:
            - sg-xxxxxxxxxxxxxxxx VPNServer用のセキュリティグループ
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp3
            VolumeSize: 8
            DeleteOnTermination: true
      Tags:
        - Key: Name
          Value: "VPNServer"
      UserData:
        Fn::Base64: |
          #!/bin/bash
          sudo su
          # efs-utils インストール
          yum update -y
          yum install amazon-efs-utils -y
          # efsマウント
          file_system_id_01=fs-xxxxxxxxxxxxxxx
          efs_mount_point=/mnt/efs
          mkdir -p $efs_mount_point
          echo "${file_system_id_01}:/ ${efs_mount_point} efs _netdev,noresvport,tls,iam 0 0" >> /etc/fstab
          # dockerインストール
          yum install -y docker
          systemctl enable docker
          service docker start
          # 初期設定後は以下はコメントアウトしてください。
          docker run -d -e SPW=任意のパスワード --hostname my-vpnserver-container --name softether-server --cap-add NET_ADMIN --network host --privileged siomiz/softethervpn
          # 初期設定後は以下のコメントを外す
          # docker run -d --hostname my-vpnserver-container --name softether-server --cap-add NET_ADMIN --network host --privileged -v $efs_mount_point/vpn_server.config:/usr/vpnserver/vpn_server.config siomiz/softethervpn
```
### 初回起動時
テンプレートの使い方はNATインスタンスと同様です。

VPNサーバにログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/7e758f7b-7173-f33f-5606-5452ed82ce47.png)

「docker ps」でVPNServerのコンテナ名を確認し、「docker exec -it コンテナ名 bash」でログインします。
コンテナにログインしたら、「/usr/vpccmd/vpncmd」で、VPNserverの管理コマンドラインを起動します。

1の「Management of VPN Server or VPN Bridge」を選択します。
「Hostname of IP Address of Destination:」には空入力でOKです。
「Specify Virtual Hub Name:」にも空入力でOKです。
「Password:」に、テンプレートのSPW=に記入したパスワードを入力します。
うまくいくと、以下のようにVPN Serverのプロンプトが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/07d14bbe-8496-d475-706a-49c4dc2ee734.png)

DDNS名を確認するため、「DynamicDNSGetStatus」と入力します。
赤枠部分をメモして、「exit」(2回)します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/63d076ad-9f98-ba18-dc69-10833204bb01.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/3f2df491-fdab-da41-43ee-9e1896bf4539.png)

次に、VPNServerの設定ファイルをEFS上にコピーします。
```
docker cp softether-server:/usr/vpnserver/vpn_server.config /mnt/efs/vpn_server.config
```
「Successfully copied 15.9kB to /mnt/efs/vpn_server.config」が表示されたら、設定ファイルの外部保存が成功していますので、一旦テンプレートを削除してVPNServerインスタンスを削除します。

### テンプレートの編集

今度はefsの設定ファイルをdockerコンテナが読み込むようにテンプレートを編集して、VPNServerを起動します。
```
docker run -d --hostname my-vpnserver-container --name softether-server --cap-add NET_ADMIN --network host --privileged -v $efs_mount_point/vpn_server.config:/usr/vpnserver/vpn_server.config siomiz/softethervpn
```

### GUIで管理画面に接続。
Windowsに「SoftEhter VPN Server Manager」をインストールします。インストール手順は省略します。
新しい接続設定のホスト名に事前にメモしたDDNS名を入力します。また、管理パスワードは前回の起動時に使用したテンプレートのSPW=に記入したパスワードを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/47f3bb9f-0dcf-9d35-ef30-9e19aa13a598.png)

うまくいけば、これで接続すると下図のようにログインでき、GUI操作が可能になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3636209/2f1ea4a6-65ed-8b91-9d6a-087781abdcea.png)

管理パスワードの変更や各種ネットワークの設定が可能となります。
※設定情報のConfigファイルへの反映は5分間隔です。強制的に反映させるためには管理コマンドラインで「flush」を実行します。

### まとめ
VPNServerをテンプレート化することで、使いたいときだけ必要最小限のコストでVPN機能を利用できるようになりました。あくまでVPNServerをGUIで操作できるようになっただけで、まだVPNを設定したわけではないので、ようやくスタートラインに立った感じです。次は、このsoftehterVPNを設定して、ローカルPCとAWSのVPN接続を試してみようと思います。

また、NATインスタンスでSoftEtherのNATトラバーサルが機能しなかったことが気になりますので、余裕ができたら調べてみようと思います。

### 2024.1.7追記
この方法ではVPNServer側でローカルブリッジが機能しませんでした。SecureNATを有効にすれば機能します。
以下のフォーラムに書いてありましたが、EC2のNICがプロミスキャスモードに対応できないのが原因のようでした。

https://www.vpnusers.com/viewtopic.php?t=64776

もともとAWSとの接続でアドレス変換をさせたくなかったため、今度はAWS側にVPNSeverではなくVPN Clientの設定を試してみようと思います。

### 2024.1.21追記
SoftetherでTAPデバイスを追加してブリッジさせることができそうなことがわかったため、AWSとローカルPC環境をアドレス変換せずにVPN接続について後日試してみる予定です。