オンプレから格安で閉域 AWS 環境に繋ぐため IPv6 対応 VPN Gateway 代わりの EC2 インスタンスを strongSwan で構築

AWS に検証用の閉域環境を作り、自宅オンプレミスのルーター（YAMAHA RTX1200）と Site-to-Site で接続するのに安い方法はないかと調べていたのですが、AWS 側で VPN 用のグローバル IPv4 アドレスを払い出すと課金されるため、無料で付与されるグローバル IPv6 アドレスを使ってうまく費用を抑えられないか？と思い立ちました。

しかし、AWS VPN Gateway は IPv6 でのトンネルに対応しておらず、またオンプレミス側にも固定のグローバル IPv4 アドレスがないと難しいです。IPv6 でのトンネルに対応している Transit Gateway はアタッチメント料金が高くて厳しく、Direct Connect は論外……。

そこで、VPC のパブリックサブネットに EC2 インスタンスを立てて、IPv6 で自宅のルーターと Site-to-Site の IPSec VPN を張れば、安価に自宅と AWS の閉域環境（プライベートサブネット）を接続できるのではないかと思い、検証してみました。

結論から言うと、EC2 の Linux で strongSwan を立てていわゆるルーターインスタンスとすることで、EC2 に払い出される IPv6 アドレスで自宅のルーターと IPSec で Site-to-Site VPN を張り、自宅オンプレミスから AWS の VPC にあるプライベートサブネットへ接続することができました。以下に構築時のメモを記載します。

## 構成図

今回作成する検証環境は次のとおりです。パブリックサブネットにいる EC2 にパブリック IPv6 アドレスで IPSec トンネルを作り、その中にプライベート IPv4 アドレスの通信を通すことで、自宅オンプレミスとプライベートサブネットとの相互通信を目指します。

![IPv6-VPNGateway.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443658/4aac9942-7243-497c-bb4d-43609584d128.png)

## AWS 側の設定

### IPv6 対応のパブリックサブネットを作成する

オンプレミスから接続したいプライベートサブネットのある VPC に、「IPv6 CIDR ブロック」を設定したパブリックサブネットを作成します。IPv4 CIDR ブロックの方は今回の検証では特に使わないので、任意の設定で問題ありません。

### パブリックサブネットに EC2 インスタンスを起動する

パブリックサブネットに VPN Gateway 代わりとする EC2 インスタンスを立てます。AMI には Ubuntu Server 24.04 LTS を選択します。Amazon Linux にしなかった理由は、Amazon Linux だと公式リポジトリから strongSwan をインストールできないためです（このインスタンスはグローバル IPv4 アドレスを持たず、VPC に NAT Gateway もない想定なので、外部のパッケージを入れなくても完結するようにしたい）。

また、ポイントとして、EC2 の「ネットワーク設定」で「パブリック IP アドレスの自動割り当て」を「無効化」とします。これによりグローバル IPv4 アドレスは払い出されないため、ENI の IPv4 アドレス料金はかからなくなります。逆に「IPv6 IP を自動で割り当てる」は「有効化」します。

### EC2 インスタンスのネットワークの送信元アドレス検証を無効化

EC2 の ENI 上にトンネルを通して通信できるようにするため、送信元アドレス検証を無効にします。

![スクリーンショット 2025-02-22 195855.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443658/d613300f-abbd-4895-adfe-14f1e2107121.png)

### IPv6 用のインターネットゲートウェイとルートテーブルを作成する

VPC にインターネットゲートウェイを作成してアタッチします。

次に作成したパブリックサブネットと関連付けるルートテーブルを作成し、IPv6 のデフォルトルート（::/0）の宛先をこのインターネットゲートウェイに設定します。

これでパブリックサブネットから IPv6 で外部のインターネットと接続できるので、パブリックサブネットの EC2 インスタンスから自宅オンプレミスのルーターへまずはグローバル IPv6 アドレス同士で疎通できるようになります。

また、併せて自宅オンプレミス側のサブネットのネクストホップを EC2 の ENI に設定しておきます。

![スクリーンショット 2025-02-22 200232.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443658/71d33652-1579-485a-91b1-460be510c1c4.png)

### IPSec に必要なポートをセキュリティグループで開放する

