Apacheのアクセス制御をちゃんと理解する。
Apacheの設定で
Order deny,allow
とか
Satisfy any
とか、なんだか意味わからん人のために。僕はずっとわかってなかった。
基本
Apacheのアクセス制御には、
- ホストによる制御 (Order,Allow,Deny)
- ユーザ認証による制御 (Auth*, Require)
の2通りがある。
Satisfyは、2通りあるアクセス制御の両方を満たす必要があるかどうかを決定する。デフォルトはSatisfy all。Satisfy anyなら、どちらか片方満たせばよい。
Order
Order deny,allow
は、全てのホストからのアクセスを許可する。
Order allow,deny
は、全てのホストからのアクセスを拒否する。
Order deny,allow Allow from 127.0.0.1
は、全てのホストからのアクセスを許可してしまう。もしローカルからだけ許可したいなら、
Order deny,allow Deny from all Allow from 127.0.0.1
と書かなければならない。逆に allow,deny なら、
Order allow,deny Allow from 127.0.0.1
でローカルからのアクセスだけを許可できる。調子に乗って
Order allow,deny Deny from all Allow from 127.0.0.1
などとすると、全てのアクセスを拒否してしまう。
どういう意味?
図にするとこういう処理をしている。
擬似コードの方がわかりやすいかもしれない。
<Location /> Order deny,allow </Location>
は、
if (Location.match? /) { access = allow // デフォルト if (Host.match? none) { // deny条件(なし) access = deny if (Host.match? none) { // allow条件(なし) access = allow } } }
という処理をしている。デフォルトの存在に注目。Order deny,allowの場合、まずデフォルトで許可される。次にDeny fromの条件に合うか調べられ、合致したら拒絶に変わる。さらにAllow fromに合致すると許可に戻る。
Order allow,denyの場合は、逆に拒絶がデフォルトになる。
<Location /> Order allow,deny Allow from all Deny from spam.example.com </Location>
は、
if (Location.match? /) { access = deny // デフォルト if (Host.match? all) { // allow条件 access = allow if (Host.match? spam.exaple.com) { // deny条件 access = deny } } }
という処理になり、spam.example.com 以外のホストが許可される。
- Order (allow,)deny,allow
- Order (deny,)allow,deny
のように先頭に省略があると考えるとわかりやすい。よく見かけるのは、
Order deny,allow Deny from all Allow from xxx
というパターン。これは、Allowにマッチしたホストだけを許可しますよという意味。
Order allow,deny Allow from xxx
と同じだが、全てを拒絶する(Deny from all)と明示的に書いた方がわかりやすい。
なお、if文が入れ子になっていると考える必要はない。Order deny,allow は、
if (Location.match? /) { access = allow if (Host.match? none) access = deny if (Host.match? none) access = allow }
という擬似コードの方が適当かもしれない。ただ、自分は入れ子になっている方が理解しやすかった。denyでないなら下の判定は不要だし。
マージ
ここまで挙げた例には <Location /> が1つしか出てこなかった。複数の Location セクションがあるとどうなるか。
マニュアルでは、
でページを割いて解説している。
- Directory以外は、設定ファイルに現れた順番に処理される。
- Directoryは短い方から長い方に並べ替えてから処理される。
- 複数のDirectoryが同じディレクトリに適用される場合は、設定ファイルの順に処理される。
ということなのだが、「処理される」の内容がわからない。
次のスクリプトを書いて、設定を変えながら実験した。ドキュメントルートに index.html と 1 というファイルが置いてある。
#! /bin/sh /opt/local/apache2/bin/apachectl restart echo / curl -s -I http://default.local/ | head -n1 echo /1 curl -s -I http://default.local/1 | head -n1
まず、Locationの中に何も書かなかった場合。
<Location /> Order allow,deny Allow from all Deny from all </Location> <Location /1> </Location>
これは / も /1 も拒絶される。何も書かない場合、デフォルトで上書きされたりはしない。
/ HTTP/1.1 403 Forbidden /1 HTTP/1.1 403 Forbidden
次に Order だけを省略するとどうなるか。
<Location /> Order allow,deny Allow from all Deny from all </Location> <Location /1> Allow from all Deny from all </Location>
これは / は拒絶され、/1 は許可される。
/ HTTP/1.1 403 Forbidden /1 HTTP/1.1 200 OK
/ も /1 も同じように Allow from all,Deny from all していることに注意。/1だけが許可されるのは、デフォルトの Order deny,allow が適用されるため。擬似コードだと、
if (Location.match? /) { access = deny if (Host.match? all) { access = allow if (Host.match? all) { access = deny // 全てのアクセスがまず拒絶される。 } } } if (Location.match? /1) { access = allow // デフォルトの Order deny,allow if (Host.match? all) { access = deny if (Host.match? all) { access = allow // /1はこれが適用される。 } } }
となる。Order,Allow,Deny は3つで1セット(mod_authz_host)で、どれか1つを書くとデフォルトが有効になるようだ。
Location は設定ファイル内の順序に影響を受ける。上の設定の順序だけを入れ替えると結果が変わる。
<Location /1> Allow from all Deny from all </Location> <Location /> Order allow,deny Allow from all Deny from all </Location>
/ HTTP/1.1 403 Forbidden /1 HTTP/1.1 403 Forbidden
これはif文が入れ替わるため。
if (Location.match? /1) { access = allow // Order deny,allow if (Host.match? all) { access = deny if (Host.match? all) { access = allow // /1を許可するが、、 } } } if (Location.match? /) { access = deny if (Host.match? all) { access = allow if (Host.match? all) { access = deny // 結局全部拒否されてしまう。 } } }
Satisfy Any
ユーザ認証と併用するとどうなるか。
<Location /> Order deny,allow Deny from all Allow from 127.0.0.1 AuthType Basic AuthName "Staff Only" AuthUserFile /path/to/pass Require valid-user Satisfy any </Location> <Location /1> Order deny,allow Deny from all </Location>
/ HTTP/1.1 200 OK /1 HTTP/1.1 401 Authorization Required
Satisfy any の効果で、ローカルから認証なしで / にアクセスできる。ここまでは良い。 /1 の方が問題。明示的にアクセスを禁止しているのに、なぜか認証を求められる。認証が通るとアクセスできてしまう。
この挙動は、次のような擬似コードを考えると、理解できる。
// デフォルトは何も制限されない。 access = allow; hostAccess = allow; authAccess = allow; // /のホスト制限。 if (Location.match? /) { hostAccess = allow; if (Host.match? all) { hostAccess = deny; if (Host.match? 127.0.0.1) { hostAccess = allow; } } } // Basic認証。 if (Location.match? /) { authAccess = deny; if (User.match? valid-user) { authAccess = allow; } } // /1のホスト制限。 if (Location.match? /1) { hostAccess = allow; if (Host.match? all) { hostAccess = deny; if (Host.match? none) { hostAccess = allow; } } } // 最後にSatisfy anyの判定を行う。 if (Location.match? /) { access = deny; if (hostAccess || authAccess) { access = allow; } }
/1 は Location / に合致するので、Basic認証が適用され、さらに Satisfy any も適用される。
というわけで、/1 には Satisfy all を書くのが正解。
<Location /> Order deny,allow Deny from all Allow from 127.0.0.1 AuthType Basic AuthName "Staff Only" AuthUserFile /path/to/pass Require valid-user Satisfy any </Location> <Location /1> Order deny,allow Deny from all Satisfy all </Location>
これで、ホストの条件が必須になり、/1 へのアクセスが全て拒否される。
/ HTTP/1.1 200 OK /1 HTTP/1.1 403 Forbidden
Directory
力尽きました。短いのから長いのの順に並べ替えてから処理されるとのこと。
念のため書くと上のような例では Location ではなく Directory/Files を使うべき。
まとめ
こんなバッドノウハウ、本当はどうでもいいと思う。Apache 3.0では、かっこいいDSL(VCL)で書けるようにする構想があるらしいのでがんばってほしい。
- 【レポート】高速化プログラミングの参照実装としても活用される「Varnish」 (1) OSの機能をフル活用してHTTPサーバの動きを高速化するHTTPアクセラレータ | エンタープライズ | マイナビニュース
(2007年だけど)
可読性と複雑さの両立には DSL が向いている。設定の中に制御構造が含まれるような場合は特に DSL がよい。自分自身を説明しないボキャブラリは独善で邪悪です。