VPN と Web を一台の公開ホストに:役割を分ける方法
一つの公開 IP でトンネルを終端し、HTTPS を提供し、二つのプライベート LAN に到達できます。まずアドレス、各ポート、転送ポリシーの所有者を決めます。
一台のマシンで三つの要件を同時に満たせます。トンネルを終端し、HTTPS に応答し、二つのプライベート LAN の間を転送します。それが妥当かどうかは、紙の上で決められる三点にかかっています。公開アドレスの所有者は誰か、各ポートとプロトコルの組の所有者は誰か、どのプロセスにパケット転送を許すか、です。この三点が正しければ、共用ホストは何の変哲もありません。誤れば、きれいに起動して決して応答しないサービスに週末を費やすことになります。
以下は決定手順であり、構築手順書ではありません。各手順は、いま追加した役割が本当に分離されていることを示す確認で終わります。
ポートより先にアドレスの所有者を決める
トンネルの終端と Web サーバーは同じカーネルを共有できます。同じアドレス、ポート、プロトコルを同時に共有することが失敗の原因です。WireGuard は通常 UDP 51820 を使い、OpenVPN は UDP 1194 か TCP 443 を使い、Web サーバーは TCP 443 を使います。痛いのは HTTPS の隣で TCP 443 を使う OpenVPN です。どちらも同じソケットを欲しがり、カーネルは先にバインドした側に渡します。手前にデマルチプレクサ、たとえば sslh や TLS を理解するプロキシを置いた場合だけ、このポートを共有できますが、その障害はファイアウォールの問題とまったく同じに見える部品が増えます。
何かを変える前に所有者を見ます。ss -lntup は、待ち受けている各ソケットのローカルアドレス、ポート、プロセスを出力します。0.0.0.0:443 を示す二行は、init スクリプトがどれほど陽気でも、二つ目のサービスが決してバインドしなかったことを意味します。ブリッジも所有者を移します。eth0 が br0 に加わると、IP は br0 へ移り、eth0 を名指すファイアウォール規則はすべて一致しなくなるので、サービスはブリッジ上でバインドし、ip -br addr で確認します。
同じ所有の規則が、仮想マシンで起きる意外な現象を説明します。同じ LAN 上のゲストの中で動くトンネルサーバーはゲストのアドレスを保つため、ホスト自身のアドレス宛ての接続はホストにとどまり、ゲストのトンネルはそれらを決して見ません。クライアントプロファイルが何と言おうと変わりません。
二つの LAN の間をルーティングするか、片側を NAT する
互いが見えない二つの LAN は、それぞれ公開ホストに接続でき、公開ホストがトンネル間を転送します。このルーティング構成には三つ必要です。転送を有効にすること、各遠隔プレフィックスへの経路、通過を許す転送ポリシーです。sysctl -w net.ipv4.ip_forward=1 が一つ目を、ip route add 10.20.0.0/24 dev wg1 が二つ目を、nftables の次のような行が nft add rule inet filter forward iifname "wg0" oifname "wg1" accept 三つ目を担います。
送信元 NAT は導入が簡単で、デバッグは難しくなります。マスカレードはクライアントのアドレスを書き換えるため、遠い側は本当のホストではなくハブを見ることになり、アクセス規則とログは帰属の役に立たなくなります。両方のネットワークが自分のものならルーティングしてください。片側が第三者のもので戻り経路を足さないなら、NAT が今週出せる選択肢です。いずれにせよアドレス計画を書いてください。重複する二つの /24 はどんな設定でもルーティングできません。
ノート PC のインターネット出口として働くことが、同じ箱で内向きのトンネルを終端することを妨げるわけではありません。妨げるのは重複したアドレスだけで、だからこそアドレス計画が重要な文書になります。
二つの役割を止めた状態で分割を試す
有用な試験は引き算です。二つの役割を止め、三つ目がまだ応答することを確かめます。トンネルサービスが落ちていても Web 要求は完了すべきで、Web サーバーが止まっていてもトンネルのハンドシェイクは完了すべきです。すべてが動いているときだけ両方が動くなら、共有ファイアウォール表、迷い込んだ経路、誤ったアドレスを指す名前といった偶発的な依存があります。
三つのコマンドがその試験を具体化します。curl -sk --resolve example.test:443:203.0.113.10 https://example.test/ は外部 DNS なしでローカルの待ち受けが応答することを示し、wg show はクライアント側でハンドシェイクの完了を示し、tcpdump -ni any port 51820 はハブ側でルーティングの問題と鍵の問題を切り分けます。キャプチャに一度も現れないハンドシェイクは、ホストより手前で死んでいます。プロバイダーのファイアウォール、誤った公開アドレス、途中の NAT 規則です。
転送する LAN でも同じ引き算を繰り返します。ピアを一つ切断すると、ハブをまたぐ ping は失敗すべきです。再接続すると、規則に触れずに ping が戻るべきです。トンネルを再起動して初めて戻る ping は、到達性ではなくポリシーを指しており、直し方が違います。
役割を引く間も規則集合を読みやすく保ちます。nft list ruleset は本当に必要な方向ごとに一つの許可規則を示すべきで、すべてを許可する転送チェーンは分離ではなく、動くデモです。
IPv6 のみのアクセス回線と IPv4 のサービス
アクセス回線によっては IPv6 の到達性しか与えず、サービスはそれでも IPv4 で公開しなければなりません。トンネル自体はまだ通信を通す側のファミリーで流れ、公開したサービスはトンネル内のアドレスに座ります。これは通常の前提を反転させ、監視を静かに壊します。IPv4 経路向けに書いたファイアウォール規則や稼働確認は、いま何も運ばないインターフェースを見ているのです。
各ファミリーは別々に試します。curl -4 と curl -6 で試すのは、一度の成功が試していない経路について何も語らないからです。ポートの所有はファミリー間で変わりませんが、待ち受けは変わります。ss -lntup は IPv4 と IPv6 のソケットを別の行として並べ、0.0.0.0:443 は [::]:443 を、プロセスがデュアルスタックのソケットを要求しない限り覆いません。
一方のファミリーで応答し他方で拒否するサービスは、通常は待ち受けの問題で、ルーティングの問題ではありません。設計を終わりと呼ぶ前に、各役割が自分のアドレスかポートを持ち、転送が有効で許可され、他の二つを止めても各役割がまだ応答することを確認してください。