Displaying posts tagged with

“mod_dosdetector”

mod_dosdetector改造版バージョン1.0公開

mod_dosdetectorの改造版、mod_dosdetector-forkのバージョン1.0.0を公開します。

mod_dosdetector-fork-1.0.0.tar.gz
GitHubプロジェクトページ

プログラム本体は3ヶ月前とほぼ全く同じ状態ですが、より使いやすくするために以下のような追加・修正を行っています。

  • 詳細な説明を含むREADMEを追加
  • サンプル設定ファイル(dosdetector-sample.conf)を追加
  • Makefileを修正(installターゲットでビルドオプションを指定できるようになった)

READMEはとりあえず日本語で書いてみましたが、いずれ英訳するつもりです。またバージョンが1.0.0という切りの良い数字になっているのは、オリジナル(最新版0.2)との混乱を避けるためであって、特に深い意味はありません。

READMEにも含まれていますが、オリジナルと大きく違うのは以下の3点です。

  • DoSチェックの対象としないアクセスを環境変数で指定できる
  • 不要なサブリクエストの生成処理を取り除くことでパフォーマンスが改善されている
  • 共有メモリ処理が改善されている

それぞれが技術的に何を意味するのかはこのブログのmod_dosdetectorタグを参照してください。

またせっかくなのでオリジナルの配布物に含まれていたspecファイルも全面的に修正しておきました。RPMパッケージを作るにはspecファイルtarballを適切な場所に配置して

rpmbuild -bb mod_dosdetector-fork.spec

を実行してください。以下のファイルを含むRPMパッケージが出来上がります。

/etc/httpd/conf.d/dosdetector.conf
/usr/lib/httpd/modules/mod_dosdetector.so

CentOS 5上でしかテストしていませんが、Apache 2.2が入っているFedora等ならばうまくビルドできるのではないかと思います。

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言語の勉強を兼ねてこれらの改造にチャレンジしてみよう。

mod_dosdetector の使い方

サーバ/インフラを支える技術』で知ったDoS攻撃判定モジュール mod_dosdetector の使い方。mod_dosdetecotr のバージョンは 0.2、環境は Apache 2.2.3 @ CentOS 5。

改造版も公開しています。

インストール

wget http://ncu.dl.sourceforge.net/sourceforge/moddosdetector/mod_dosdetector-0.2.tar.gz
tar xvzf mod_dosdetector-0.2.tar.gz
cd mod_dosdetector-0.2
make
sudo make install

httpd.conf 設定例

# 実際にはこの一行は make install で追加される
LoadModule dosdetector_module modules/mod_dosdetector.so
 
DoSDetection     on
DoSPeriod        5
DoSThreshold     10
DoSHardThreshold 25
DoSBanPeriod     30
DoSTableSize     100
DoSIgnoreContentType  image|javascript|css

DoS判定の肝となる数値は DoSPeriod, DoSThreshold, DoSHardThreshold, DoSBanPeriod の4つで、上の例ではだいたい次のような意味になる:

  1. 同一IPアドレスから5秒間に10回以上のアクセスがあった場合「DoS攻撃の疑いあり」と見なし、その後30秒間のアクセスに対しては環境変数 SuspectDoS をセットする(値は”1″)
  2. さらに5秒間のアクセス回数が25回に達した場合、「激しいDoS攻撃の疑い」と見なし、環境変数 SuspectHardDoS もセットする(値は”1″)
  3. 初めに「DoS攻撃の疑いあり」と判定してから30秒が経過したら、次のアクセスでもう一度判定をやり直す。直近5秒のアクセスが10回を下回っていれば、疑いは晴れる

アクセス数の計測はファイルの種類・有無を問わずに行われるため、デフォルトではページ内の画像やCSSなども計測対象となってしまう。それらを除外したい場合は、DoSIgnoreContentType ディレクティブで除外したいファイルの MIME type を正規表現で指定しておく(ファイル名(URL)ではないので注意)。各種画像ファイル・JavaScript・CSSファイルを除外したい場合は上例のように設定しておけば良い。

ただし多くの環境では拡張子 ico に対する MIME type の指定は行われていないはずなので、そのままだと favicon.ico が text/plain 等と見なされて除外されない(.ico に限らず、未設定の場合は httpd.conf の DefaultType になる)。次のように AddType を追加しておけば image と判定される。

AddType  image/vnd.microsoft.icon  .ico

ちなみにこれがアイコンファイルの正式な MIME type である。

DoS攻撃に対する防御設定

mod_dosdetector が行うのはあくまでも攻撃の”検出”だけなので、防御する方法はまた別に設定する必要がある。多くの場合は mod_rewrite と併用することになるだろう。例えば「激しいDoS攻撃の疑い」なアクセスに対してステータスコード503 + 静的なHTMLを返す場合は次のように設定する(Apache 2.2 only)。

RewriteEngine On
RewriteCond %{ENV:SuspectHardDoS} =1
RewriteRule .*  - [R=503,L]
 
ErrorDocument 503 /server_is_busy.html

しかし現実には、攻撃の判定とその後の防御は、匙加減が難しい。どの程度のアクセスを攻撃と見なすかはサイトによって千差万別だろうし、あまりに頻繁・過剰な反応をしてしまうとユーザが離れてしまう。

そこで個人的には、まず厳し目の設定を行って、ログを取ってみることをおすすめする。「DoS攻撃の疑いあり」なアクセスを普通のログと分けて記録するには次のように設定する。

LogFormat "%{SuspectHardDoS}e %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" dos_suspect
CustomLog logs/dos_suspect_log dos_suspect env=SuspectDoS

ログの行頭に環境変数 SuspectHardDoS が記録されるため、どのような経過をたどって「疑いあり」から「激しい攻撃の疑い」へと移行したかも分かる。

後はこの記録を見つつ、通常のアクセスが引っかからないような適当な設定を探ることになる。もしサーバリソースの都合上、どうしても通常のアクセスが引っかてしまう可能性があるのなら、その時は防御方法をより穏当なものに設定することもできる(このあたりの柔軟性は mod_dosdetector の設計の妙だと思う)。

その他

上で触れなかったディレクティブについて少々:

DoSDetection
DoS判定機能の有効・無効を設定。on で有効
DoSTableSize
クライアントの追跡記録の最大保存数。この数値を大きくすればより多くのクライアントを同時に追跡できるが、メモリ使用量と負荷が上昇する
DoSShmemName
クライアントの追跡記録を保存しておくための共有メモリの名前。普通は設定する必要は無いはず

最後に

素晴らしいソフトウェアをオープンソースとして公開してくださった田中慎司さん(そして株式会社はてな)に感謝!