ITの窓辺から

三流IT技術者の日常

プロキシサーバ経由の宛先とHTTP Hostヘッダ

今回の記事はいつにも増して基本的な話になっています。あまりに基本すぎて拍子抜けするかもしれませんが・・・、HTTPにあまり馴染みがない人だとなめらかに説明できないのではないでしょうか。

宛先URIと名前解決

クライアントからWebサーバへのHTTP通信パケットをキャプチャすると以下のような内容になります。

f:id:ReaLiZeZNSG:20180808000103p:plain

PCからこのブログにアクセスした時のものです。
HTTPメソッドとしてGETが使用されており、宛先Webサーバのルートディレクトリのindex.htmlを要求しています。宛先WebサーバはDNSによる名前解決が行われて宛先が明確になっているためGETで指定するURIは相対URIで指定することになります。

一方、以下の画像はプロキシサーバ経由でWebサーバにアクセスした時のキャプチャです。

f:id:ReaLiZeZNSG:20180808001636p:plain

先ほどと異なりGETメソッドの宛先URI絶対パスになっています。
プロキシサーバを経由する場合、クライアントはWebサーバへのアクセスをプロキシサーバに代行してもらう形になるため、宛先URIを完全に指定してあげないとプロキシサーバはどのWebサーバにアクセスして良いのかわかりません。すなわち、渡されたURIに対する名前解決はクライアントではなくプロキシサーバにより行われます。テストに出るので覚えておきましょう。

Hostヘッダ

HostヘッダはHTTP1.1において唯一必須のHTTPヘッダです。宛先Webサーバ名を格納します。先程の画像を見ると、直接アクセスとプロキシ経由のアクセス両方でHostヘッダに同じ値が格納されています。先程プロキシサーバ経由の場合、プロキシサーバは宛先Webサーバがどこかわからないので絶対URIで指定すると書きました。

しかしHostヘッダにWebサーバの名前が書いてあります。ここに宛先書いてあるじゃん、と思うかもしれませんが、残念ながらHostヘッダに書かれている内容は名前解決には利用できません。名前解決に使えるのはあくまでHTTPリクエストラインに書かれている宛先URIのサーバ名だけです。

ちなみにHostヘッダの用途は、HTTPリクエストを受け取ったWebサーバに宛先FQDNを知らせることです。Apache等のWebサーバにはバーチャルホストという機能があり、この機能を使うと同一Webサーバ内に複数のURIを持たせることができます。バーチャルホストを使用している場合に、そのHTTPリクエストがどのバーチャルホスト宛に来たのかWebサーバはHostヘッダの値を参照して把握します。

次のHTTPに関する記事ではHTTPS通信の復号化について触れていこうと思います。恐らく最近トレンドになっている機能だと思います。色々準備が必要なので週明けとかになりそうですかね。

HTTPSとCONNECTメソッド

以下の画像はSquidの通信をTcpdumpしたものです。

f:id:ReaLiZeZNSG:20180806223048p:plain

宛先はAppleのどこかのページです。AppleのサイトはHTTPSによるアクセスになっており、TLSで暗号化されています。そのため、宛先URIはサーバ名までしか表示されないですし、この後はTLSのやり取りが始まり、問題なければ暗号化されたHTTPのアプリケーションデータの通信が開始されます。

CONNECTメソッド

クライアントがプロキシ経由でHTTPS通信をする場合、HTTP的には通常CONNECTメソッドを使用します。CONNECTメソッドはHTTP1.1で実装されたメソッドです。CONNECTメソッドを使うとHTTP以外のプロトコルをトンネルするように指示を出すことができます。この場合はプロキシサーバに対してTLS通信をトンネルするように指示を出すことになります。簡単書くと以下の順序で処理が行われる、はずです。

  1. CONNECTメソッドを受け取ったプロキシは宛先URIとHTTPコネクションを張ります。ここでいうコネクションはL4のTCP/UDPとは関係ない言葉です。コネクションが確立できれば当然ながらサーバからはHTTP200の応答があります。これでHTTP以外のプロトコルがトンネルされる準備が整いました。
  2. クライアントからのTLS通信がサーバと行われ、TLSセッションが張られます。
  3. HTTPS (HTTP over TLS)による通信が行われ、アプリケーションデータの転送が行われます。