自宅オンプレミスのルーターと EC2 インスタンスで IPSec を張れるようにするため、セキュリティグループで UDP 500 番ポートとプロトコル番号 50（ESP）を自宅オンプレミスのルーターのグローバル IPv6 アドレスから接続できるよう設定します。

また、IPSec とは無関係ですが、併せて EC2 インスタンスに SSH できるよう TCP 22 番ポートも開けておきます。今回 EC2 インスタンスにグローバル IPv4 アドレスを設定していないため、Session Manager で EC2 インスタンスを操作できません。VPC エンドポイントを作れば Session Manager で接続できますが、今回費用を安く抑える目的があるので、SSH はグローバル IPv6 アドレスで接続しています。

![スクリーンショット 2025-02-22 200517.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443658/b7cb0360-1e68-48b1-a88d-5beb6d28f7d5.png)

## strongSwan の設定

### strongSwan のインストール

Ubuntu Server 22.04 LTS の公式リポジトリから apt で strongSwan をインストールします。

```console
# apt install strongswan
```

swanctl と systemd で strongSwan を管理したいため、strongswan-starter と strongswan-charon をアンインストールし、strongswan-swanctl と charon-systemd をインストールします。

```console
# apt remove strongswan-starter strongswan-charon
# apt install strongswan-swanctl charon-systemd
```

strongSwan を起動し、以降 OS 起動時に自動で strongSwan を起動するようにします。

```console
# systemctl start strongSwan
# systemctl enable strongSwan
```

### strongSwan の設定ファイルの編集

strongSwan の設定ファイルは以下のとおり。

- /etc/strongswan.conf
- /etc/strongswan.d/charon.conf
- /etc/swanctl/swanctl.conf

/etc/strongswan.conf と /etc/swanctl/swanctl.conf はデフォルトから変更せず、/etc/strongswan.d/conf.d ディレクトリに次のファイルを追加して設定することにします。

- /etc/swanctl/conf.d/connection.conf

また、今回 strongSwan をトンネルモード IPSec（Route-based VPN）で設定し、トンネルに VTI を割り当てたいので、strongSwan 起動時に VTI を作るシェルスクリプトを追加します。

- /etc/swanctl/updown.sh

以下具体的な設定例です。strongSwan の公式ドキュメントを参考にして設定しました。

https://docs.strongswan.org/docs/5.9/features/routeBasedVpn.html

トンネルモードの場合、charon の istall_routes を無効にする必要があります。これが有効なままだと、VTI をネクストホップに指定したルーティングテーブルを設定しても動作しません。また、VTI にはシェルスクリプトで IP アドレスを割り当てたいので、仮想 IP アドレスプールから仮想 IP を払い出されないよう istall_virtual_ip も無効にしています。

```/etc/strongswan.d/charon.conf
charon {
    install_routes = no
    install_virtual_ip = no
    # 中略
}
```

コネクションに関する設定です。IPSec のパラメータは以下のとおりとします。

| 項目 | 設定値 |
|:-:|:-:|
| IKE バージョン | 2 |
| IKE 認証方式  | 事前共有鍵 |
| IKE グループ識別子 | modp2048 |
| ハッシュアルゴリズム | sha256 |
| 暗号ペイロード | ESP |
| 暗号アルゴリズム | AES128-CBC |

検証目的なので認証方式は事前共有鍵としました。トンネルモードの場合、local_ts と reomote_ts は 0.0.0.0/0 にする必要があります。

```/etc/swanctl/conf.d/connection.conf
connections {
   site-to-site {
      local_addrs  = 2001:db8:dead:beaf::1:2:3 # EC2 インスタンスの IPv6 アドレス
      remote_addrs = 2001:db8:feed::4:5:6 # 自宅オンプレミスのルーターの IPv6 アドレス

      local {
         auth = psk
         id = 2001:db8:dead:beaf::1:2:3 # EC2 インスタンスの IPv6 アドレス
      }
      remote {
         auth = psk
         id = 2001:db8:feed::4:5:6 # 自宅オンプレミスのルーターの IPv6 アドレス
      }
      children {
         net-net {
            local_ts  = 0.0.0.0/0
            remote_ts = 0.0.0.0/0
            esp_proposals = aes128-sha256
            mode = tunnel
            mark_in = 42
            mark_out = 42
            updown = /etc/swanctl/updown.sh # 後述する VTI 作成用シェルスクリプトのパス
         }
      }
      version = 2
      mobike = no
      proposals = aes128-sha256-modp2048
   }
}

secrets {
 ike-1 {
  id = 2001:db8:dead:beaf::1:2:3 # EC2 インスタンスの IPv6 アドレス9
  secret = "hogehogefugafuga" # 事前共有鍵
 }
}
```

