IPsec Site-to-Site VPN:strongSwanとBGPでLinuxとAWS VPCを接続する

土曜日の午前2時。フランクフルトのベアメタルラックとAWS EU-Central-1間のデータベースレプリケーションが突如停止しました。アラートチャンネルには大量の通知が押し寄せ、pingのパケットロス率は100%に達しています。

上流のコアスイッチが交換された際、ネイバー状態が再確立されなかったため、800 Mbpsのトラフィックスパイクによって静的GREトンネルがダウンしてしまいました。ハイブリッド接続が切断されると、トランザクションは瞬く間に滞留します。物理リンクがフラッピングした際にも、数秒で経路を再計算できる自動化されたルートベースIPsecトンネルが必要です。

LinuxエッジルーターとAmazon VPCを結ぶ本番環境のハイブリッドブリッジは、主に2つのツールで構成されます。strongSwanがIKEv2/IPsec暗号化レイヤーを担い、FRRouting(FRR)が動的なBGP経路交換を管理します。450 Mbpsのデータベーストラフィックを継続的に流した検証環境において、このアーキテクチャは14 msの安定したレイテンシを維持し、AWSのアベイラビリティゾーンのメンテナンス時にもTCPセッションを切断することなく乗り切りました。

クイックスタート(5分)

ここでは次のような基本構成を想定します。パブリックIP 203.0.113.10 とローカルサブネット 192.168.10.0/24 を持つUbuntu 22.04ゲートウェイを運用しています。AWS側では、VPC 10.0.0.0/16 に仮想プライベートゲートウェイ(VGW)をアタッチしてプロビジョニング済みです。AWSからは2つのトンネルエンドポイントが提供されます。ここではトンネル1を設定対象とし、リモートパブリックIP 198.51.100.1、トンネル内部のリンクローカルIP 169.254.10.1/30、事前共有キー(PSK)MySuperSecretKey123 を使用します。

まず、LinuxホストにstrongSwanとFRRoutingをインストールします。

sudo apt-get update
sudo apt-get install -y strongswan strongswan-pki frr

カーネルパラメータを調整してパケットフォワーディングを有効化し、リバースパスフィルタリング(RPF)による通信障害を防ぎます。

sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv4.conf.all.accept_redirects=0
sudo sysctl -w net.ipv4.conf.all.send_redirects=0
sudo sysctl -w net.ipv4.conf.default.rp_filter=2
sudo sysctl -w net.ipv4.conf.all.rp_filter=2

認証用のシークレットを /etc/ipsec.secrets に保存します。

# /etc/ipsec.secrets
203.0.113.10 198.51.100.1 : PSK "MySuperSecretKey123"

/etc/ipsec.conf にIKEv2プロポーザルと仮想トンネルインターフェース(VTI)のマークを定義します。

# /etc/ipsec.conf
config setup
    charondebug="ike 1, knl 1, cfg 0"
    uniqueids=no

conn %default
    keyexchange=ikev2
    ike=aes256-sha256-modp2048!
    esp=aes256-sha256-modp2048!
    dpdaction=restart
    dpddelay=10s
    dpdtimeout=30s
    lifetime=8h
    ikelifetime=24h
    type=tunnel

conn aws-tunnel-1
    left=203.0.113.10
    leftid=203.0.113.10
    leftsubnet=0.0.0.0/0
    right=198.51.100.1
    rightid=198.51.100.1
    rightsubnet=0.0.0.0/0
    mark=42
    auto=start

mark 42 に紐付けられた仮想トンネルインターフェースを作成します。

sudo ip tunnel add vti0 mode vti local 203.0.113.10 remote 198.51.100.1 okey 42 ikey 42
sudo ip addr add 169.254.10.2/30 remote 169.254.10.1 dev vti0
sudo ip link set vti0 up mtu 1399

strongSwanを起動し、フェーズ1およびフェーズ2のハンドシェイクを確認します。

