[Rails] rack-attack gemでRack middlewareレベルのアクセス制限を実施する

こんにちは、hachi8833です。今回はrack-attack gemを取り上げます。 rack-attack gem リポジトリ: kickstarter/rack-attack rack-attack gemとは Railsが動作する土台となるRack middleware(後述)のレベルでアプリへのアクセスを制限できます。 アクセス制限をRailsアプリ側のコードで実装せずに済むので、開発がシンプルになります。逆にアクセス制限をRailsアプリと密に連携しようとすると大変かもしれません。 rack-attackの設定はRubyのコードになっているので、込み入った要件にも対応可能です。Rack::Attackというクラスを使います。 クラウドやホスティング環境でも類似のアクセス制限は多くの場合可能ですし、より下のレイヤで制限をかける方がパフォーマンス上有利ですが、設定は別途記録が必要になります。rack-attackの設定ならリポジトリに登録できるのでアクセス制限の履歴をRailsアプリのGitログなどで容易に追えるようになります。 以下の制限がチェックされます。 safelist アクセスを許可する対象を記述する blocklist アクセスをブロックする対象を記述する throttle 許容するアクセス回数や頻度の上限を指定し、超えたらブロックする track その他のチェック(ログ出力など)を行う 上記はattack.rbで以下のような流れで処理されます。 def call(env) req = Rack::Attack::Request.new(env) if safelisted?(req) @app.call(env) elsif blocklisted?(req) self.class.blocklisted_response.call(env) elsif throttled?(req) self.class.throttled_response.call(env) else tracked?(req) @app.call(env) end end アクセス制限では、IPアドレスやURL、UserAgent(≒ブラウザの種類)、HTTPレスポンスなど、さまざまな要素を指定できます。ログ出力(ActiveSupport::Notificationsを使用)にも対応しています。 2012年のv1.0リリース以来長く使われていて実績があるのが助かります(現在はv5.0.1)。GitHubの☆も3,000個を超えていますね。 明示されていませんが、Rails 3以上であれば利用できるようです。 追記: Rackを利用しているSinatraでも利用できそうですが、sinatra/rack-protectionという類似のgemが割りと最近Sinatra本体に統合されたようなので、そちらを使うのがよいかもしれません。 rack-attack gemの導入 READMEにひととおり書いてありますが、一応こちらにも書いておきます。 Gemfileのproduction環境用グループに以下を追記し、bundle installを実行します。 gem ‘rack-attack’ 以下のいずれかの方法でRack::Attack middlewareを有効にします。 # config/application.rbに書く場合(Rails 3以上) config.middleware.use Rack::Attack # config.ru (Rackupファイル)に書く場合 use Rack::Attack config/initializers/ディレクトリの下にrack-attack.rbを作成し、Rack::Attackを定義して設定を記述します。 # config/initializers/rack-attack.rb class Rack::Attack # カスタム設定をここに記述 end 設定方法 初めての場合はWikiに掲載されている一般的な設定例を使い、そこから少しずつ調整するとよいでしょう。以下に抜粋しました。 # 例: 同一IPアドレスからのリクエストを60回/分に制限 throttle(‘req/ip’, :limit => 300, :period => 5.minutes) do |req| req.ip # unless req.path.start_with?(‘/assets’) end # 例: 同一IPアドレスからの/loginsへのPOSTリクエストを20秒あたり5回までに制限 throttle(‘logins/ip’, :limit => 5, :period => 20.seconds) do |req| if req.path == ‘/login’ && req.post? req.ip end end より高度な設定方法についてはAdvanced Configurationをご覧ください。ブラックリストを環境変数で与える方法や、BASIC認証への攻撃を遮断する方法などが記載されています。「大半のコードはテストされてないのでご注意ください」とあります。 注意 通常利用で支障が生じないよう、設定値を下げすぎないようにします。設定に引っかかるとリクエストがRailsアプリに渡されなくなるので、該当ユーザーから見て応答なしの状態になります。 設定例のコメントによると、cssや画像などのアセットをRackで送信するようにしている場合、アセットへのリクエストもrack-attackでアクセス数としてカウントされ、期待より早い段階でthrottleの遮断が効いてしまう可能性があるとのことです。その場合はそうしたアセットを条件から除外する必要があるかもしれません。 rack-attack gemの使いみち 以下のような使い方が主になると思います。 短時間の大量アクセス(DOS攻撃)を遮断したい /loginページで同一IPアドレスからの連続POSTリクエスト(パスワードの総当り攻撃)を遮断したい 特定のIPアドレスにのみアクセスを許可したい 特定のURLだけ特定のIPアドレスにのみアクセスを許可したい 同一IPアドレスからの短時間/大量アクセスの制限は、特にインターネットで一般公開されるRailsアプリで役立ちそうです。非常に大規模な大量アクセスについては、rack-attackだけに任せず、他の方法によるアクセス制限も併用する必要があるかもしれません。 # examples/rack_attack.rbより # 同一IPからの1秒あたりのアクセスを30回までに制限 Rack::Attack.throttle(“req/ip”, :limit => 30, :period => 1) { |req| req.ip } 請け負ったRailsアプリのセキュリティに問題が見つかった場合に、セキュリティ問題をつぶす前に取り急ぎrack-attackでアクセスを絞り込むといった使い方も考えられます。 参考: RailsのRack middlewareについて 技術評論社の「Rackとは何か」が一番参考になると思います。Rack middlewareに関するその他の一次情報リンクは以下のとおりです。 Railsguides(英語のみ): Rails on Rack Rack API: http://www.rubydoc.info/github/rack/rack/master/file/SPEC