strongSwan 起動時に読み込ませて VTI を作るシェルスクリプトです。

```bash:/etc/swanctl/updown.sh
#!/bin/bash

set -o nounset
set -o errexit

VTI_IF="vti1"
VTI_LOCALADDR=10.255.0.253/30 # トンネルに割り当てる IP アドレス
VTI_DESTINATION=10.1.0.0/16 # 自宅オンプレミス側のサブネット

case "${PLUTO_VERB}" in
    up-client)
        ip -6 tunnel add "${VTI_IF}" local "${PLUTO_ME}" remote "${PLUTO_PEER}" mode vti6 key "${PLUTO_MARK_OUT%%/*}"
        sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1" # トンネルモードのため SPD を無効に
        sysctl -w "net.ipv4.conf.${VTI_IF}.rp_filter=0" # 送信元アドレス検証を無効に
        ip addr add "${VTI_LOCALADDR}" dev "${VTI_IF}"
        ip link set "${VTI_IF}" up
        ip route add "${VTI_DESTINATION}" dev "${VTI_IF}"
        ;;
    down-client)
        ip tunnel del "${VTI_IF}"
        ;;
esac
```

設定が終わったら strongSwan を再起動します。

## 自宅オンプレミスルーター（YAMAHA RTX1200）の設定

IPSec VPN に関する部分のみを抜粋します。

```
ip route 10.128.128.0/20 gateway tunnel 2 # VPC のプライベートサブネット

tunnel select 2
 ipsec tunnel 2
  ipsec sa policy 2 2 esp aes-cbc
  ipsec ike version 2 2
  ipsec ike group 2 modp2048
  ipsec ike hash 2 sha256
  ipsec ike keepalive log 2 on
  ipsec ike keepalive use 2 on rfc4306 10 6 0
  ipsec ike local address 2 2001:db8:feed::4:5:6 # 自宅オンプレミスのルーターの IPv6 アドレス
  ipsec ike local name 2 2001:db8:feed::4:5:6 ipv6-addr
  ipsec ike pre-shared-key 2 hogehogefugafuga # 事前共有鍵
  ipsec ike remote address 2 2001:db8:dead:beaf::1:2:3 # EC2 インスタンスの IPv6 アドレス
  ipsec ike remote name 2 2001:db8:dead:beaf::1:2:3 ipv6-addr
 ip tunnel address 10.255.0.254/30 # トンネルのオンプレミス側 IP アドレス
 ip tunnel remote address 10.255.0.253 # トンネルの EC2 側 IP アドレス
 ip tunnel tcp mss limit auto
 tunnel enable 2
```

## IPSec の動作確認

### EC2（Ubuntu Server 22.04 LTS）の動作確認

vti が作成され、IP アドレスが設定されています。

```console
# ip addr
6: vti1@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 8961 qdisc noqueue state UNKNOWN group default qlen 1000
    link/tunnel6 2001:db8:dead:beaf::1:2:3 peer 2001:db8:feed::4:5:6 permaddr 2001:db8:abcd::
    inet 10.255.0.253/30 scope global vti1
       valid_lft forever preferred_lft forever
    inet6 fe80::dead:beaf:feed:1234/64 scope link
       valid_lft forever preferred_lft forever
```

自宅オンプレミス側のサブネットへのルーティングテーブルが正しく設定されていることを確認します。charon.ip_routes を無効にしていないと、スタティックルートを設定しても自宅オンプレミス側のサブネットへのネクストホップが VTI ではなく実インターフェイスに向いてしまい、正しく通信できないので注意です。

```console
# ip route
10.1.0.0/16 dev vti1 scope link
10.255.0.252/30 dev vti1 proto kernel scope link src 10.255.0.253
# ip route get 10.1.0.1
10.1.0.1 dev vti1 src 10.255.0.253 uid 1000
    cache
```

### オンプレミス自宅ルーター（YAMAHA RTX1200）の動作確認

