If you're feeling ambitious or you have a very particular use-case for Rack::Attack, these advanced configurations may help.
🪲
By layering throttles with linearly increasing limits and exponentially increasing periods, you can mimic an exponential backoff throttle. See #106 for more discussion.
# 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
You can define helpers on requests like localhost?
or subdomain
by monkey-patching Rack::Attack::Request
. See #73 for more discussion.
class Rack::Attack::Request < ::Rack::Request
def localhost?
ip == "127.0.0.1"
end
end
Rack::Attack.safelist("localhost") { |req| req.localhost? }
You can have Rack::Attack
configure its blocklists from ENV variables to simplify maintenance. See #110 for more discussion.
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
By doing a bunch of monkey-patching, you can add a helper for resetting specific throttles. The implementation is kind of long, so see #113 for more discussion.
Rack::Attack.reset_throttle "logins/email", "[email protected]"
You can configure blocklists to check values stored in Rails.cache
to allow setting blocklists from inside your application. See #111 for more discussion.
# 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
An example implementation for blocking hackers who spam basic auth attempts. See #47 for more discussion.
# After 5 requests with incorrect auth in 1 minute,
# block all requests from that IP for 1 hour.
Rack::Attack.blocklist('basic auth crackers') do |req|
Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 5, :findtime => 1.minute, :bantime => 1.hour) do
# Return true if the authorization header is incorrect
auth = Rack::Auth::Basic::Request.new(req.env)
auth.credentials != [my_username, my_password]
end
end
Instead of matching the URL with complex regex, it can be much easier to match specific controller actions:
Rack::Attack.safelist('unlimited requests') do |request|
safelist = [
'controller#action',
'another_controller#another_action'
]
route = (Rails.application.routes.recognize_path request.url rescue {}) || {}
action = "#{route[:controller]}##{route[:action]}"
safelist.any? { |safe| action == safe }
end