strongSwanとVTIを用いたLinuxでのルートベースIPsec VPN構築:クラウドとオンプレミス間のセキュアな接続

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

背景と導入の理由

オンプレミスのインフラストラクチャをAWS、Azure、Google Cloudに接続することは、日常的によくある課題です。Linux上でIPsecのサイト間(Site-to-Site)トンネルを構築する際、ポリシーベース(Policy-based)またはルートベース(Route-based)の2つのモデルから選択する必要があります。

従来のIPsec構成の多くは、ポリシーベースのトンネルを採用していました。この方式では、Linuxカーネルが送信パケットをセキュリティポリシーデータベース(SPD: Security Policy Database)と照合します。パケットが設定済みのサブネットペア(例えば 192.168.1.0/24 から 10.0.0.0/16 への通信)に一致すると、カーネルによってパケットが暗号化されます。

これは正常に動作するものの、トラブルシューティングが極めて困難です。ip link コマンドを実行してもネットワークインターフェースが表示されず、標準のLinuxルーティングテーブルからトンネルへトラフィックを直接転送することもできません。さらに厄介なのは、DevOpsチームが新たに 10.50.0.0/16 といったサブネットを追加するたびに、ipsec.conf を更新してセキュリティアソシエーション(SA: Security Association)を再ネゴシエーションしなければならず、一時的な通信切断が発生することです。

ルートベースVPNは、IPsecを仮想トンネルインターフェース(VTI: Virtual Tunnel Interface)に紐付けることで、このアーキテクチャ上の課題を解決します。ハードコードされたIP範囲に一致させる代わりに、暗号化ポリシーを mark = 42 のような数値マークに関連付けます。トンネルはワイルドカードセレクタ(0.0.0.0/0 <=> 0.0.0.0/0)をネゴシエーションし、vti0 という仮想インターフェースを経由してトラフィックをルーティングします。

vti0 は物理ネットワークカードとまったく同様に扱うことができます。ip route add を使ったサブネットの転送、iftop によるリアルタイムの帯域幅モニタリング、nftables によるトラフィックフィルタリング、さらにはBGPフェイルオーバーを目的としたFRRouting(FRR)などの動的ルーティングデーモンの実行も可能です。

ここでは、パブリックインターネットを介した2つのエンドポイント間にトンネルを構築します:

  • ローカルゲートウェイ(オフィス / データセンター): パブリックIP 198.51.100.10、ローカルLAN 192.168.1.0/24、トンネルVTI IP 10.254.0.1/30
  • クラウドゲートウェイ(AWS EC2 / GCP Compute Engine / リモートVM): パブリックIP 203.0.113.20、クラウドVPC 10.0.0.0/16、トンネルVTI IP 10.254.0.2/30
  • XFRMマークID: 42(VTIインターフェースとカーネルのIPsec状態を紐付け)

インストール

以下のコマンドはUbuntu 22.04/24.04およびDebian 12を対象としています。RHELやRocky Linuxをご利用の場合は、apt を dnf に置き換え、strongswan と strongswan-libcharon をインストールしてください。

1. strongSwanパッケージのインストール

公式リポジトリからstrongSwanと関連する暗号化モジュールを取得します:

sudo apt update
sudo apt install -y strongswan strongswan-pki libcharon-extra-plugins

2. Linuxカーネルモジュールの有効化

VTIは ip_vti と xfrm4_tunnel という2つのカーネルモジュールに依存します。これらを直ちにロードします:

sudo modprobe ip_vti
sudo modprobe xfrm4_tunnel

システムの再起動後もこれらのドライバーが自動ロードされるよう、/etc/modules-load.d/ipsec-vti.conf に設定を保存します:

echo "ip_vti" | sudo tee -a /etc/modules-load.d/ipsec-vti.conf
echo "xfrm4_tunnel" | sudo tee -a /etc/modules-load.d/ipsec-vti.conf

3. カーネルパラメータのチューニング(sysctl)

ゲートウェイがインターフェース間でパケットを転送するため、IPフォワーディングを有効にしておく必要があります。また、Linuxのリバースパスフィルタ(rp_filter)はデフォルトで厳格モード(1)になっています。厳格モードでは、ルーティングテーブルの不一致によって検証に失敗するため、vti0 から返送されるパケットがサイレントに破棄されてしまいます。ルーズモード(2)に設定することで、このサイレントパケットロスを防止できます。