SA が確立していることを確認します。

```
sa   sgw isakmp connection   dir  life[s] remote-id
-----------------------------------------------------------------------------
1     2    -    ike          -    26744   2001:db8:dead:beaf::1:2:3
9
3     2    1    tun[002]esp  send 26744   2001:db8:dead:beaf::1:2:3
9
6     2    1    tun[002]esp  recv 26744   2001:db8:dead:beaf::1:2:3
```

ルーティングテーブルが正しく設定されていることを確認します。

```
show ip route
Destination         Gateway          Interface       Kind  Additional Info.
10.128.128.0/20     -                 TUNNEL[2]    static
10.255.0.252/30     -                 TUNNEL[2]  implicit
```

あとは自宅オンプレミス側から VPC のプライベートサブネットに通信が通れば、IPv4 over IPv6 の Site-to-Site IPSec VPN が完成です。

## BGP の設定

せっかくなので、ルーターインスタンスにした EC2 と BGP で経路交換してルーティングテーブルを設定できるようにします。

ただ残念ながら、本当の仮想プライベートゲートウェイではないため、VPC のルートテーブルに BGP で経路情報を伝搬できないので、VPC のルートテーブルは静的に追加する必要があります。

### BIRD のインストールと設定

Ubuntu Server 22.04 LTS の公式リポジトリから apt で BIRD の version 2 系をインストールします。

```console
# apt install bird2
```

ログファイルを BIRD デーモンから書き込めるようにしておきます。

```console
# touch /var/log/bird.log
# chown bird:bird /var/log/bird.log
```

BIRD を起動し、以降 OS 起動時に自動で BIRD を起動するようにします。

```console
# systemctl start bird
# systemctl enable bird
```

BIRD の設定ファイルを変更します。やり方は色々あると思いますが、ポイントは VPC のプライベートサブネットへの経路情報をどう自宅オンプレミス側に流すかで、今回は BIRD の設定ファイルでプライベートサブネットへのスタティックルートを設定するようにしました。なお、プライベートサブネットへのネクストホップは、EC2 のいるパブリックサブネットのデフォルトゲートウェイになります。

また、検証機材が YAMAHA RTX1200 と古く、そのままだと Unsupported optional error が発生して BGP が確立しませんでした。RTX1210 以降であれば bgp neighbor のオプションで capability を無視できるのですが RTX1200 だとできないので、BIRD 側で capabilities を無視する設定としました。

```/etc/bird/bird.conf
router id 10.255.0.253; # EC2 側のトンネルの IP アドレス

protocol device {
}

protocol direct direct1 {
        ipv4; # Connect to default IPv4 table
}

protocol kernel {
        ipv4 {
                import none; # Import to table, default is import all
                export filter {
                        if proto = "direct1" then reject;
                        accept;
                };
        };
}

protocol static {
        ipv4; # Again, IPv4 channel with default options
        route 10.128.128.0/20 via 10.128.32.1; # VPC のプライベートサブネットへのネクストホップ
}

protocol bgp bgp1 {
        local as 65532; # 一意のプライベート AS 番号
        source address 10.255.0.253; # EC2 側のトンネルの IP アドレス
        neighbor 10.255.0.254 as 65534; # 自宅オンプレミスルーター側に設定する AS 情報
        ipv4 {
              import all;
              export filter {
                      if net ~ [ 10.128.0.0/16+] then accept; # VPC のサブネットへのルートは全部自宅オンプレミス側に流す
                      if source = RTS_BGP then accept;
                      reject;
              };
        };
        capabilities off; # RTX1200 では Unsupported option error となるため
};
```

設定が終わったら BIRD を再起動します。

### オンプレミス自宅ルーター（YAMAHA RTX1200）の BGP 設定

一旦スタティックルートで追加したパブリックサブネットへのルーティングテーブルを削除します。

```
# no ip route 10.128.128.0/20 gateway tunnel 2
```

BIRD に設定した BGP の情報と合わせるように RTX1200 側も設定します。

```
bgp use on
bgp autonomous-system 65534 # 一意のプライベート AS 番号
bgp neighbor 1 65532 10.255.0.253 # EC2 側のトンネルの IP アドレス
bgp router id 10.255.0.254 # 自宅オンプレミスルーター側のトンネルの IP アドレス
bgp import filter 1 include 10.1.0.0/16 # 自宅オンプレミスルーターの LAN 側のサブネット
bgp export filter 1 include 10.128.0.0/16 # VPC のパブリックサブネット
```

