人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

nginxでもapacheでも使用可能なDoS的アクセスを検知して任意の制御をするWebサーバ拡張をmrubyで作った

github.com

会社の運用メンバーと色々議論していた中で、「カジュアルにWebサーバへのDoSみたいなアクセスが来た時に検知して制御したいよねー」という話が上がったので、http-dos-detectorというnginxでもapacheでも使えるWebサーバ拡張をmrubyで書きました。

もちろんmrubyなので、mod_mrubyngx_mrubyを使っています。ので、Rubyコードはそのまま同じコードをどちらのミドルウェアでも使えます。mod_mrubyとngx_mrubyを使えば、ちょっとしたWebサーバの拡張は両方で同じ実装に落とし込めるので便利ですね。

使い方

例の如く、GitHubのREADME通りにインストールして下さい。Rubyのコードはmod_mrubyでもngx_mrubyでも有効なので、そのコードを読み込む設定をそれぞれ以下のように記述するだけでよいです。

  • apache + mod_mruby
LoadModule mruby_module modules/mod_mruby.so

<IfModule mod_mruby.c>
  mrubyPostConfigMiddle         /etc/httpd/conf.d/dos_detector/dos_detector_init.rb cache
  mrubyChildInitMiddle          /etc/httpd/conf.d/dos_detector/dos_detector_worker_init.rb cache
  mrubyAccessCheckerMiddle      /etc/httpd/conf.d/dos_detector/dos_detector.rb cache
</IfModule>
  • nginx + ngx_mruby
http {
  mruby_init /path/to/nginx/conf/doc_detector/dos_detector_init.rb cache;
  mruby_init_worker /path/to/nginx/conf/doc_detector/dos_detector_worker_init.rb cache;
  server {
    location /dos_detector {
      mruby_access_checker /path/to/nginx/conf/doc_detector/dos_detector.rb cache;
    }
  }
}

この中で、 dos_detector.rb を好きなようにカスタマイズしてDoS検知と制御の挙動を変更することができます。

  • dos_detector.rb
Server = get_server_class
r = Server::Request.new
cache = Userdata.new.shared_cache
mutex = Userdata.new.shared_mutex

config = {
  # dos counter by key
  :counter_key => r.hostname,

  # judging dos access when the number of counter is between :behind_counter and 0
  :behind_counter => -2000,

  # set behind counter when the number of counter is over :threshold_counter
  # in :threshold_time sec
  :threshold_counter => 10000,
  :threshold_time => 5,

  # expire dos counter and initialize counter even
  # if the number of counter is between :behind_counter and 0
  :expire_time => 10,
}

dos = DosDetector.new r, cache, config

mutex.lock
begin
  Server.return Server::HTTP_SERVICE_UNAVAILABLE if dos.detect?
rescue => e
  raise "DosDetector failed: #{e}"
ensure
  mutex.unlock
end

例えば上記の例では、アクセス先のホスト名(:counter_key)に対し、5秒間(:threshold_time)で10000アクセス(:threshold_counter)あったらDoSと検知し、そこから2000アクセス(:behind_counter)の間か、10秒間(:expire_time)はDoS判定をする、という動きをします。

この辺りの挙動はサーバに合わせてパラメータを適宜チューニングすると良いです。また、上記例ではホスト名をキーにカウンタを計測していますが、そのキーをホスト名(r.hostname)以外の値、例えばファイル名とかファイルサイズ、ファイルのuidやアクセスIPアドレス・国名といったように、リクエスト単位でmod_mrubyやngx_mrubyから取得可能な値に変更することも可能です。

このカウンタのキーを色々と工夫したり、複数dosインスタンスを生成して組み合わせることで様々な条件でDoS判定する事も可能です。

また、上記例でdos.detect?によりDoS検知した時の制御を503(Server.return Server::HTTP_SERVICE_UNAVAILABLE)を返すというシンプルな処理にしていますが、これを例えばリダイレクトだったりとか、別のプロキシサーバにプロキシするとかそういった処理を実装することも可能です。これもmod_mrubyとngx_mrubyでやれる事だったら何でもできます。

現在のカウンター値等の情報を取得したり、それを元にロギングしたい場合などは、data = dos.analyzeなどによってデータを取得することができます。

まとめ

以上のように、apacheでもnginxでも同じRubyコードで動作可能なDoS検知・制御モジュールを作ってみました。これによって、様々なWebサービスへのアクセス過多をミドルウェアレイヤでRubyを用い柔軟に制御できるようになりました。mod_mrubyngx_mrubyを使うと違うWebサーバソフトウェアであっても共通したコードで同じ処理を実装できます。

便利なので是非ご活用下さい。