Displaying posts tagged with

“Apache”

mod_dosdetector-forkをApache 2.0で動かす

今更ながら、mod_dosdetector-forkをApache 2.0で動かすための方法をまとめておく。少しだけソースコードを編集する必要がある。

必要なもの:

一応、自動で編集するスクリプトも作ってみたが、先に手動で編集する場合の手順をざっと説明しておく。

手順1.

mod_dosdetector.c をエディタで開き、/* code for apache 2.0 */ というコメントが書いてある部分に次の1行を書き加える。

#include "apache20.h"

手順2.

Apache 2.2のソースを展開して srclib/apr/shmem/unix/shm.c をエディタで開き、関数apr_shm_removeの定義部分をコピーして、手順1で編集した部分の直後にペーストする。最終的にこんな感じになる。

/* code for apache 2.0 */
#include "apache20.h"
APR_DECLARE(apr_status_t) apr_shm_remove(const char *filename,
                                         apr_pool_t *pool)
{
//..略..
}

以上で必要な編集作業は終わり。ここまでの手順を自動化するスクリプトがapache20.plという名前で入っているので、これを実行してもいい。

# 展開したApache 2.2のソースディレクトリを指定する
perl apache20.pl path/to/httpd-2.2.x

あとは通常通りのインストール手順でOK。

#apxsが/usr/sbinに入っている場合
#コンパイル
make PATH=/usr/sbin:$PATH
 
#インストール
make PATH=/usr/sbin:$PATH install

動作確認は次の環境で行った。

  • ソースコードのコピー元: Apache 2.2.21
  • 動作: Apache 2.0.64 on CentOS 5
  • 動作: Apache 2.0.52 on CentOS 4

今後どの程度メンテナンスを行っていくかは不明なので、そこは予めご了承いただきたい。あと2.2側のコード変更によっては自動化スクリプトは動かなくなる可能性があるので、そのときは適宜手動で対応のこと。

nginxでメンテナンス中画面を表示する

どのURLにアクセスされてもステータスコード503を返して /home/www/maintenance.html を表示する設定。

server {
    server_name  your.hostname; 
    error_page 503 /maintenance.html; 
    location / { 
        return 503;
    }
    location = /maintenance.html {
        root /home/www;
    }
}

同等の設定をApache(>=2.2)で書くと次のようになる。

<VirtualHost *:80>
    ServerName your.hostname
    DocumentRoot /home/www
 
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !=/maintenance.html
    RewriteRule .* - [R=503]
 
    ErrorDocument 503 /maintenance.html
</VirtualHost>

CakePHPで特定のコントローラに対するBasic認証をApache側でかけようとしたが無理だった

2010-11-17: 記事のタイトル・内容を修正しました。最終的に、Cakeの特定のコントローラにApache側で認証をかけることは不可能という結論に至りましたので、「なぜ不可能なのか」を説明する内容に差し替えました。コメント欄にて重要な指摘をくださったshin1x1さんに感謝します。

CakePHP 1.2/1.3

CakePHPで特定のコントローラ(URL)に対して、Apacheの機能を使ってBasic認証をかけようとして、実際にその方法を見つけたとして記事を公開したのですが、無理だということに気がつきました。

Cakeは最終的に $_GET['url'] しか見ていないので、一つでも認証無しでアクセス可能なコントローラ/アクションが存在すれば、

/controller/action?url=/protected

というパラメータを使うことで任意のURL(上では /protected)に対して認証無しでアクセスできてしまいます。

以下に私が使おうとしていた .htaccess のコードを示しておきます。これは同じようなアイデアを思いついた人に対して、このような方法では無理だということを示すためのものなので、決して使用しないでください。

# Basic認証の設定
AuthType Basic
AuthName "Members only"
AuthUserFile /path/to/.htpasswd
Require valid-user
 
# 認証対象となるURLの指定
SetEnvIf Request_URI ".*" allowed
SetEnvIf Request_URI "^/members" !allowed
# !INSERTED! /index.php に直接アクセスするのを許可しない
SetEnvIf Request_URI "^/index\.php" !allowed
 
