Apache 2.4系でのモダンなアクセス制御の書き方

これまでのApache2.2系以前でのアクセス制御の書き方は賛否両論でした。僕はあまり好きじゃありませんでした。

過去のアクセス制御に関しては、以下の記事がとてもわかりやすくまとめられていると思います。

こせきの技術日記 – Apacheのアクセス制御をちゃんと理解する。

ここで、以下のように言及されています。

こんなバッドノウハウ、本当はどうでもいいと思う。Apache 3.0では、かっこいいDSL(VCL)で書けるようにする構想があるらしいのでがんばってほしい。

ということで、2.4系ではDSLとはいかないまでも、Require*というディレクティブを使ったモダンな書き方ができるようになったので、それを2.2系以前のアクセス制御の記述と比較しながら紹介したいと思います。

基本的なアクセス制御

2.2系以前のアクセス制御では、Order、Deny、Allow、Satisfy等を利用して書いてきました。これが非常にやっかいで分かりにくく、結構はまった人も多いんじゃないかと思います。

こせきの技術日記 – Apacheのアクセス制御をちゃんと理解する。に詳しく言及されていますが、例えば、ある特定のIPアドレスからのみアクセスを許可したい場合は、以下のように書く必要があります。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  Order deny,allow
  Deny from all
  Allow from 127.0.0.1
</Location>

[/program]

これを、例えば以下のように書いてしまうと、全てのアクセスを拒否してしまいます。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  Order allow,deny
  Deny from all
  Allow from 127.0.0.1
</Location>

[/program]

非常にわかりにくい!なんだこれは!と個人的には思っています。こういうミスではまった人も多いんじゃないでしょうか。

そこで、Apache2.4系のRequireを使うと、上記を以下のように書く事ができます。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  Require ip 127.0.0.1
</Location>

[/program]

シンプルですね。非常にわかりやすいです。

Require ipのほかにも、Require hostとかけたりします。この辺りはApache2.4のマニュアルを見るといくつかentityについて書かれれているでしょう。基本は以下のような文法です。

[program lang=’apache’ escaped=’true’]

Require [not] entity [value ...]

[/program]

グループ単位でアクセス制御する場合は、以下のように書けます。

[program lang=’apache’ escaped=’true’]

<Location /engineer>
  Require group engineer
</Location>
<Location /customer>
  Require group customer
</Location>

[/program]

リクエストメソッドでも、以下のように簡単にかけます。

[program lang=’apache’ escaped=’true’]

Require method GET POST

[/program]

また、ローカルから(127.0.0.1だったり::1だったり自身のグローバルIPアドレスだったり)を許可する場合は、まるっと以下のようにも書けます。

[program lang=’apache’ escaped=’true’]

Require local

[/program]

正規表現や環境変数を利用したアクセス制御

さらに、環境変数を使ったアクセス制御で、2.2系以前では以下のように書いていました。たとえば、User-Agentが特定のものだけを許可するようなアクセス制御だと、以下のように書きます。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  SetEnvIf User-Agent ^IamAdmin/0¥.1 admin_ok
  Order deny,allow
  Deny from all
  Allow from env=admin_ok
</Location>

[/program]

それを、Apache2.4系のモダンな書き方では以下のように書きます。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  SetEnvIf User-Agent ^IamAdmin/0¥.1 admin_ok
  Require env admin_ok
</Location>

[/program]

グッとシンプルかつ分かりやすい記述になりましたね。さらにこれをスーパーモダンに書くと以下のようになります。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  Require expr "${USER_AGENT} ^IamAdmin/0¥.1"
</Location>

[/program]

さらにシンプルになりました。この、Require exprを使うと、例えば、ある時間帯のみアクセスを許可するような設定も、以下のように簡単かつモダンに書けてしまいます。

[program lang=’apache’ escaped=’true’]

<Location /goldentime_only>
  Require expr "%{TIME_HOUR} -ge 18 && %{TIME_HOUR} -le 23"
</Location>

[/program]

簡単ですね。

複数の制御を組み合わせたアクセス制御

さらにApache2.4系のモダンなアクセス制御では、以下のようなディレクティブを使う事ができます。

  • RequireAll
    • 2.2系以前のSatisfy Allのような意味で全ての条件にマッチするば新
  • RequireAny
    • 2.2系以前のSatisfy Anyのような意味で1つ以上の条件にマッチすれば真
  • RequireNone
    • どれにもマッチしなければ真

上記のディレクティブを使って、複数の条件を組み合わせてアクセス制御をモダンに記述することができます。

例えば、特定のIPレンジから、かつ、特定のグループからのみアクセスできる記述は以下のようになります。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  <RequireAll>
    Require ip 192.168.1
    Require group admin
  </RequireAll>
</Location>

[/program]

さらに、「特定のIPレンジからならアクセスOKだし、特定のIPレンジ以外でも特定のグループに属していればアクセス可能」という記述をしたい場合、2.2系以前ではSatisyfy anyを使って以下のように書く必要がありました。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  Order deny,allow
  Deny from all
  Allow from 192.168.1
  Require group admin
  Satisfy any
</Location>

[/program]

しかし、これを2.4系のモダンなアクセス制御に書き換えると、RequireAnyを使って以下のように書きます。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  <RequireAny>
    Require ip 192.168.1
    Require group admin
  </RequireAny>
</Location>

[/program]

直観的で、少しDSLっぽくなってきたようにも思えます。Orderを使ってはまっていたのと比べると、非常にわかりやすくなったと思います。

少し複雑なアクセス制御を書いてみよう

最後は、少し複雑なアクセス制御を実装してみましょう。

例えば、

「アクセス元のuserがsuperadminだったら無条件で許可、または、IPアドレスが192.168配下で、かつ、adminグループであるか192.168.1以下のIPアドレスっだたら許可。ただし、上記が問題なくても、groupがblacklistに所属している場合はさすがにだめ」

を書きたいとします。これを実際にモダンなアクセス制御の記述で書いてみると以下のようになります。

[program lang=’apache’ escaped=’true’]

<Location /admin>
  <RequireAll>
    <RequireAny>
      # userがsuperadminだったら無条件で許可
      Require user superadmin

      # または、IPアドレスが192.168以下で、かつ、adminグループであるか
      # 192.168.1以下のIPアドレスっだたら許可
      <RequireAll>
        Require ip 192.168
        <RequireAny>
            Require group admin
            Require ip 192.168.1
        </RequireAny>
      </RequireAll>
    </RequireAny>

    # ただし、上記の中でgroupがblacklistに所属している場合は
    # さすがにだめ
    <RequireNone>
      Require group blacklist
    </RequireNone>
  </RequireAll>
</Location>

[/program]

プログラムの経験がある人だと、それなりに後から読んでも理解できそうに思えます。これを2.2系以前の書き方で書くとどうなるか、考えてもいませんが、どうなるか見てみたいものです。

最後に

以上が、2.4系からのモダンなアクセス制御の書き方になります。DSL、とまではいかないかもしれませんが、Require[All,Any,None]ディレクティブの存在やRequire entityの存在によって、かなり可読性があがり、直観的に書けるようになったと、個人的に思っています。

また、今回は混乱を招かないように、IfやElseIf、Else等をあえて紹介しないようにしましたが、それらの条件式を使うとさらに柔軟でDSLのような記述ができるようになりますので、色々試してみて下さい。それらで工夫して書いてみたアクセス制御の記述とか見てみたいです。

皆さんもぜひ、Require*を使ったモダンなアクセス制御で記述して、「あー、セキュリティ的にやっちゃってたー」みたいな事にならないようにして頂ければ、この記事を書いて良かったとうれしくなります。