二台目のOpenVPNサーバーを立ち上げても、クライアントが接続する場所が二つ目に増えるだけで、それ以上ではありません。そのクライアントが最初のサーバーのネットワークに入るわけではなく、client-to-client というオプションは一つのサーバープロセス内の転送を変えるだけです。二つのプールが一つのネットワークになるのは、方向ごとに責任者を決めて、意図的に両者の間をルーティングしたときです。以下はすべて、ユーザー空間の TUN モードで、その内部転送を切った状態を前提とします。判断を下すのはカーネルです。

誤解:アカウント一覧が一つならネットワークも一つ

認証と到達性が混同されるのは、どちらもサーバー設定の中にあるからです。二つのゲートウェイのどちらにもログインできる利用者は、外から見れば扉が二つある一つのシステムの利用者のように見えます。内側では、各サーバーが自分のプールからアドレスを配り、自分が終端するクライアントにだけルートを入れます。そのため、ゲートウェイ A のクライアントがゲートウェイ B のクライアントにパケットを送るとき、自分のサーバーが一度も聞いたことのない宛先を要求することになります。パケットは A のデフォルトルートへ流れ、トンネルを出て、許可を求められたことのないファイアウォールに届きます。

したがって二台目のサーバーを足しても、冗長性も検出も共有ブロードキャストドメインも増えません。増えるのは計画を必要とするルーティングされたネットワークであり、その計画はアドレッシングから始まります。

各ゲートウェイに独自のプールとトランジットアドレスを与える

他の何とも重ならない二つの範囲と、ホスト間の小さなトランジットリンク、それだけでアドレス計画の全体です。トランジットは自分が管理するプライベートなセグメントに置き、どのサーバーがリースを出すよりも前に数字を書き留めておきます。

GatewayClient poolTransit address
gateway-a10.64.10.0/24172.28.44.1/30
gateway-b10.64.20.0/24172.28.44.2/30

プロファイルを揃えたいからといって、両方のサーバーに同じプールを与えたくなりますが、こらえてください。カーネルのルートは、共有範囲内の任意のアドレスを今どちらのホストが持っているかを示せないため、ルーティングテーブルは一方のゲートウェイを指し、クライアントはもう一方につながっているかもしれません。インターフェース名はローカルの慣習であり、共有された状態ではありません。ここでは両方のホストがトンネルデバイスを tun0、トランジットリンクを transit0 と呼んでいますが、どのパケットもその名前を運ばないので問題ありません。

両側にルートを入れる

各ゲートウェイには、トランジットアドレス経由で相手のプールへのルートが必要で、どちらかが中継点として働くには転送が有効でなければなりません。まず現在のテーブルを確認し、次にエントリを追加して、再接続フックではなくホストのネットワークマネージャー経由で永続化します。

sysctl net.ipv4.ip_forward
ip route add 10.64.20.0/24 via 172.28.44.2 dev transit0
ip route get 10.64.20.37

もう一方のホストでもプールを入れ替えて同じことをします。無関係なスクリプトから同じルートを繰り返し追加すると、ホストに複数のコピーが残り、次に誰かがテーブルを読んだときに不可解な変更が見えることになります。トランジットリンク自体がトンネルなら、ルートはそのトンネルのトポロジーを指し、中継点は再接続を乗り越えなければなりません。三十秒消えるトランジットアドレスはプール間の経路も一緒に連れ去ります。これは発見するより先に明言しておくべき設計上の性質です。

所有権を偽らずにクライアントのルートを配る

クライアントには遠隔プールへのルートが必要です。なければ各自の OS がパケットをデフォルトゲートウェイへ送り、トンネルはそれを見ることもありません。サーバー設定はそれを届ける自然な場所です。

push "route 10.64.20.0 255.255.255.0"

ミラー行をもう一方のサーバーの設定に置き、テストクライアントを再接続してそのテーブルを読みます。配られたルートを無視するクライアントには、そのクライアント自身の承認済み設定が必要であり、決して届かないルートはアクセス制御ではありません。クライアントに、もう一方のゲートウェイのプール全体に対する iroute を与えてはいけません。このディレクティブは接続された一台のクライアントの背後にあるサブネットのためのもので、欠けたゲートウェイ間ルートを覆い隠すために使うと、到達できないアドレスを所有していると信じるサーバーが生まれます.

一つの要求を通し、別の要求を拒む

実際の操作を選びます。たとえば 10.64.10.21 のクライアントから 10.64.20.37 のサービスへの HTTPS を、両方のホストで正確に許可します。各ファイアウォールは受信側で要求を、戻り側で追跡された応答を許可しますが、宛先側のサービス自身のポリシーもそれを受け入れる必要があります。

iptables -A FORWARD -i tun0 -o transit0 -s 10.64.10.21 -d 10.64.20.37 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i transit0 -o tun0 -d 10.64.10.21 -s 10.64.20.37 -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED -j ACCEPT

相互ルートがある場合、送信元 NAT は不要であり、ここではむしろ有害です。送信元を変換すると、どのクライアントが要求したのかが隠れ、宛先側の送信元ベースのポリシーが台無しになります。インターネット向けの既存の masquerade ルールがこの経路も拾っていないか確認してください。そのうえで拒否を試します。プール B に閉じておくべきサービスは、プール A から到達できないままでなければならず、同じゲートウェイ内のクライアント同士も試す必要があります。今変更した転送ポリシーは彼らにもかかるからです。

メンタルモデルにもう一つ修正を。トランジットリンクは復号済みの内部パケットを運ぶので、私的な保証ではなく信頼境界です。管理していないネットワークを越えるなら、その中継点を承認済みの暗号化トンネルで保護し、その実際のトポロジーでルーティングします。エンドツーエンドの TLS はあくまでアプリケーション自身を認証します。だからこそ内部サービスはそれを使い続けるべきです。

関連記事