# 認証をバイパスさせるための設定
Order Deny,Allow
Deny from all
Allow from env=allowed
Satisfy Any
 
# 通常のCakeのrewrite設定
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

なおこの記事は以下のブログ記事を参考にして書きましたが、同様の問題が存在すると考えられますので注意してください。

mod_rewriteを使ってドキュメントルートを分割する

(特にCakePHPに限定される話題ではありませんが、CakePHPの.htaccessを例に説明します)

通常、Webアプリケーションを構成するファイルは全てソースコード管理システムの管理下に置き、何らかのデプロイツールを用いて本番環境に転送します。本番環境でファイルを直接編集することはしません。

しかし時には一部の画像ファイルやCSSファイルを管理から除外して、自由に編集できるようにしたい場合があります。そういう場合は mod_rewrite を活用して、本来のドキュメントルートの他にもう一つ別のディレクトリを実質的なドキュメントルートとして割り当てることで、高い柔軟性を持たせることができます。

前提

CakePHPで構築したアプリケーションが /var/www/cake にあり、ドキュメントルートは /var/www/cake/app/webroot である。CakePHPデフォルトの .htaccess は次の通り。

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

やりたいこと

/home/files/public に置いたファイルに対して、あたかもドキュメントルートに置いたかのようにアクセスさせたい。
例) /home/files/public/css/main.css に対して http://example.com/css/main.css でアクセスできる。

シンボリックリンクの作成とmod_rewriteの設定

まずドキュメントルート内に /home/files/public に対するシンボリックリンクを適当な名前で作成します。当該ディレクトリに対して Options FollowSymlinks の権限が必要です。

ln -s /home/files/public /var/www/cake/app/webroot/__files

次に mod_rewrite の設定を追加します。

RewriteEngine On
RewriteCond %{DOCUMENT_ROOT}/__files/%{REQUEST_URI} -d [OR]
RewriteCond %{DOCUMENT_ROOT}/__files/%{REQUEST_URI} -f
RewriteRule ^(.+)$  __files/$1 [QSA,L]
 
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]

この方法ではシンボリックリンクを使っていますが、mod_rewrite の設定を httpd.conf に書く場合は Alias を使って実現することもできます。

いずれにせよまず /home/files/public の検索が行われ、次に /var/www/cake/app/webroot の検索が行われます。パスの衝突には十分な注意が必要です。あらかじめ管理外とするパスを固定しておけるのであれば、それがベストなのは言うまでもありません。

mod_rewriteとPHP_SELFの関係

mod_rewrite でURLを書き換える場合の PHP_SELF の値は、RewriteRule を .htaccess に書くか httpd.conf に書くかによって違ってくる。http://example.com/foo/bar を http://example.com/index.php に書き換える場合について例示すると以下のようになる。

.htaccessに書く場合

RewriteRule ^foo/bar$ index.php

index.php でアクセスできるPHP_SELFの値は /index.php になる。

httpd.confに書く場合

RewriteRule ^/foo/bar$ /index.php

index.php でアクセスできるPHP_SELFの値は /foo/bar になる。

フレームワークの中には .htaccess で書き換えることを前提としてパス処理を行っているものがあり(CakePHP 1.2など)、httpd.confで書き換えをする場合には何らかの回避策をとる必要がある。

(1) PTオプションを使用する

RewriteRule に passthrough|PT オプションを指定する。

RewriteRule ^/foo/bar$ /index.php [PT]

このオプションを指定した場合、書き換え後のURLに対してAliasやRedirectの適用が行われるようになることに注意(元々そういった用途に使うためのオプション)。

(2) フレームワークレベルで設定する

CakePHP 1.2の場合はindex.php内Dispacherクラスのコンストラクタ第2引数で、PHP_SELFに替わる基準パスを指定できる。

$Dispatcher = new Dispatcher(null, ''); //空文字列でルートを意味する

……

技術的に言うとこれはURL書き換え時に request_rec 構造体の uri メンバを書き換えるか否かの違い。.htaccess でURLを書き換えた場合は書き換え後のURLに対して internal-redirect が発生するらしく、結果として handler の中から書き換え後の uri が見えるようだ(RewriteLogから推測)。

