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

こんにちは、hachi8833です。今回はrack-attack gemを取り上げます。

rack-attack gem

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に関するその他の一次情報リンクは以下のとおりです。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833 コボラー、ITコンサル、ローカライズ業界を経てなぜかWeb開発者志願。 これまでにRuby on Rails チュートリアルの大半、Railsガイドのほぼすべてを翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

Rubyスタイルガイドを読む

BigBinary記事より

ActiveSupport探訪シリーズ