sudo ipsec restart
sudo ipsec statusall
ping -c 3 169.254.10.1

pingの往復時間が20 ms未満で応答すれば、IPsecトンネルを介してパケットが正常に疎通しています。次は動的ルーティングの伝播設定に進みます。

詳細解説

LinuxルーターをAWSに接続する際、エンジニアがルーティングのブラックホール(パケット喪失)に直面することは珍しくありません。この問題のほとんどは、ポリシーベースIPsecとルートベースIPsecの混同が原因です。

ルートベースとポリシーベースのアーキテクチャ比較

従来のIPsecはセキュリティポリシーデータベース(SPD)に依存しています。Linuxカーネルは送信パケットを厳密なアドレスタプルと照合し、パケットが 192.168.10.0/24 -> 10.0.0.0/16 に一致する場合にのみESPパケットとして暗号化します。しかし、AWS側で 10.1.0.0/16 のような新しいサブネットを追加すると、トラフィックは何の通知もなくドロップされます。通信を許可するには、双方がChild SAを再ネゴシエーションしなければなりません。

動的ルーティングプロトコルを使用すると、この問題はさらに複雑になります。BGPはリンクローカルアドレス(169.254.0.0/16)経由でHelloパケットを交換しますが、これらは標準的なRFC 1918の暗号化ポリシーの範囲外となるためです。

VTI(Virtual Tunnel Interface)はこの問題を美しく解決します。leftsubnet=0.0.0.0/0 と rightsubnet=0.0.0.0/0 を指定し、XFRMファイアウォールマーク(mark=42)を付与することで、strongSwanは vti0 インターフェースから送出されるすべてのパケットを捕捉・暗号化します。これにより、トンネルにどのトラフィックを流すかを標準のLinuxルーティングテーブルで完全に制御できるようになります。

BGPピアリングに向けたFRRoutingの設定

/etc/frr/daemons を編集してBGPデーモンを有効化します。

bgpd=yes

/etc/frr/frr.conf で自律システム(AS)を設定します。このプロファイルでは、オンプレミス側のASNを 65000、AWS VGW側の標準ASNを 64512 とします。

frr version 8.1
frr defaults traditional
hostname linux-vpn-gw
!
router bgp 65000
 bgp router-id 192.168.10.1
 neighbor 169.254.10.1 remote-as 64512
 neighbor 169.254.10.1 description AWS-VPC-Tunnel1
 neighbor 169.254.10.1 timers 10 30
 !
 address-family ipv4 unicast
  network 192.168.10.0/24
  neighbor 169.254.10.1 activate
  neighbor 169.254.10.1 soft-reconfiguration inbound
 exit-address-family
!
line vty
!

新しいルーティングルールを適用します。

sudo systemctl restart frr

FRRの制御シェル(vtysh)から動的ネイバーテーブルを確認します。

sudo vtysh -c "show ip bgp summary"
sudo vtysh -c "show ip route bgp"

ネイバーの状態が Established になっていることを確認してください。10〜30秒以内に、カーネルのルーティングテーブルに 10.0.0.0/16 via 169.254.10.1 dev vti0 が表示されます。

高度な活用法

耐障害性を高めるデュアルトンネル冗長化の構築

AWSはVPN接続ごとに、異なるアベイラビリティゾーンにまたがる2つのパブリックゲートウェイエンドポイントを生成します。片方のエンドポイントしか終端していない場合、定期的なハイパーバイザーのアップデート時などに回線が切断されてしまいます。別のマーク(mark=43)にバインドした2つ目のVTIインターフェース(vti1)を構築しましょう。

通常運用時にAWSがトンネル1を優先するように、BGPのAS-Pathプリペンドを設定します。トンネル1がダウンした場合、AWSは30秒以内に対向のインバウンドトラフィックをトンネル2へ自動的に切り替えます。