mod_dosdetector 改造一段落

mod_dosdetectorの改造は、先日の共有メモリ処理の改善をもって一段落。今後は実際に運用しながらバグを取りつつ、ドキュメントでも書きつつ、またオリジナルの mod_dosdetector に対するフィードバックも考えていこうと思う。

やり残したこと。

ディレクティブ有効範囲の矛盾の解消

mod_dosdetector はクライアント情報を管理するのに共有メモリを使用している。共有メモリ・セグメントはサーバ中に唯一つしか存在しないので、DoS判定の閾値や継続時間などの設定値も当然サーバ中で唯一になっているべきである。ところが実際には、ディレクティブはどこにでも(.htaccessにでも)何度でも書くことができる。

この矛盾した状況を解消するには、設定は全て httpd.conf のグローバルスコープにしか書けないように変更すれば良い。しかし「設定は一箇所にまとめておいてね!」という紳士規定によって得られる「どこにでも設定を書くことができる」というメリットを捨ててまで、そうする価値があるかと言われると…よく分からない。

Apache 2.0 対応

参考:mod_dosdetector を Apache 2.0 系で動かすパッチ

こちらは技術的には特に難しくない。しかしきちんとした配布物としてまとめようとすると、途端に面倒くさくなる。というのも、2.0 に足りていない一部の機能を 2.2 のソースコードからバックポートしてくる必要があるために、ライセンス/ファイル構成/アーキテクチャ判定の面で一気に複雑になってしまうからだ。機能の本質とは関係のない部分で、そういった複雑さを持ち込むのは、やはり気が進まない。

(この点、mod_fcgid は Apache License に違反しているのではないだろうか…)

これらやり残したことは、いつか良いアイデアが浮かんだら、取り組むつもり。

APR 共有メモリの初期化処理

mod_dosdetector 改造版。共有メモリの初期化処理を改善した。かなり試行錯誤を繰り返すことになったが、自分なりに理解をした上で修正できたと思う。

共有メモリ機構においては、セグメントの「名前」が共有の鍵となる。名前の衝突にはくれぐれも注意しなければならない。

  1. 新たに共有メモリ・セグメントを作成する際、既にその名前が使用されていると、作成に失敗する
  2. 同じ名前を指定して attach すれば、どんなプロセスでも共有に参加できる。異なるソフトウェアに属する異なるプロセスが、異なる意図を持って同じメモリにアクセスすれば、メモリ内容は破壊されプロセスはクラッシュする

名前の衝突を防ぐための一つの工夫として、共有メモリ・セグメントの名前を”捨てて”しまう方法がある。名前を捨てることで、新たに別のプロセスが共有に参加することはできなくなる(既に共有に参加しているプロセスからは変わらずアクセスできる)。また名前を捨てた後なら、新たに同じ名前で共有メモリを作ることができるようになる。

APR では apr_shm_remove を使うことで名前を捨てることができる。上に挙げた2つの問題は、それぞれ次のようにして回避できる:

  1. apr_shm_create を実行する前に、その名前で apr_shm_remove を実行する
  2. 共有メモリの作成と初期化が終わった時点で apr_shm_remove を実行する

(もちろん厳密に言えば remove と create の間で他のプロセスが create する可能性とか、remove を実行する前にプロセスがクラッシュする可能性とかも考えられるわけだが、大部分のトラブルは回避できるはずである)。

mod_dosdetector ではバージョン0.2からこれらの対策が施されたのだが、(多分古いコードの名残で?)remove/create を実行する前に、まず既存の共有メモリ・セグメントに attach を試みるコードが入っていた。これだと偶然に同じ名前のセグメントが存在した場合にクラッシュする危険性がある。

多分もともとは、Apache が不正終了するなどして共有メモリが破棄されなかった → 再起動の際の apr_shm_create が名前の衝突でコケる、という事態に備えてこのような手順を導入したのではないかと思う。しかし対策1と2によって名前衝突の危険性はほとんどなくなったはずなので、この処理はもう役目を終えたのではないか、と考える。

