会社の運用メンバーと色々議論していた中で、「カジュアルにWebサーバへのDoSみたいなアクセスが来た時に検知して制御したいよねー」という話が上がったので、http-dos-detectorというnginxでもapacheでも使えるWebサーバ拡張をmrubyで書きました。
もちろんmrubyなので、mod_mrubyとngx_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_mrubyとngx_mrubyを使うと違うWebサーバソフトウェアであっても共通したコードで同じ処理を実装できます。
便利なので是非ご活用下さい。