設定が終わったら BGP 設定を再読込みします。

```
bgp configure refresh
```

### BGP の動作確認

BIRD で正しく BGP が確立してルーティングテーブルが設定されているか確認します。

```console
# birdc show protocol
BIRD 2.14 ready.
Name       Proto      Table      State  Since         Info
device1    Device     ---        up     09:20:06.823
direct1    Direct     ---        up     09:20:06.823
kernel1    Kernel     master4    up     09:20:06.823
static1    Static     master4    up     09:20:06.823
bgp1       BGP        ---        up     09:20:10.965  Established

# birdc show route
BIRD 2.14 ready.
Table master4:
10.1.0.0/16          unicast [bgp1 09:20:10.988] * (100) [AS65534i]
        via 10.255.0.254 on vti1
10.128.128.0/20      unicast [static1 09:20:06.823] * (200)
        via 10.128.32.1 on ens5
10.255.0.252/30      unicast [direct1 09:20:06.825] * (240)
        dev vti1
10.128.32.0/20       unicast [direct1 09:20:06.825] * (240)
        dev ens5

# ip route
default via 10.128.32.1 dev ens5 proto dhcp src 10.128.36.186 metric 100
10.1.0.0/16 dev vti1 scope link # 元々シェルスクリプトで設定していたスタティックルート
10.1.0.0/16 via 10.255.0.254 dev vti1 proto bird metric 32 # 新たに BIRD が BGP で受け取ったルート
10.128.0.2 via 10.128.32.1 dev ens5 proto dhcp src 10.128.36.186 metric 100
10.128.32.0/20 dev ens5 proto kernel scope link src 10.128.36.186 metric 100
10.128.32.1 dev ens5 proto dhcp scope link src 10.128.36.186 metric 100
10.128.128.0/20 via 10.128.32.1 dev ens5 proto bird metric 32 # 新たに BIRD で設定したスタティックルート
10.255.0.252/30 dev vti1 proto kernel scope link src 10.255.0.253
```

BGP が Established になっています。

また、Ubuntu のルーティングテーブルに proto bird となっているものが BIRD で設定された経路情報で、自宅オンプレミス側のサブネットへの経路情報と、パブリックサブネットへの経路情報が追加されていることが分かります。

RTX1200 側でも確認します。

```
> show status bgp neighbor 10.255.0.253
BGP neighbor is 10.255.0.253, remote AS 65532, local AS 65534, external link
  BGP version 4, remote router ID 10.255.0.253
  BGP state = Established, up for 01:25:37
  Last read 00:00:28, hold time is 240, keepalive interval is 80 seconds
  Received 75 messages, 0 notifications, 0 in queue
  Sent 69 messages, 0 notifications, 0 in queue
  Connection established 26; dropped 25
  Last reset 01:25:41
Local host: 10.255.0.254, Local port: 179
Foreign host: 10.255.0.253, Foreign port: 55421

> show ip route
Destination         Gateway          Interface       Kind  Additional Info.
10.1.0.0/24         10.1.0.254             LAN3  implicit
10.1.15.0/24        10.1.15.254            LAN1  implicit
10.128.32.0/20      10.255.0.253      TUNNEL[2]       BGP  path=65532
10.128.128.0/20     10.255.0.253      TUNNEL[2]       BGP  path=65532
```

こちらも正しく設定されていることが確認できました。

これで自宅オンプレミス側と AWS VPC のプライベートサブネット側で互いに通信できるようになっていると思います。

IPv6 を使えば少ない費用でオンプレミスと閉域 AWS 環境とのハイブリッド接続を検証できますね。

## 参考サイト

https://docs.strongswan.org/docs/latest/index.html

https://www.rtpro.yamaha.co.jp/RT/manual/rt-common/ipsec/ipsec_chapter.html

https://qiita.com/homelith/items/5121b580c1cc8bdea5c0

https://bird.network.cz/?get_doc&f=bird.html&v=20

https://www.rtpro.yamaha.co.jp/RT/docs/bgp/bgp4-command.html

https://qiita.com/tuna2134/items/0ecc57be4c2ad5dd2179