SSLアクセラレータ配下のapacheで、アクセスがhttpかhttpsかを判別する方法

少し前に試行錯誤して現在はひとまず解決したのですが、同じようにはてなで悩んでた人がいるみたいなので、自分の設定例を軽くまとめてみる。

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などで適当に出力すればわかります。

ここでhttps環境変数が8443ではなくて、443になっているのがポイントです。

  1. clientからのhttpsのアクセスはLB,SSLアクセラレータを介して実サーバの8443に転送
  2. 実サーバのapacheはVirtualHostの8443番で待ち受けて応答

しているので、8443になりそうですが。。。
以下、それの補足です。

ServerNameと自己参照URL

これを理解するまで色々試行錯誤しました。詳細はapacheの以下ドキュメントを参照。

なぜ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/になっていると思われます。すると以下の問題が起こります。

  1. 環境変数で取得できるSERVER_PORTが80番になる
  2. 自己参照URLがhttp://のため、"/"なしのdirectoryにアクセスした場合などのリダイレクトがhttpになる

1番はそのままですね。本エントリの条件を満たしてないので問題外。2番は例を見てもらったほうがわかりやすいと思います。

"/"なしのディレクトリにアクセスした場合、

apacheデフォルトではmod_dirのDirectorySlashが働いて"/"が補完されます。このときにclientに返されるURLは、自己参照URLに/が補完された形です。こんな感じです。

はてなグループSSLアクセラレータを使っているかは知りませんが、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 になります。そのため

  1. 環境変数で取得できるSERVER_PORTが8443になってhttpとhttpsは判別できる、、、が!
  2. リダイレクトするとhttp://www.hoge:8443/directory/のようなURLになる

opensslで確認すると302でLocatin: http://www.hoge:8443/directory/ が返ってくるのが確認できます。8443番ポートなんてのは通常FWで閉じるのでアクセスすら出来ません。おまけに内部の仕組みが出てカッコ悪いですね。あわてて修正した苦い記憶が。。。

まとめ

httpかhttpsかを判別する方法。

  1. SSLアクセラレータ: httpとhttpsのポート番号を変える
  2. apache: httpとhttpsでポート番号を変えたNameVirtualHostを設定する
  3. httpsのVirtualHostのServerNameはhttps://のschemeを明示的に指定する

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の設定まで全て同じになるので、ポート番号を判別したくなくても個別に設定したほうが良いのか。

*1:実サーバにhttps用のバーチャルホストを設定しますが、プロトコルはhttpなので443は使わず8443にしています。サーバでmod_sslなどを使ってないなら443でも問題ないです

debianのbusybox - static link版

fedorayumでinstallしたbusyboxはlibraryをstatic linkしてたのに、debianはshared library使ってるのねー。

# apt-get install busybox
# ldd `which busybox`
        linux-gate.so.1 =>  (0xffffe000)
        libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0xb7ed2000)
        libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7ead000)
        libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7d7b000)
        /lib/ld-linux.so.2 (0xb7f0f000)

static版ねーのかなと探したら見つかった。/lib壊れたときのこととか考えたらstaticのほうがいいかなと思って。

# apt-cache search busybox
busybox - Tiny utilities for small and embedded systems
busybox-static - Standalone rescue shell with tons of builtin utilities
mindi-busybox - Collection of shell utilities in a single executable for Mindi/Mondo
# apt-get install busybox-static
# ldd `which busybox`
        not a dynamic executable

mod_proxyでhot-standby

active/standbyって言ったりもするかも。日本語のドキュメントにはまだ翻訳されてないんですね。

status=+Hでhot-standbyになる。

ProxyPass / balancer://hotcluster/
<Proxy balancer://hotcluster>
  BalancerMember http://1.2.3.4:8009 loadfactor=1
  BalancerMember http://1.2.3.5:8009 loadfactor=2
  # The below is the hot standby
  BalancerMember http://1.2.3.6:8009 status=+H
  ProxySet lbmethod=bytraffic
</Proxy>
  • statusパラメータの説明

Single letter value defining the initial status of this worker: 'D' is disabled, 'S' is stopped, 'I' is ignore-errors, 'H' is hot-standby and 'E' is in an error state. Status can be set (which is the default) by prepending with '+' or cleared by prepending with '-'. Thus, a setting of 'S-E' sets this worker to Stopped and clears the in-error flag.

ループバックデバイスって何さ

CDのisoイメージをmountするときに-o loopって慣習的にしてたけど、なんなの?

どうやら任意のファイルをブロックデバイスのように扱う機能、のことらしい。なるほど。

実例

この人のやってること見てイメージできた。

  1. /dev/zeroから空のファイル作成
  2. mkfsでext用のファイルシステムとして作り直す
  3. -o loopでブロックデバイスのようにマウント

試しにloopオプションをつけないで、普通のファイルをマウントしたらブロックデバイスじゃないと怒られた。

# mount -t ext3  ./hoge.img /mnt/
mount: ./hoge.img is not a block device (maybe try `-o loop'?)
# mount -t ext3 -o loop ./hoge.img /mnt/

grubのstage1とstage2

stage1と2って何だ。と昔から疑問に思ってて放置してたのでぐぐった。

GNU GRUB はその高機能さゆえにハードディスクのMBR領域内(512byte)には収まりません。そこで MBR 領域に stage1 というファイルを、OSのファイルシステム上にstage2というファイルを置き、stage1 から stage2 を呼び出す(stage1.5 を経由している?)ことで対処しています。

へぇ。

apacheのServerAliasに間違ってポート番号付加してしまう

ホストの別名だからServerAliasにはポート番号つけてはいけない。とわかっていても、たまに間違えてポート番号までつけてしまう。当然、目的のVirtualHostにマッチしなくてあわわわわ。半年に1回ぐらいやってしまうな。

追記

ServerNameにポート番号はつけないほうが良いのかな。後でSSLアクセラレータのことと一緒にまとめる。

SSLアクセラレータ使用時でもIEのSSL不具合対策用の設定は入れておいたほうがいいかも

参考

古いIESSL時に不具合があるらしくて、apacheのmod_sslでもdefaultのssl.confにこんなの書いてる。

BrowserMatch ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0

なので、mod_ssl使用時は注意してたんだけど。。。

SSLアクセラレータ使用時

こないだ問題が起きたのはapache(mod_sslなし) + SSLアクセラレータでの状況。mod_ssl使用してないから油断してたんだけど、IEhttpsでのみレスポンスが遅い現象*1が起きた。
まさかと思って試しにKeepAlive Offにすると現象は解消。。。というわけでhttps用のバーチャルホスト*2に次のを書いとくことにした。

BrowserMatch ".*MSIE.*" \
nokeepalive downgrade-1.0 force-response-1.0

これでひとまず解決。

*1:特定のページで発生したのでアプリ固有の問題の可能性も大いにあるけど、アプリ担当者が根を上げたのでapacheで解決しました

*2:SSLアクセラレータ使用時は80番以外の適当なポートを使って別のバーチャルホスト設定にしてる