トンネルを接続し、同じブックマークを開くと、許可リスト上のサイトはオフィスにいたときとまったく同じ応答を返す。これが多くのリモートアクセス計画が暗黙のうちに前提としていることであり、三つの別々のことが同時に成り立つ間だけ通用する。そのどれもVPNクライアントの機能ではなく、どのDNSレコードもそれらを提供できない。自宅でブックマークが失敗すると、ブラウザは原因が経路の欠落であれ、未承認の出口アドレスであれ、報告する送信元を置き換えたプロキシであれ、同じ汎用エラーを一つだけ返す。この三つを一つの問題として扱うことが、小さなポリシー修正を許可リスト拡大の一週間に変えてしまう道である。

実際のオフィスネットワークとの接触に耐える設計は、この前提より狭い:明示的な三行の判断表、迂回できない一つの施行点、そして何かを証明する拒否テストである。前提の解体が先に来るのは、その後のすべてが、どの層が送信元を誤って伝えているかを知ることにかかっているからだ。

神話:接続することは経路を選ぶことではない

トンネルが確立され、クライアントはある範囲のプレフィックスに経路をインストールする。接続が行うのはそれだけである。無関係なHTTPSリクエストがトンネルを通るかどうか、届いたときにどの公開アドレスから来たように見えるかは、後で別の構成要素が決める:経路表が道を選び、出口が送信元を選ぶ。

だから同じノートPC、同じブラウザプロファイル、同じブックマークでも、オフィスと台所のテーブルでは結果が異なる。オフィスでは、サイト運営者が同意した保守された公開アドレスからリクエストが出ていく。フルトンネルではVPNの出口から出ることがあり、それは別のアドレスで、誰かが追加するまでは未承認である。スプリットトンネルではトンネルにまったく入らないこともある。その三つの経路のうち二つだけがサイト所有者との合意に含まれていたもので、三つ目はallowの一行を足して塞ぐべき不具合ではない。

三つのリクエスト状態、二つの承認済み出口

ファイアウォールに触れる前にマトリクスを書き出すこと。表が設計であり、規則はその一つの実装にすぎず、以下のアドレスはドキュメント用の範囲なので、自分の保守する値に置き換えてサイト所有者が監査できる場所に保管すること。

Request pathSource the site seesIntended decision
Office, no tunnelOffice public exitAdmit to the application login
Remote, approved full tunnelApproved VPN egressAdmit to the same login
Remote, ordinary connectionNeither approved addressDeny at the network boundary

中間の行が、こうした計画のほとんどが破綻する場所である。重要なのは施行装置が見るアドレスであり、ノートPCがトンネル内で持つプライベートアドレスでも、そのLANアドレスでもない。10.8.0.6 のような値は公開サイトにとって何の意味もなく、192.168.1.44 も同様である。トンネルのアドレスはトンネルのアドレッシングの詳細であり、サイトが検証できる資格情報ではない。

VPNが安定した文書化された出口を提供できないなら、マトリクスに有効な中間の行はなく、許可リストをどれだけ増やしても作れない。その時点で誠実な選択肢は、明示的なプライベート経路で到達するプライベートサイトか、アプリケーションの手前にあるID認識型のアクセスゲートウェイである。どちらも公開の許可リストとは別の契約であり、どちらも、たまたま動くまで伸びたリストより監査者に説明しやすい。

判断が下される場所で送信元を読む

制限はリクエストを終端する装置に置き、別の起点経路がそれを迂回しないことを確認する。Nginxのアクセスモジュールallowとdenyのディレクティブを順に評価し、最初の一致が勝ち、囲むブロックから継承した規則はlocationの内部でも有効である。狭いallowが同じ場所の広いallowの隣にある場合、通常は広い方が決定する。そのlocationの有効な設定全体を、一行たりとも編集する前に読むこと。

server {
    listen 443 ssl;
    server_name intranet.example.com;

    set_real_ip_from 10.20.0.10;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    location /app/ {
        allow 203.0.113.24;    # office public exit
        allow 198.51.100.77;   # approved VPN egress
        deny  all;
        proxy_pass http://app_backend;
    }
}

ロードバランサーやリバースプロキシの背後では、その規則はそう伝えない限りプロキシを見る。呼び出し元のアドレスは、サポートされた信頼できるプロキシの仕組みによってのみ、プロキシのアドレスを明示した一覧とともに復元すること。承認済みアドレスを主張するリクエストヘッダーは、呼び出し元がそれを所有する証拠ではなく、公開のリスナーでは検証されていない転送ヘッダーが許可リストを飾りに変えてしまう。

次に、もう一方のアドレスファミリーを確認する。IPv4だけを対象にした規則セットは、AAAAでも応答するホスト名にとって不完全な判断であり、IPv6が機能するクライアントは、決してレビューしていない経路で同じアプリケーションに到達する。

スプリットトンネル:決して入らないリクエスト

スプリットトンネルは、リストにあるプレフィックスだけを運ぶ。社内のオフィスサブネットは通常そのリストに入るが、公開ウェブサイトは入らない。ブラウザは名前を解決し、選んだアドレスを経路表と比べ、それでも手前にローカルのデフォルトゲートウェイを見つける。

こうしてリクエストは利用者の自宅回線から届き、拒否され、報告書にはVPNが壊れていると書かれるが、トンネルは設定されたとおりに動いている。正当な修正は二つあり、その宛先をトンネル経由にして承認済みの出口に運ばせるか、サイトへ直接到達することを認めてリモート利用者にアクセスゲートウェイのような別の承認済み経路を与えるかである。自宅のアドレスが偶然含まれるまでサイトの公開許可リストを広げることは、どちらでもなく、設計が施行すべき境界を取り除いてしまう。最初の二つはマトリクスを誠実に保ち、三つ目はそこから静かに一行を削除する。

拒否を証明し、次に回復を証明する

表の各行に立つクライアントから、実際の判断を働かせるリクエストでテストする。無関係なアドレス表示ページへの到達性チェックは、その宛先がアプリケーションとは別の経路を通る可能性があるため、何も証明しない。

curl -sS -o /dev/null -w 'status=%{http_code}
' https://intranet.example.com/app/login
# repeat with the tunnel up, then with it down
tail -f /var/log/nginx/access.log | grep ' /app/'

境界のアクセス記録を、表が予測した内容と比べる。次にトンネルを落として拒否されるべき新しいリクエストを送るが、すでに読み込まれたページやすでに開いている接続は何もテストしない。拒否が現れないなら、施行点はあなたが思う場所にはない。

ネットワーク許可の後にもアプリケーション認証を残すこと(オフィスの出口もVPNの出口もそれぞれ多くの人を代表し、どちらも個人を識別しない)。利用者への指示は、承認済みのトンネルを立ち上げてから再試行する、という短いままでよい。これが、送信元の規則を推測ではなく判断として保ちながら、一つのURLを便利にしておく方法である。

関連記事