管理ページはモバイル回線のスマートフォンからは応答し、VPNに接続したノートパソコンからはタイムアウトする。トンネル自体は壊れていない、要求がトンネルに入らないからだ。ホスト名はゲートウェイの公開アドレスに解決され、そのアドレスは自身が終端するトンネルの外からしか到達できない。修正とは、外側の端点を動かすことではなく、サービスに内側の入口を与えることだ。

ページはトンネル経由ではなく外から読み込まれる

比較しているのは二つの異なる要求だ。モバイル回線ではブラウザーが公開アドレスへ直接到達する。トンネルの内側では同じ名前が同じ公開アドレスに解決され、パケットはトンネル自身の遠い側にあるアドレスへ届くためにトンネルから出る必要がある。同じ名前、同じアドレス、まったく異なる二つの位置。

宛先ルートが選ぶのはアドレスであり、ポートではない。ルーティングテーブルはポート443が管理用でポート1194が転送用だとは知らないため、どちらも同じ宛先として扱われる。

だからページは動くことも動かないこともある、クライアントがどの名前とどのネットワークを使ったかによる。これは認証の問題ではなく経路の問題だ。どちらのクライアントも認証は問題なく通っている。

外側の端点を到達可能に保つ

フルトンネルのプロファイルにはVPN端点への例外ルートが必要で、OpenVPNのredirect-gatewayオプションは、遠隔の端点が既存のゲートウェイ経由のルートを保ち、トンネルが自分自身へルーティングしないことを、まさに記述している。その例外を外すと暗号化された転送がトンネルをたどり、セッションはループの中で終わる。端点のアドレスがトンネルの内側から到達できるのは、この例外のおかげだ。

その結果、公開アドレスはクライアントから到達可能なままで、そのアドレスへのブラウザーの要求も同じ例外をたどってトンネルに入らずに出てしまう。例外は仕事をしており、害をなしているのは名前のほうだ。

何かを変える前に、インターフェースの一覧ではなくルートの判断を読む。以下のコマンドが出力するのは一覧ではなく判断だ。失敗したクライアントから実行する。

ip -4 route get 203.0.113.77
ip -4 rule show
ip -4 route show table all

検索結果が物理ゲートウェイ経由の端点ルートを示すなら、動作は正しく、管理ページが単に誤った経路へ送られているだけだ。ここが名前解決を修正に変える地点だ。

サービスに非公開の名前を与える

サービスには、トンネルの内側でゲートウェイのアドレスに解決される名前が必要で、同時に、アプリケーションが期待する証明書とホスト名の意味を保つ。管理用の名前には非公開ゾーンのレコードが通常の答えだ、転送用の名前はそのままにしておく。

転送用の名前を内側のアドレスに向け直してはいけない、次のトンネル確立が、トンネルの内側にしか存在しないアドレスへ届こうとするからだ。その誤りが生むのは一度だけ動くVPNだ。

アドレスファミリーの両方も確認する、残されたAAAAレコードは、非公開の計画が決して届かなかった場所へブラウザーを送りかねないからだ。

dig +short admin.hq.internal
dig +short -t AAAA admin.hq.internal
curl -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' https://admin.hq.internal/

ポリシーをプロセスの動く場所に合わせる

ゲートウェイで待ち受けるネイティブプロセスへはローカル入力の経路で届くため、入力チェーンとソケットのバインドが要求を受け入れるかを決める。転送ルールとIP転送はローカルのリスナーに応答させない。入力ポリシーでトンネルの送信元を許可し、プロセスがトンネルから到達できるアドレスにバインドされていることを確認する。

ホストのポートに公開されたコンテナーは別の経路をたどる、パケットが宛先変換されて転送されるからだ。その要求はホストの転送ポリシーとDocker自身のルールに当たり、だからこそ動くネイティブのリスナーが同じポートを拒むコンテナーと共存できる。

どちらの場合も起動順序が重要だ、トンネルが上がってからしか現れないアドレスへのバインドは、起動時に静かに失敗するからだ。

非公開のアクセスと拒否される公開経路を確認する

アプリケーション自身の本人確認を保ったまま、許可されたクライアントから新しい名前付き要求を行う。そのうえでトンネルの外から同じサービスを試し、応答しないことを確認する。非公開の名前は制限ではなく利便であり、公開アドレスにバインドされたままのリスナーは依然として露出している。最後のこの確認こそ、多くのレビューが飛ばすものだ。

利用者に渡したホスト名ではなく、バインドとフィルターの状態を読む。露出は名前ではなくソケットの性質だ。この区別はどんなDNS変更でも生き残る。

ss -lntp | grep ':443'
nft list ruleset | grep -n 443
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://203.0.113.77/

内側の要求がページを返し外側の要求がタイムアウトするなら、サービスには意図した非公開の経路が一つあり、転送は到達可能なまま保たれる。外側の要求がまだ応答するなら、誰かがサービスを内部専用と表現する前に、リスナーか公開されたコンテナーの項目を絞り込む必要がある。

関連記事