HTTPS通信をする時はCONNECTメソッドを使う」という表現はいたるところで見ますが実態はこんな感じです。間違っていないのですが、ちゃんと知っておくとプロキシ構成、Webサーバの構成を正しく理解できます。

プロキシ経由のHTTPSの実態はCONNECTメソッドで確立されたHTTPコネクション上を流れるTLS上を流れるHTTPというわけです。HTTPSによる暗号化は最後のHTTPに対してかかります。

4階層モデル

OSI7階層モデルという言葉があります。色々なところで聞く言葉ですが、インターネット(≒HTTP)はOSI7階層モデルに従っていません。4階層で構成されています。といっても元はOSI7階層を集約したものですが。インターネットでは最上位層をアプリケーション層と定義しており、このアプリケーション層にはOSI7階層モデルのL5〜L7が集約されています。つまりTLSによる暗号化とHTTPが一纏めになっており、4階層モデルに照らし合わせるとHTTPSは一つのレイヤのプロトコルと見ることができます。もちろん実際はHTTP over TLSなのですが。

CONNECTメソッドとX-Forwarded-For

HTTPSを使うとHTTPリクエストやHTTPレスポンスが暗号化されます。暗号化されると当然ながらプロキシ等の中継サーバではこれらの中身を盗聴、改ざんは困難になります。念の為ですが、HTTPリクエストにはHTTPリクエストライン、HTTPリクエストヘッダ、HTTPリクエストボディが含まれます。

X-Forwarded-ForはHTTPリクエストヘッダに入ります。HTTPS通信の場合、暗号化されているのでHTTPSリクエストヘッダの改ざんができません(プロキシによるX-Forwarded-Forの挿入はある種の改ざんです)。つまりHTTPSの場合X-Forwarded-Forが使用できないというのは、この意味では間違っていません。

ところが以下の画像を見てみてください。(この記事の冒頭の画像と同じです。)

f:id:ReaLiZeZNSG:20180806231740p:plain

HTTPS通信にも関わらず、X-Forwarded-Forが含まれています。何なら他のヘッダも含まれています。これは何故でしょうか。もうおわかりでしょう。プロキシ経由でのHTTPS通信をするためにCONNECTメソッドを使用しています。HTTPリクエストラインのCONNECTメソッドとそのHTTPリクエストヘッダ自体は暗号化されないため、このように任意のHTTPリクエストヘッダを挿入することができます。このようにHTTPSだからといって必ずしもX-Forwarded-Forが使用できないわけではないのです。

 HTTPSでX-Forwarded-Forは使えない?

そこら中にHTTPSだからX-Forwarded-Forは使用できないと書いてあるのは何故でしょうか。この記事の中でも使用できない例を一つ書きましたが、恐らく多くの記事ではプロキシではなく、Webサーバの手前のリバースプロキシでX-Forwarded-Forを使おうとしているのだと思います。この場合、リバースプロキシでHTTPS通信にX-Forwarded-Forを挿入しようとすると、当然ながら暗号化されているため挿入できません。

しかし前回記事に書いたとおり、クライアントからのHTTPS通信をリバースプロキシで終端することでHTTPSの復号化が可能です。いわゆるSSLオフロード、SSLアクセラレーションと呼ばれる機能です。クライアントのTLS通信の終端先がWebサーバではなくリバースプロキシになる構成です。リバースプロキシで復号化が行われるため、リバースプロキシからWebサーバ向けのHTTP通信にはX-Forwarded-Forを挿入することができます。全てのリバースプロキシでこの機能が使えるわけではないので、環境によって各記事の表記が異なっているのが実態なのでしょう。

realizeznsg.hatenablog.com

 

まだ数回SquidやHTTPに関する記事を続けようと思います。