同じサーバーIP、二つの経路:OpenVPNの外側フローをマークする
VPNサーバーを除外するホストルートは、そのサーバーへのHTTPSも除外してしまう。代わりに暗号化された封筒をマークし、未マークのトラフィックにトンネルを与えよう。
通常のループ保護はホストルートだ:VPNサーバーの公開アドレス宛のトラフィックをアップリンクから出し、暗号化セッションが自分自身のトンネルに入らないようにする。これは、除外が宛先に紐づいていて宛先が同一であるため、同じサーバー上のWebサービスにもトンネル経由で到達したくなった瞬間までしか機能しない。打開策は、宛先で分類するのをやめ、どのソケットがパケットを送ったかで分類することだ。
分離したい二つのフローから始める
一方のフローは暗号化されたOpenVPNトランスポートそのもので、アップリンクから出る必要があり、そうしなければトンネルが自分自身の外側接続を運んでしまう。もう一方は同じ公開アドレスへの通常のHTTPSリクエストで、トンネルの送信元からサービスに届くようトンネル内に置きたい。宛先は同じで、多くの場合ポート番号も同じだが、必要な経路は異なる。
この解説では、10.90.0.10のクライアントがtun1経由で、サーバーの公開アドレスが198.51.100.25、既定ルートが通常のアップリンクを指す未変更のメインテーブルを前提とする。サーバー側は内側の経路でWebサービスを受け入れ、トンネルの送信元へトラフィックを返す必要もあるが、公開アドレスへパケットを送ってもサーバーのリスナーやファイアウォールポリシーは上書きされない。
何かを変える前に、ホストの現在のルールを記録し、マーク、テーブル番号、三つのルール優先度という誰も使っていない値を選ぶ。ここでの誤りはブラウザのタブを壊すだけでは済まず自分のリモートアクセスを失わせるので、この接続を経由しない非常口を確保しておくこと。
封筒を運ぶソケットをマークする
クライアント設定は暗号化された送信パケットにマークを得て自分でルートをインストールするのをやめ、経路を決めるのは代替ポリシーだけになる:
mark 42
route-noexec
disable-dco
十進の42は十六進の0x2aであり、ルーティングルールが照合するのはこの十六進の形だ。link-optionsのリファレンスは、markがLinux上の暗号化された送信パケットに適用されると説明しており、まさに経路を制御すべきソケットである。データチャネルのオフロードを無効にするとこの例はユーザー空間の経路に留まり、そこで説明されているマークの挙動がそのまま当てはまる。
route-noexecはOpenVPNによるルートの自動追加と削除を抑止する。その代わりに何もインストールしないため、この瞬間からルートの所有者がトンネルに必要なすべてのルートを維持しなければならない。変更後にプライベートな宛先が消えたなら、それは欠けている代替ポリシーであり、マークの欠陥ではない。
この作業中は、198.51.100.25へのホストルートをメインテーブルに残さないこと。そのようなルートは設計が狭めようとしている除外をそのまま復活させ、下のポリシーが参照される前にHTTPSフローで勝ってしまう。
それ以外はすべてトンネルテーブルへ送る
トンネルインターフェースが存在しアドレスを保持したら、ポリシーはこうなる:
ip -4 route add default dev tun1 src 10.90.0.10 table 47100
ip -4 rule add priority 20000 lookup main suppress_prefixlength 0
ip -4 rule add priority 20001 not fwmark 0x2a lookup 47100
ip -4 rule add priority 20002 not fwmark 0x2a unreachable
最初のルールは、承認されたローカルネットワークを含むホスト固有のルートを見えるままにしつつ、メインテーブルの既定ルートをこの検索では見えなくする。この区別は重要だ。既定を抑止することはテーブルを無視することとは違うし、まだ必要なローカル例外が生き残るのはまさにこのおかげだ。
未マークのトラフィックはトンネルテーブルに落ち、トンネルの送信元を取得してVPNから出る。マークされたトラフィックは次の二つのルールのfwmarkテストで失敗し、アップリンクの既定ルートがまだあるリストのさらに下の通常のメイン検索へ進む。最後のunreachableルールが、トンネルルートが欠けたときに未マークのフローが黙ってアップリンクへ戻るのを防ぐ。セレクターは最初から最後までマークであり、宛先アドレスが判断に入ることはない。
そのガードを切断をまたいで維持し、手作業ではなくポリシーの残りと一緒にインストールする。インターフェースとともに消えるルールはポリシーではなく偶然だ。トンネルが落ちたときにポリシーを削除すると障害モードは「ブロック」から「漏洩」に変わるが、古いトンネルルートが後で期限切れになるほうが、間違ったインターフェースから出たトラフィックよりずっと説明しやすい。
戻り経路にも同じマークを見せる
送信マークは仕組みの半分にすぎず、ここがほとんどの再現記事が省く部分だ。暗号化セッションへの応答はアップリンクに到着し、送信元が来た道をそのまま戻るかどうかを問う戻り経路検証の対象になる。助けがなければ、その検索は未マークのトンネルポリシーを適用し、パケットは間違ったインターフェースにあると結論づける。
修正はファイアウォールの所有者の担当だ:送信接続にマークを保存し、一致する受信パケットに復元し、該当する経路で送信元検証付きのマーク処理を有効にする。OpenVPNがソケットマークを設定してもこの三つのどれも行わず、クライアントオプションだけを並べたレシピは不完全だ。設計で最も見えにくく作り直しで最も失われやすい部分なので、その統合は書面で依頼すること。
戻り経路の緩い検査も代替にはならない。緩いモードでも送信元に対する何らかのルートが必要であり、追加したunreachableガードは未マークの検索に対してそれを意図的に外す。この設計に頼る前に接続マークの統合を完了してレビューしてもらい、ホストフィルターは別の問いとして扱うこと(マークはルーティングの信号であり、許可ではない)。統合が欠けていれば、症状はセッションが確立した直後に応答が届き始めたとたん静かになることだ。
二つのルート検索で証明する
カーネルが二つの文脈で何を決めるかを比較し、終了ステータスではなくdevとsrcフィールドを読む:
ip -4 route get 198.51.100.25
ip -4 route get 198.51.100.25 mark 0x2a
最初の検索はトンネルの送信元を持つtun1に、二番目はアップリンクに解決されるはずだ。これは合成検索でありポリシーを証明するが実際のプロセスは証明しないので、動作中のクライアントの実際の転送経路を別途確認し、そのうえで証明書検証を有効にしたまま本物のHTTPSリクエストを行う。
完成と宣言する前に、設計が扱わないものを書き留める。DNS、IPv6、ルールのライフサイクルはそれぞれ独自の判断を要し、これはIPv4ルーティングの例であって完全なプライバシー設定ではない。その価値はセレクターにある:封筒はアップリンクの除外を得て、同じアドレスの通常のサービスはそれを引き継がなくてよい。