参考:

共有メモリをAPRで使用するには
コンパクトに必要な情報がまとめられている
apr_shm_attach() and APR_EEXIST
apr_shm_remove が追加されるに至った議論

mod_dosdetector 改造継続中

tkyk’s mod_dosdetector-fork at master – GitHub
オリジナルに対して最小限の変更のみ加えたバージョンはcompat-2タグ

まずバグ修正。前回エントリ時点でのmasterブランチおよびcompat-1タグには実にしょぼいバグがあった。「フラグの逆転」という初歩的なミスによって、DoSIgnoreContentType の効果が逆転する状態になっていた……。C言語に不慣れなのがこういう形で露呈するとは。どうやら脳内で ! 演算子を論理 false のチェックだと見なしてしまうらしい。C言語の場合、! はあくまでも「ゼロかどうかを調べる」演算子だと肝に銘じておこう。

その後masterブランチでは、クライアントの管理処理を全部クリティカルセクションに押し込む、という修正を行った。それなりに大きな規模の修正になった。これで共有メモリに対する更新処理は全てロックの中に収まったはず。動作は全く変わっていないはず。「DoS攻撃の検出」と「検出結果に応じた処理」が綺麗に分離されて、コードの見通しはよくなったはず、である。

改造版 mod_dosdetector

mod_dosdetector の改造版をgithubで公開した。元のライセンスに従ってMITライセンスとなっている。

今のところ、最大の違いは次の2点である:

  • 無視するアクセスを環境変数「NoCheckDoS」で指定できるようになった
  • DoSIgnoreContentTypeが指定されていない場合、サブリクエストの生成を行わないようにした

例えば画像/js/cssファイルに対するアクセスを無視するには、次のように SetEnvIf で指定する。DoSIgnoreContentType は指定しなくて良い。

SetEnvIf Request_URI "\.(gif|jpe?g|ico|js|css|png)$" NoCheckDoS

これで各アクセスごとのサブリクエストが行われなくなるので、パフォーマンスも改善されるはず。abを使った単純なテストでは、20%程度は効果があるようだ(並列20、合計10000アクセスで実験)。

Requests per second:    1638.03 [#/sec] (mean)
Time per request:       12.210 [ms] (mean)
Time per request:       0.610 [ms] (mean, across all concurrent requests)
Requests per second:    2058.26 [#/sec] (mean)
Time per request:       9.717 [ms] (mean)
Time per request:       0.486 [ms] (mean, across all concurrent requests)

もし上記の変更点「だけ」が欲しいという方は、compat-1タグ compat-2 タグをチェックアウトしてほしい。今後masterブランチでは(既に取り掛かっているものも含め)

  • 共有メモリ処理と排他処理の整理
  • 設定ディレクティブまわりの整理
  • Apache 2.0系への対応
  • 諸々のコードのクリーンアップ

といった課題にチャレンジしていくつもりである。

mod_dosdetector 継続調査

先日 mod_dosdetector について調べて以来、Apache モジュールについて俄然興味が湧いてきた。もともとは mod_dosdetector の正確な動作を確かめたかっただけなのだが、処理の流れを追うために Apache のソースコードにまで手を出して読みふけっているうちに、夢中になってしまった。普段から馴染み深いソフトウェアだけに、動作の裏側が分かってくるとすごく面白い。勢い、この分野で定本と言われる『The Apache Modules Book』も買ってしまった。

とりあえず mod_dosdetector で気になっているのは

  • 無視するアクセスを Content-Type で指定するのは適当か?Content-Type を取得するにはサブリクエストの生成を行う必要があるので、オーバーヘッドが大きい。URLを対象にした方が良いのではないか?
  • あるいは、無視するアクセスの選別自体は mod_setenvif などに任せて、mod_dosdetecotr 側では環境変数をチェックすれば済むのではないか?
  • 共有メモリに対してロックの外で更新操作を行っている箇所があるが、問題は無いのか?

C言語の勉強を兼ねてこれらの改造にチャレンジしてみよう。