少し前に試行錯誤して現在はひとまず解決したのですが、同じようにはてなで悩んでた人がいるみたいなので、自分の設定例を軽くまとめてみる。
SSLアクセラレータ配下にあるapache上でクライアントからのアクセスがhttpsかhttpであるかの判別をする方法はありますか?
mod_rewriteを利用しhttpのアクセスをhttpsにリダイレクトする設定を考えていますが、SSLアクセラレータを経由してのアクセスとなるため、apacheへの接続は全てhttpとなります。
回答にもあるように、恐らくapache単独では解決できませんが、SSLアクセラレータの設定と組み合わせれば可能です。それも拡張ヘッダやmod_rewriteなどの特別な仕組みは使用せずに。ただし、ここで書いてるのはSSLアクセラレータ付きのロードバランサ(以下、LB)を使用してる例なので、他の全てのSSLアクセラレータには当てはまらないかもしれません。でも似たようなことはできるかと。
あと、記事を書きながらhttps時のリダイレクト(自己参照URL)についても思い出して、それについても考慮して書きました。単にportを判別するだけならもっと簡単で良さげな気もしますが、長々と書いちゃっています。
設定
とりあえず先に結論を。
LB,SSLアクセラレータの設定
実サーバにhttp/httpsのリクエストを送信する際に、転送先のポート番号をそれぞれ個別に設定する。例えば
言い換えると、http/httpsともに実サーバの80番にしか送れない場合は判別できません。たぶん大抵の場合は大丈夫だと思います。
### 2016 追記
L7まで見れる最近のロードバランサ(ADC)の場合、http headerをいじれるので
その場合はhttp/httpsともに実サーバが80番でも大丈夫ですね。
port:80のvirtualserverの場合はheaderをいじらず実サーバに渡す。
port:443のvirtualserverの場合はこのエントリ末尾に書いているように、"X_HTTPS: ON"のようなheaderをinsertする。
アプリケーション側でX_HTTPS headerがあるかどうかで判別する。
### 追記終わり
実サーバのapacheの設定
NameVirtualHostでポート番号を変えて、http用とhttps用の2つの設定を作ります。
後で詳しく書きますが自己参照URLとServerNameの設定が大事。apacheのServerNameのドキュメントにもこう書いています。
リバースプロキシやロードバランサやSSL負荷軽減装置のような、 SSLを処理するマシンの後ろでサーバを動かす場合は、 サーバが正しい自己参照 URLを確実に生成するように、 https:// スキームとクライアントが接続するポート番号を、 ServerName ディレクティブに指定してください。
Listen 80 Listen 8443 NameVirtualHost *:80 NameVirtualHost *:8443 # http用の設定 <VirtualHost *:80> ServerName www.hoge ## その他色々 </VirtualHost> # https用の設定 <VirtualHost *:8443> ServerName https://www.hoge ## その他色々 </VirtualHost>
schemeとport番号を明示的に書くならこう。
# http用の設定 <VirtualHost *:80> ServerName http://www.hoge:80 ## その他色々 </VirtualHost> # https用の設定 <VirtualHost *:8443> ServerName https://www.hoge:443 ## その他色々 </VirtualHost>
判別方法
こう設定しておけば、httpとhttpsで環境変数SERVER_PORTの値が変わります。cgi,phpなどで適当に出力すればわかります。
- http://www.hoge/ => 80
- https://www.hoge/ => 443
ここでhttpsの環境変数が8443ではなくて、443になっているのがポイントです。
しているので、8443になりそうですが。。。
以下、それの補足です。
ServerNameと自己参照URL
これを理解するまで色々試行錯誤しました。詳細はapacheの以下ドキュメントを参照。
- ServerName
- UseCanonicalName
- UseCanonicalPhysicalPort
- mod_dir
なぜhttps用のVirtualHostのServerNameにhttps://のschemaを指定しているのか。
話がややこしくならないように、ここでは以下のapacheのデフォルト設定が入ってるとします。
UseCanonicalName Off UseCanonicalPhysicalPort Off
そして、ServerNameに他の設定をした例を紹介します。
ServerNameにschemaを指定せず、かつポート番号を書かない場合
通常のVirtualHostのように設定すると、以下のようになると思います。
# https <VirtualHost *:8443> ServerName www.hoge ## 残りhttps用の設定 </VirtualHost>
このとき、clientからhttps://www.hoge/にアクセスされた場合の、apacheの自己参照URLはhttp://www.hoge/になっていると思われます。すると以下の問題が起こります。
- 環境変数で取得できるSERVER_PORTが80番になる
- 自己参照URLがhttp://のため、"/"なしのdirectoryにアクセスした場合などのリダイレクトがhttpになる
1番はそのままですね。本エントリの条件を満たしてないので問題外。2番は例を見てもらったほうがわかりやすいと思います。
"/"なしのディレクトリにアクセスした場合、
apacheデフォルトではmod_dirのDirectorySlashが働いて"/"が補完されます。このときにclientに返されるURLは、自己参照URLに/が補完された形です。こんな感じです。
- はてなグループで実験
※ はてなグループがSSLアクセラレータを使っているかは知りませんが、httpにリダイレクトするという例で紹介します。
- https://fragments.g.hatena.ne.jp/hogem https://のスラッシュなしにアクセスすると
- http://fragments.g.hatena.ne.jp/hogem/ http://にリダイレクト
opensslで確認するとよくわかります。
$ openssl s_client -connect fragments.g.hatena.ne.jp:443 -state (中略) HEAD /hogem HTTP/1.0 host: fragments.g.hatena.ne.jp HTTP/1.1 302 Found Date: Fri, 14 Nov 2008 12:22:30 GMT Server: Apache/2.2.3 (CentOS) Location: http://fragments.g.hatena.ne.jp/hogem/ X-Framework: Hatena/2.0 Content-Type: text/html; charset=iso-8859-1 Vary: Accept-Encoding Connection: close SSL3 alert read:warning:close notify closed SSL3 alert write:warning:close notify
302でhttpのURLが返ってきています。
ServerNameに8443のポート番号を明示的に指定する場合
最初はこの設定にしていました。この設定だとclientがhttpsに来た場合にSERVER_PORTを8443で取れるから。ただ、ServerNameの説明を読み直して、"これはひどい"状態になっていることが確認できたので慌てて元に戻しました。
同じようにclientからhttps://www.hoge/にアクセスされた場合を考えます。この設定だとapacheの自己参照URLがhttp://www.hoge:8443 になります。そのため
- 環境変数で取得できるSERVER_PORTが8443になってhttpとhttpsは判別できる、、、が!
- リダイレクトするとhttp://www.hoge:8443/directory/のようなURLになる
opensslで確認すると302でLocatin: http://www.hoge:8443/directory/ が返ってくるのが確認できます。8443番ポートなんてのは通常FWで閉じるのでアクセスすら出来ません。おまけに内部の仕組みが出てカッコ悪いですね。あわてて修正した苦い記憶が。。。
まとめ
httpかhttpsかを判別する方法。
20090323追記
よくよく考えたらこんな面倒なことしてポート番号を見なくてもいいのではないかということに気が付いた。mod_envで適当な環境変数作るだけでいい?
https用のVirtualHost内で
Setenv X_HTTPS ON
とか
Setenv HTTPS ON
とかを設定して、後はプログラム側で$ENV{X_HTTPS}を判定するだけ。(phpは$_SERVER["HTTPS"] でHTTPSを判別してると思うので後者のほうがいいかも?本当のSSLじゃないので個人的には環境変数 HTTPS を設定するのは気持ち悪いですが)
この場合でもやはりhttpとhttps用のvirtualhostをわけることは必須ですね。というか、わけなかった場合、DocumentRootやCustomLogの設定まで全て同じになるので、ポート番号を判別したくなくても個別に設定したほうが良いのか。