Rack middleware for blocking & throttling
Pull request Compare This branch is 288 commits behind kickstarter:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
examples
lib/rack
spec
.gitignore
.travis.yml
Gemfile
README.md
Rakefile
rack-attack.gemspec

README.md

Rack::Attack!!!

A DSL for blocking & throttling abusive clients

Rack::Attack is a rack middleware to protect your web app from bad clients. It allows whitelisting, blacklisting, and throttling based on arbitrary properties of the request.

Throttle state is stored in a configurable cache (e.g. Rails.cache), presumably backed by memcached.

Installation

Install the rack-attack gem; or add it to you Gemfile with bundler:

# In your Gemfile
gem 'rack-attack'

Tell your app to use the Rack::Attack middleware. For Rails 3 apps:

# In config/application.rb
config.middleware.use Rack::Attack

Or for Rackup files:

# In config.ru
use Rack::Attack

Optionally configure the cache store for throttling:

Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache

Note that Rack::Attack.cache is only used for throttling; not blacklisting & whitelisting. Your cache store must implement increment and write like ActiveSupport::Cache::Store.

How it works

The Rack::Attack middleware compares each request against whitelists, blacklists, and throttles that you define. There are none by default.

  • If the request matches any whitelist, it is allowed. Blacklists and throttles are not checked.
  • If the request matches any blacklist, it is blocked. Throttles are not checked.
  • If the request matches any throttle, a counter is incremented in the Rack::Attack.cache. If the throttle limit is exceeded, the request is blocked and further throttles are not checked.

Usage

Define blacklists, throttles, and whitelists as blocks that return truthy values if matched, falsy otherwise. A Rack::Request object is passed to the block (named 'req' in the examples).

Blacklists

# Block requests from 1.2.3.4
Rack::Attack.blacklist('block 1.2.3.4') do |req|
  # Request are blocked if the return value is truthy
  '1.2.3.4' == req.ip
end

# Block logins from a bad user agent
Rack::Attack.blacklist('block bad UA logins') do |req|
  req.path == '/login' && req.post? && req.user_agent == 'BadUA'
end

Throttles

# Throttle requests to 5 requests per second per ip
Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
  # If the return value is truthy, the cache key for the return value
  # is incremented and compared with the limit. In this case:
  #   "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
  #
  # If falsy, the cache key is neither incremented nor checked.

  req.ip
end

# Throttle login attempts for a given email parameter to 6 reqs/minute
Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
  request.path == '/login' && req.post? && req.params['email']
end

Whitelists

# Always allow requests from localhost
# (blacklist & throttles are skipped)
Rack::Attack.whitelist('allow from localhost') do |req|
  # Requests are allowed if the return value is truthy
  '127.0.0.1' == req.ip
end

Responses

Customize the response of blacklisted and throttled requests using an object that adheres to the Rack app interface.

Rack:Attack.blacklisted_response = lambda do |env|
  [ 503, {}, ['Blocked']]
end

Rack:Attack.throttled_response = lambda do |env|
  # name and other data about the matched throttle
  body = [
    env['rack.attack.matched'],
    env['rack.attack.match_type'],
    env['rack.attack.match_data']
  ].inspect

  [ 503, {}, [body]]
end

For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:

request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }

Logging & Instrumentation

Rack::Attack uses the ActiveSupport::Notifications API if available.

You can subscribe to 'rack.attack' events and log it, graph it, etc:

ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
  puts req.inspect
end

Motivation

Abusive clients range from malicious login crackers to naively-written scrapers. They hinder the security, performance, & availability of web applications.

It is impractical if not impossible to block abusive clients completely.

Rack::Attack aims to let developers quickly mitigate abusive requests and rely less on short-term, one-off hacks to block a particular attack.

Rack::Attack complements tools like iptables and nginx's limit_zone module.

Travis CI Code Climate

License

Copyright (c) 2012 Kickstarter, Inc

Released under an MIT License