以下のネットワーク設定を /etc/sysctl.d/99-ipsec-vti.conf に書き込みます:

sudo tee /etc/sysctl.d/99-ipsec-vti.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.disable_policy = 1
net.ipv4.conf.all.disable_policy = 1
EOF

sudo sysctl --system

設定手順

ステップ1: strongSwan IPsecポリシーの設定

ローカルゲートウェイで /etc/ipsec.conf を開きます。ルーティングされたすべてのトラフィックを捕捉するため、leftsubnet と rightsubnet の双方に 0.0.0.0/0 を指定します。mark=42 パラメータは、ID 42でマークされたパケットのみを暗号化するようXFRMカーネル層に指示します:

sudo tee /etc/ipsec.conf <<'EOF'
config setup
    charondebug="ike 2, knl 2, cfg 2, net 1, esp 1"
    uniqueids=yes

conn %default
    keyexchange=ikev2
    ike=aes256-sha256-modp2048!
    esp=aes256-sha256-modp2048!
    dpdaction=restart
    dpddelay=30s
    dpdtimeout=120s
    auto=start

conn vti-tunnel
    left=198.51.100.10
    leftid=198.51.100.10
    leftsubnet=0.0.0.0/0
    right=203.0.113.20
    rightid=203.0.113.20
    rightsubnet=0.0.0.0/0
    mark=42
    authby=secret
    type=tunnel
EOF

ステップ2: 事前共有鍵(PSK)の保存

強力な秘密鍵(32文字以上)を生成し、/etc/ipsec.secrets に保存します。rootユーザーのみが読み取れるようにファイルパーミッションを制限します:

sudo tee /etc/ipsec.secrets <<EOF
198.51.100.10 203.0.113.20 : PSK "K9#mQ2$zL8!vP5*xR7@wT1^bY4&cN3(e"
EOF

sudo chmod 600 /etc/ipsec.secrets

ステップ3: VTIデバイスのプロビジョニング

Linuxトンネルインターフェースをマーク 42 に紐付けます。標準的なEthernetのMTUは1500バイトです。ESPおよびSHA256によるIPsecカプセル化はおよそ50〜73バイトのオーバーヘッドを追加するため、パケットの断片化(フラグメンテーション)を防ぐためにトンネルのMTUを 1418 に下げます:

# マーク42に関連付けられたトンネルデバイスを作成
sudo ip tunnel add vti0 mode vti local 198.51.100.10 remote 203.0.113.20 key 42

# ポイントツーポイントの/30トンネルIPアドレスを設定
sudo ip addr add 10.254.0.1/30 peer 10.254.0.2 dev vti0

# インターフェースを起動
sudo ip link set dev vti0 up

# IPsecオーバーヘッドを考慮して安全なMTU値を設定
sudo ip link set dev vti0 mtu 1418

# 新しい仮想インターフェースにルーズモードのパスフィルタリングを適用
sudo sysctl -w net.ipv4.conf.vti0.rp_filter=2
sudo sysctl -w net.ipv4.conf.vti0.disable_policy=1

ステップ4: vti0経由のクラウドサブネットのルーティング

クラウドVPC(10.0.0.0/16)へトラフィックを向けるには、ルートエントリを1つ追加するだけです。vti0 に送られたパケットには自動的にマーク42が付与され、XFRMによって捕捉されて暗号化されます:

sudo ip route add 10.0.0.0/16 dev vti0

後からセカンダリVPC(例えば 172.16.0.0/20)を追加する必要が生じた場合でも、ip route add 172.16.0.0/20 dev vti0 を実行するだけで済みます。IPsecのリロードも、ネゴシエーションに伴うダウンタイムも発生しません。

ステップ5: インターフェースライフサイクルの自動化

手動で実行したコマンドは再起動時に失われます。strongSwanの leftupdown フックを利用して、フェーズ2の確立時に vti0 を自動起動し、トンネル切断時にはクリーンに削除するようにします。

このスクリプトを /usr/local/sbin/ipsec-vti.sh として保存します:

sudo tee /usr/local/sbin/ipsec-vti.sh <<'EOF'
#!/usr/bin/env bash

VTI_IF="vti0"
VTI_LOCAL="10.254.0.1/30"
VTI_REMOTE="10.254.0.2"
REMOTE_SUBNET="10.0.0.0/16"
VTI_KEY="42"

case "${PLUTO_VERB}" in
    up-client)
        ip tunnel add "${VTI_IF}" mode vti local "${PLUTO_ME}" remote "${PLUTO_PEER}" key "${VTI_KEY}"
        ip addr add "${VTI_LOCAL}" peer "${VTI_REMOTE}" dev "${VTI_IF}"
        ip link set dev "${VTI_IF}" mtu 1418 up
        sysctl -w "net.ipv4.conf.${VTI_IF}.rp_filter=2" > /dev/null
        sysctl -w "net.ipv4.conf.${VTI_IF}.disable_policy=1" > /dev/null
        ip route replace "${REMOTE_SUBNET}" dev "${VTI_IF}"
        ;;
    down-client)
        ip link set dev "${VTI_IF}" down 2>/dev/null
        ip tunnel del "${VTI_IF}" 2>/dev/null
        ;;
esac
EOF

sudo chmod +x /usr/local/sbin/ipsec-vti.sh

/etc/ipsec.conf 内の conn vti-tunnel ブロックに leftupdown=/usr/local/sbin/ipsec-vti.sh を追加します。

ステップ6: strongSwanの起動

サービスを再起動してIKEデーモンを初期化し、接続を確立します:

sudo systemctl restart strongswan-starter

動作確認とモニタリング

リンクの状態を、IKE/ESPステータス、トンネルインターフェースのメトリクス、エンドツーエンドのping、生パケットキャプチャの4段階で確認します。

1. IKEおよびESP SAの確認

ipsec statusall を使用してstrongSwanのステートマシンを確認します:

sudo ipsec statusall

フェーズ1およびフェーズ2のアソシエーションが確立(ESTABLISHED)されていることを確認します:

Security Associations (1 up, 0 connecting):
  vti-tunnel[1]: ESTABLISHED 3 minutes ago, 198.51.100.10[198.51.100.10]...203.0.113.20[203.0.113.20]
  vti-tunnel{1}:  INSTALLED, TUNNEL, reqid 1, ESP SPIs: c3a48e10_a2099df3
  vti-tunnel{1}:   0.0.0.0/0 === 0.0.0.0/0, mark 42

0.0.0.0/0 === 0.0.0.0/0, mark 42 のステータスが INSTALLED になっていれば、マーク42に一致するトラフィックを暗号化する準備がカーネルで整っていることを示しています。

2. インターフェースカウンタの確認

仮想インターフェースを通過するパケットの流れを確認します:

ip -s link show dev vti0

RX および TX のバイトカウンタを確認します。TX が増加しているにもかかわらず RX が0のままである場合は、クラウド側のファイアウォール(AWSのセキュリティグループやGCPのファイアウォールルール)を確認してください。UDPポート500および4500、さらにIPプロトコル50(ESP)が開放されていることを確認します。

3. エンドツーエンドのレイテンシとMTUのテスト

クラウドVPC内のホスト(例: 10.0.1.15)に対してICMPエコー要求を送信します:

ping -c 4 10.0.1.15

次に、Path MTUの問題を検証します。Don’t Fragment(DF)ビットをセットした1418バイトのパケットを送信します(ICMPペイロード1390バイト + IPv4ヘッダー20バイト + ICMPヘッダー8バイト = 1418バイト):

ping -M do -s 1390 -c 2 10.0.1.15

Frag needed and DF set (mtu = 1418) と表示された場合は、疎通するまでペイロードサイズを8バイトずつ減らし、その値に合わせて vti0 のMTUを下げてください。また、大容量のHTTP/SSHペイロードでのパケットドロップを防ぐために、iptables でTCP MSSクランピングを適用することも可能です:

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

4. リアルタイムパケットキャプチャ

vti0 上の平文トラフィックと、WANアダプタ(eth0)上の暗号化トラフィックを監視して、暗号化が正常に行われていることを確認します。

パブリックインターフェース上で、暗号化されたESPパケットのみが表示されることを確認します:

sudo tcpdump -n -i eth0 host 203.0.113.20 and proto 50

別のターミナルで vti0 を監視し、暗号化されていないアプリケーションペイロードを確認します:

sudo tcpdump -n -i vti0 icmp

もし eth0 で受信ESPパケットが確認できるにもかかわらず vti0 に何も表示されない場合は、dmesg | grep xfrm を確認してください。多くの場合、リバースパスフィルタリング(rp_filter)またはマークIDの不一致が原因です。

Share: