Rack::Attack Documentation

repository·main·Indexed 26 days ago

https://github.com/rack/rack-attack

A Rack middleware for Ruby applications that protects Rails and Rack apps from abusive clients. It provides tools to safelist, block, and throttle requests based on request properties. Features include Fail2Ban and Allow2Ban filters, customizable blocklist and throttle responses, and integration with ActiveSupport::Notifications for monitoring and logging.

Tokens
5.8K
Snippets
19
Records
40
Agent score
91%

What's inside Rack::Attack

  1. Overview of Rack::Attack

    main
    Rack::Attack is a Rack middleware designed to protect Rails and Rack applications from abusive clients. It allows you to define rules to allow, block, or throttle requests based on various request properties.
  2. Understand the Rack::Attack request processing logic

    main

    Rack::Attack processes requests through a specific hierarchy. Once a request matches a rule, the subsequent rules are not evaluated:

    1. Safelists: If a request matches a safelist, it is allowed immediately.
    2. Blocklists: If not safelisted, but matches a blocklist, the request is blocked.
    3. Throttles: If not safelisted or blocklisted, but matches a throttle, a counter is incremented in Rack::Attack.cache. If the limit is exceeded, the request is blocked.
    4. Tracks: If none of the above match, all tracks are checked (for logging/measuring purposes), and the request is allowed.
  3. Set up the development environment for Rack::Attack

    main

    To develop on Rack::Attack, you must have both Redis and Memcached running locally. They must be bound to 127.0.0.1 on their default ports and accessible without authentication:

    • Redis: Port 6379
    • Memcached: Port 11211
  4. Blocklist Using Environment Variables

    main

    To simplify maintenance, you can configure blocklists using values from environment variables. A common pattern is to split a comma-separated string from an ENV variable into an array and then convert that array into a Regexp for matching.

    class Rack::Attack
      # Split on a comma with 0 or more spaces after it.
      # E.g. ENV['HEROKU_VARIABLE'] = "foo.com, bar.com"
      # spammers = ["foo.com", "bar.com"]
      spammers = ENV['HEROKU_VARIABLE'].split(/,\s*/)
    
      # Turn spammers array into a regexp
      spammer_regexp = Regexp.union(spammers) # /foo\.com|bar\.com/
      blocklist("block referer spam") do |request|
        request.referer =~ spammer_regexp
      end
    end
  5. Implement Exponential Backoff Throttling

    main

    You can mimic exponential backoff by layering multiple throttle definitions with linearly increasing limits and exponentially increasing periods. This allows for progressively stricter limits as requests continue.

    # Allows 20 requests in 8  seconds
    #        40 requests in 64 seconds
    #        ...
    #        100 requests in 0.38 days (~250 requests/day)
    (1..5).each do |level|
      throttle("logins/ip/#{level}", :limit => (20 * level), :period => (8 ** level).seconds) do |req|
        if req.path == '/login' && req.post?
          req.ip
        end
      end
    end
  6. Install Rack::Attack

    main

    To install Rack::Attack, add the gem to your Gemfile and run bundle, or install it directly via the command line.

    Using Bundler: Add this to your Gemfile:

    gem "rack-attack", "~> 6.8"

    Then run:

    bundle

    Direct Installation:

    gem install rack-attack
  7. Blocklist Using Rails.cache

    main

    You can configure blocklists to check values stored in Rails.cache. This allows your application logic to dynamically add or remove IPs from a blocklist by writing to or deleting from the cache.

    # Block attacks from IPs in cache
    # To add an IP: Rails.cache.write("block 1.2.3.4", true, expires_in: 2.days)
    # To remove an IP: Rails.cache.delete("block 1.2.3.4")
    Rack::Attack.blocklist("block IP") do |req|
      Rails.cache.read("block #{req.ip}")
    end
  8. Define Custom Request Helpers

    main

    You can extend Rack::Attack::Request (which inherits from Rack::Request) to add custom helper methods. These helpers can then be used within safelist, blocklist, or throttle blocks to simplify logic.

    class Rack::Attack::Request < ::Rack::Request
      def localhost?
        ip == "127.0.0.1"
      end
    end
    
    Rack::Attack.safelist("localhost") { |req| req.localhost? }
  9. Install dependencies and run tests

    main

    Follow these steps to install the necessary dependencies and execute the test suite using Appraisal:

    1. Install standard dependencies: bundle install
    2. Install test-specific dependencies: bundle exec appraisal install
    3. Run the test suite: bundle exec appraisal rake test
    $ bundle install
    $ bundle exec appraisal install
    $ bundle exec appraisal rake test
  10. Plug Rack::Attack into your application

    main

    Rack::Attack must be added as middleware to your application to function.

    For Rails applications

    Rack::Attack is used by default in Rails. You can enable or disable it using:

    Rack::Attack.enabled = false

    For Rack applications

    Add the following to your config.ru file:

    require "rack/attack"
    use Rack::Attack

    Note: By default, Rack::Attack will not perform any blocking or throttling until you define specific rules in your configuration.

    # In config.ru
    
    require "rack/attack"
    use Rack::Attack
  11. Enable Retry-After header for throttled clients

    main

    To help well-behaved clients know when they can retry a request, you can enable the Retry-After header by setting throttled_response_retry_after_header to true.

    Rack::Attack.throttled_response_retry_after_header = true
  12. Configure the Rack::Attack cache store

    main

    By default, Rack::Attack uses Rails.cache. If you want to use a different store, you can configure it via Rack::Attack.cache.store.

    Note: The cache store is only used for throttling. It is not used for blocklisting or safelisting. The store must implement .increment and .write methods, similar to ActiveSupport::Cache::Store.