Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: rack-attack gemでアクセスをRack middlewareレベルで制限する

更新履歴

  • 2017/03/02: 初版公開
  • 2023/06/13: 更新

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

🔗 rack-attack gem

rack/rack-attack - GitHub

2012年のv1.0リリース以来長く使われていて実績があるのが助かります(現在はv6.6.1)。GitHubの★も5,300個を超えていますね。

rack-attackを利用可能なRailsバージョンはAppraisalsファイルで確認できます。

  • 以前はGitHubのkickstarter/rack-attackに置かれていましたが、現在はrack/rack-attackに置かれています。

  • Sinatraでは、以前は別gemだったsinatra/rack-protectionが本体に統合されました。

🔗 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)
      return @app.call(env) if !self.class.enabled || env["rack.attack.called"]

      env["rack.attack.called"] = true
      env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
      request = Rack::Attack::Request.new(env)

      if configuration.safelisted?(request)
        @app.call(env)
      elsif configuration.blocklisted?(request)
        # Deprecated: Keeping blocklisted_response for backwards compatibility
        if configuration.blocklisted_response
          configuration.blocklisted_response.call(env)
        else
          configuration.blocklisted_responder.call(request)
        end
      elsif configuration.throttled?(request)
        # Deprecated: Keeping throttled_response for backwards compatibility
        if configuration.throttled_response
          configuration.throttled_response.call(env)
        else
          configuration.throttled_responder.call(request)
        end
      else
        configuration.tracked?(request)
        @app.call(env)
      end
    end

アクセス制限では、IPアドレスやURL、UserAgent(≒ブラウザの種類)、HTTPレスポンスなど、さまざまな要素を指定できます。ログ出力(RailsのActiveSupport::Notificationsを利用↓)にも対応しています。

参考: Active Support の Instrumentation 機能 - Railsガイド

🔗 rack-attack gemの使いみち

以下のような使い方が主になると思います。

  • 短時間の大量アクセス(DOS攻撃)を遮断したい
  • /loginページで同一IPアドレスからの連続POSTリクエスト(パスワードの総当り攻撃)を遮断したい
  • 特定のIPアドレスにのみアクセスを許可したい
  • 特定のURLだけ特定のIPアドレスにのみアクセスを許可したい

同一IPアドレスからの短時間/大量アクセスの制限は、特にインターネットで一般公開されるRailsアプリで役立ちそうです。非常に大規模な大量アクセスについては、rack-attackだけに任せず、他の方法によるアクセス制限も併用する必要があるかもしれません。

# examples/rack_attack.rbより
# 同一IPからの1秒あたりの最大アクセス数を10回に制限
Rack::Attack.throttle("req/ip", limit: 10, period: 1) { |req| req.ip }

請け負っているRailsアプリのセキュリティに問題が見つかった場合に、取り急ぎrack-attackでアクセスを絞り込んでからセキュリティ問題に対処するといった使い方も考えられます。

🔗 rack-attack gemの導入

READMEにひととおり書いてありますが、一応こちらにも書いておきます。

  • Gemfileに以下を追記し、bundle installを実行します。またはgem install rack-attackでインストールできます。
gem 'rack-attack'

rack-attackをRailsアプリケーションにインストールすると、デフォルトで有効になります。

# rack-attackを無効にしたい場合の設定
Rack::Attack.enabled = false

rackアプリケーションの場合は以下のように設定します。

# config.ru (Rackupファイル)に書く場合
require "rack/attack"
use Rack::Attack

重要: デフォルトのrack-attackは、何らかのルールを設定するまではアクセスのブロックやスロットリングを一切行いません。

🔗 設定方法

config/initializers/ディレクトリの下にrack_attack.rbを作成し、以下のような要領でルールを定義します。

# ブロックを使わない場合
Rack::Attack.safelist_ip("5.6.7.8")
# ブロックを使う場合
Rack::Attack.safelist("mark any authenticated access safe") do |request|
  # 戻り値がtruthyの場合はリクエストを許可する
  request.env["HTTP_APIKEY"] == "secret-string"
end

初めての場合は一般的な設定例を参考にして少しずつ調整するとよいでしょう。以下に抜粋しました。

  # 例: 同一IPアドレスからのリクエスト数上限を5分あたり300回に制限
  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

より高度な設定方法については高度な設定例をご覧ください。ブラックリストを環境変数で与える方法や、BASIC認証への攻撃を遮断する方法などが記載されています。「大半のコードはテストされてないのでご注意ください」とあります。

通常利用で支障が生じないようにするため、設定値を厳しくしすぎないように注意しましょう。設定に引っかかるとリクエストがRailsアプリに渡されなくなるので、該当ユーザーから見て応答なしの状態になります。

🔗 参考: RailsのRack middlewareについて

技術評論社の「Rackとは何か」が一番参考になると思います。Rack middlewareに関するその他の一次情報リンクは以下のとおりです。

関連記事

Rackによるクエリ文字列のパースの実装を調査してみた

Rack 2-> Rack 3アップグレードガイド(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。