はじめに
ここ最近、とあるシステムを(仕事として)構築中ですが、システム的に使用する特定のインターネット上のサイトに送信元 IP 制限をかけているため、URL ベースで特定のプロキシを経由する仕組みを作ろうとしています。
基本的な構成
フォワードプロキシで実現します(プロトコルは HTTP (S) のみを前提としています)。プロキシサーバーは送信元ネットワークを信頼できるものに限定します。また、プロキシに誘導する URL を動的にコントロールしたいため、PAC ファイルを Web サーバーで配信し、利用する PC に設定してもらいます。これは一般的なものであり、よく見られる構成です。
これは、Apache でも Squid でも問題なく実装できます。
プロキシ認証の検討
前述の内容で一定の要件は満たすことになりますが、セキュリティ強化の観点から以下のようにプロキシ認証を検討しました。
- プロキシサーバーは、特定のネットワークからのみアクセスを許可しているものの、ユーザー認証を実装したい。ユーザーは LDAP に登録しているユーザーを使えると良い。
- ユーザー認証を実装するにあたり、その認証は暗号化した通信としたい。つまり、途中経路でそれが読み取られないようにしたい。
LDAP 認証
まず、LDAP 認証を実装しました。これは Apache でも Squid でも問題なく実装できますが、基本的にBasic 認証であり、BASE64 エンコードされた状態(暗号化ではない)でネットワークを流れます。
認証の暗号化
認証の暗号化には TLS の利用を検討しましたが、まずは、プロキシ通信について改めて理解する必要があります。
Web フォワードプロキシは、L7 における通信の転送であり、代理アクセスとも呼ばれます。つまり、プロキシは、その通信を解釈し、制御が可能となります。HTTP においては以下のように Proxy が HTTP を終端します。
Client --- (HTTP) --- Proxy --- (HTTP) --- Web
次に、HTTPS 通信は以下のようになります。少しわかりづらいですが、HTTPS の特性上、Proxy は HTTPS 内の情報を読み取ることができず、介入することもできません。URL などのヘッダー情報は参照できますが、実際は送信元 IP ヘッダーとポート番号を変えて、宛先の Web に転送しているだけです。
Client --- (HTTPS) --- Proxy --- ((HTTPS(IP Header & Port))) --- Web
次に、プロキシ認証における通信は以下となります。プロキシの特性として、クライアントが HTTP (S) での通信を行う際に、はじめてプロキシにアクセスされることになります。このため、HTTPS の通信を開始する場合、それに伴う通信は HTTPS で暗号化されるように思われるかもしれませんが、プロキシ認証はクライアントとプロキシ間で行われるため、宛先サーバーとの通信とは別の通信となります。
Client --- (HTTP(Auth)) --- Proxy
つまり、今回の目的は、上記のクライアントとプロキシ間の通信の暗号化が目的となります。これを実現するためには、プロキシが HTTPS プロキシとして動作する必要があり、その実装自体は、Apache でも Squid でも可能です。実際に、Curl を使用して挙動を確認することができました。
しかし、ブラウザからの利用では機能しませんでした。tcpdump やプロキシのデバッグなどでかなり調査しましたが、ブラウザを使用する場合は、クライアントが強制的に通信をリセットしているように見えます。
現時点の結論(推論)としては、「ブラウザは、同一セッションにおいて、接続先の HTTPS サーバーとの間に別の HTTPS 通信が存在する場合、中間者攻撃 (MitM) とみなし、強制的に切断しているのではないか?」と推察しています。
以下の URL では、私と同じような問題に直面しているようです。
https://rohhie.net/forward-proxy-this-and-that
おそらくですが、通常のプロキシ認証は暗号化されていない事実を知らない人が多いのではないかと感じています。
(Apache + Squid) + OAuth2 or SAML + (Keycloak + LDAP) ?
前述の課題感から、おそらくフォワードプロキシでは、従来の認証機構における認証通信の暗号化は難しいと思います。このセクションでは、未検証ではありますが、一つの可能性として OAuth2 を利用したプロキシ認証を考察します。
Apache には、OAuth2 のモジュールである mod_auth_openidc と、SAML のモジュールである mod_auth_mellon があります。この内、mod_auth_openidc に関しては、Ubuntu の APT でインストールできます。
以下が参考になりそうです。
OAuth2 が使えれば、プロキシにおける Basic 認証の問題を回避できるかもしれません。これらを踏まえ、現在、フォワードプロキシとして理想としてるのは以下の構成です。
- Apache (mod_proxy, mod_auth_openidc): フロントエンドのプロキシとして使用し、Apache の多機能性を活用した柔軟な認証機能を持たせる。必要に応じてロードバランサーを使用すると良い。
- Squid: バックエンドのプロキシとして使用し、Squid の純粋なプロキシとしての高度なキャッシュ機能などを活用する。必要に応じてロードバランサーを使用すると良い。
- Keycloak: システム全体の IAM とし、Apache の OAuth2 の認証先となる。バックエンドには LDAP を使用する。また、Keycloak からユーザー自身がユーザーの LDAP パスワードを変更できる。コンテナベースなので、Kubernetes 上にデプロイするのが良い。
- LDAP: Keycloak のバックエンドとして動作。389 Directory Server を使用する。必要に応じてマルチマスター構成とし、ロードバランサーを使用すると良い。
この構成での Squid はかなり重厚な構成となるので、小規模サイトでは不要だと思います。
まとめ
- フォワードプロキシにおける Basic 認証の暗号化 (TLS) はブラウザのセキュリティにより難しい。おそらくできない。
- 認証機能における柔軟性を考慮すると、Apache + OAuth2 が選択肢として有力(未検証)。Squid は純粋なプロキシとしての性能は魅力だが、認証機能に難がある。両者の連携で更に強力なプロキシシステムを構築できる可能性もある。
- 認証などの多機能性を求めるなら、Apache プロキシを最初に導入する方が良い。接続元ネットワークを適切に定義できる環境なら高パフォーマンスな Squid を最初に検討するのが良い。
その他
- Nginx はリバースプロキシとしては有名ですが、フォワードプロキシとしての使用は追加のモジュールを必要としたり、実例がほぼないので、このタイトルでの検討からは除外しています。
- Apache も Squid もダイジェスト認証を利用できますが、ダイジェスト認証は一応のパスワード暗号化(強い暗号化ではない)はされますが、LDAP との連携に問題があります。
- Squid には SSL Bump という機能があり、これを使用すると HTTPS 通信を終端します。つまり、TLS を複合化します。これは TLS の本来の目的である秘匿性を失っており、利用の際は組織レベルの理解が必要だと思います。また、システム管理者自身も知りたくない情報を知ることになってしまう可能性もありますね。
- Squid にはフォワードプロキシの構成の一例として、透過型プロキシを実装可能です。透過型プロキシとは、サードパーティの機能(iptables やルーター)を使用して、クライアントの通信を強制的にプロキシを経由させるようにする。プロキシに転送させるための機能やデバイスは、ネットワークのインライン上に存在する必要があり、HTTPS はその性質上、対象外となりますが、前述の SSL Bump と組み合わせることで、HTTPS も透過型プロキシとして利用できるようになります。