route-map TO_AWS_T1 permit 10
!
route-map TO_AWS_T2 permit 10
 set as-path prepend 65000 65000
!
router bgp 65000
 neighbor 169.254.10.1 remote-as 64512
 neighbor 169.254.11.1 remote-as 64512
 !
 address-family ipv4 unicast
  neighbor 169.254.10.1 activate
  neighbor 169.254.10.1 route-map TO_AWS_T1 out
  neighbor 169.254.11.1 activate
  neighbor 169.254.11.1 route-map TO_AWS_T2 out
 exit-address-family

AWSはAS-Path長を評価し、戻りパケットをトンネル1経由でルーティングします。プライマリパスが不通になるまで、トンネル2はスタンバイ状態を維持します。

再起動後もVTIインターフェースを永続化する

ip tunnel コマンドで作成した一時的なインターフェースは、システムの再起動時に消失してしまいます。systemd-networkd を使ってインターフェース属性を永続化しましょう。まず、/etc/systemd/network/25-vti0.netdev にトンネルデバイスの定義を記述します。

[NetDev]
Name=vti0
Kind=vti

[Tunnel]
Local=203.0.113.10
Remote=198.51.100.1
Key=42

続いて、/etc/systemd/network/25-vti0.network でIPアドレスとフレームサイズ(MTU)を割り当てます。

[Match]
Name=vti0

[Network]
Address=169.254.10.2/30
MTUBytes=1399

実践的なTips

1. MSSクランピングでPath MTUブラックホールを防ぐ

ICMPのpingは通るのに、HTTPSやPostgreSQLのハンドシェイクがTLSネゴシエーション時にハングアップする場合、パケットサイズが原因です。カプセル化によって約56〜73バイトのIPsec ESPオーバーヘッドが付加されます。クライアントがDon’t Fragment(DF)ビットをセットした標準の1500バイトフレームを送信すると、トンネルエンドポイント側でパケットの破棄を余儀なくされます。

ファイアウォール側でTCPの最大セグメントサイズ(MSS)を直接クランプ(制限)します。

sudo iptables -t mangle -A FORWARD -o vti+ -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

これにより、ネゴシエーションを行うクライアントに対してTCPペイロードを最大1359バイトに制限させ、MTU 1399バイトのインターフェース上でのフラグメンテーションを防ぐことができます。

2. DPDタイマーのチューニングによる迅速な障害検知

デフォルトでは、AWSはアイドル状態または応答のないトンネルを30〜60秒後に切断します。/etc/ipsec.conf でアグレッシブなDead Peer Detection(DPD)パラメータを設定します。

dpdaction=restart
dpddelay=10s
dpdtimeout=30s

回線ノイズなどで通信が途切れた場合でも、strongSwanがハートビートの喪失を検知し、AWSがセッションを無効化する前に再認証をトリガーします。

3. 30秒でできるトリアージチェックリスト

接続が切断された場合は、次の3つの診断コマンドを順番に実行してください。

  1. 暗号化ステータスの確認: sudo ipsec statusall を実行します。IKE_SA と CHILD_SA の両方が ESTABLISHED になっているか確認してください。確立されていない場合は、journalctl -u strongswan を確認し、UDP 500/4500番ポートのドロップや事前共有キーの不一致がないかを調べます。
  2. インターフェースカウンターの確認: ip -s tunnel show vti0 を実行します。TX(送信)およびRX(受信)のカウンターが対称的に増加していることを確認してください。TXが大きく増加しているにもかかわらずRXが0の場合は、リモート側のセキュリティグループによる拒否やルーティングの設定ミスが疑われます。
  3. BGPピアリングの確認: sudo vtysh -c "show ip bgp summary" を実行します。ステータスが Active または Connect の場合は、FRRが 169.254.10.1 に到達できていません。vti0 のローカルアドレスを再確認し、ファイアウォール規則でTCPポート179が許可されているか検証してください。
Share: