本文へスキップ
hdknr blog

WireGuard

カーネル実装の軽量 VPN。AllowedIPs による cryptokey routing で経路を表現する。AWS 上に中継ハブを置き、IP 制限された SaaS へ社外からオフィスの固定 IP で出る構成と、その運用上の落とし穴

概要

WireGuard は公開鍵で peer を識別する軽量な VPN。各 peer の AllowedIPs が「その peer へ送る宛先」と「その peer から受け入れる送信元」を兼ねる cryptokey routing で、最長一致で振り分けられる。AllowedIPs = 0.0.0.0/0 の peer を 1 つ置けば、それが事実上のデフォルトルートになる。

IP 制限のある SaaS へ社外から接続するため、AWS の EC2 に中継ハブを置き、オフィスの社内ノードで NAT してオフィスの固定 IP から出す構成を組んだ記録から、設計判断と落とし穴をまとめる。

詳細

中継ハブ構成

ハブの wg0.conf では Table = off が必須。AllowedIPs = 0.0.0.0/0 を素直に書くと wg-quick がハブ自身の通信までトンネルへ吸い込み、SSM が切れて管理経路を失う。クライアント由来のパケットだけを ip rule add from <client-cidr> table 200 のポリシールーティングで別テーブルへ振る。初回構築時は systemd-run --on-active=30min ... stop wg-quick@wg0 のデッドマンタイマーを保険に置く。

設計判断

運用上の落とし穴

落とし穴何が起きるか対処
systemd-networkd の ManageForeignRoutingPolicyRules=yesnetworkd の再起動で ip rule が消え、トンネルは繋がるのに出口へ行けない半壊状態。再起動テストでは PostUp が後勝ちするので再現しない。unattended-upgrades の systemd re-exec で起きるManageForeignRoutingPolicyRules=no
EC2 のステータスチェックOS が生きていれば半壊でも正常に見え、1 日気づかれなかった5 分ごとの自前ヘルスチェックをカスタムメトリクスへ
user_data の変更plan は updated in-place と出るが、実際は stop → 変更 → start想定外のリソースが動く plan で中断するガード
保存された新しい user_datacloud-init は再実行されないので実機は変わらない反映は SSM で別途配布
稼働中の wg0.conf[Interface] は初回に一度書かれただけの唯一のコピーバックアップ + wg-quick strip でパース検証
AMI を最新パラメータで追うreplace が計画され prevent_destroy で plan ごと落ち、peer の失効もできなくなるignore_changes = [ami]

ヘルスチェックは設定値の羅列より、カーネルの経路選択そのものを問うのが確実。

ip route get 1.1.1.1 from "$CLIENT_IP" iif wg0 | grep -q 'dev wg0' || note route-via-wg0

「無いべきものが在る」(POSTROUTING に MASQUERADE が生えた)も見る。自動修復はせず、検出してアラームを上げるところまでに留める。

利用者側の 2 つの障害

症状はどちらも「繋がっているのに何も開けない」だった。

  1. DNS の欠落 — AllowedIPs = 0.0.0.0/0 は DNS もトンネルへ送る。DNS を書かないと端末は自宅ルータ(192.168.x.1)へ問い合わせ、社内ノードが RFC 1918 宛として REJECT し名前解決が全滅する。DNS = 1.1.1.1, 8.8.8.8 を必須にした(社内ホスト名が引けないのは要件の帰結)。SaaS の IP はリゾルバで違う値が返るので、AllowedIPs を絞る split tunnel は成立しない
  2. MTU の過大 — WireGuard は内側のパケットに 60 バイト(IPv4 20 + UDP 8 + WG ヘッダ 16 + 認証タグ 16)を足す。MTU = 1420 は回線上 1480 バイトになり、フレッツ系 PPPoE(IPv4 上限 1454)を超える。ハンドシェイクや DNS や 350 バイトの応答は通り、TLS 証明書の数 KB で止まる。MTU = 1280(IPv6 の最小 MTU)で解消

MTU は DF ビットを立てた ping で測る(macOS なら ping -D -s <size>、内側のサイズは -s + 28)。

恒久対処: ハブで TCP MSS をクランプ

iptables -t mangle -A FORWARD -i wg0 -o wg0 -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --set-mss 1240

トラブルシュートの切り分け表

curl https://1.1.1.1/cdn-cgi/tracenslookup <host>原因
出ない—通信自体が通っていない
出る答えが返らないDNS 行の欠落
出る答えが返るMTU が大きすぎる

「名前を使わない疎通」だけでは足りない。名前解決の可否とパケットの大きさの 2 軸を、小さい応答と大きい応答の両方で測る。接続確認は IP 制限が可視化される API エンドポイントで行う(HTML パスは送信元に関係なく 200 を返すことがある)。

関連ページ

ソース記事