Skip to content

Instantly share code, notes, and snippets.

@czj
Created May 6, 2019 14:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save czj/bbe8724d217ce7498846530bc80012c6 to your computer and use it in GitHub Desktop.
Save czj/bbe8724d217ce7498846530bc80012c6 to your computer and use it in GitHub Desktop.
Sample Rack::Attack configuration file
class AppConfig
class << self
# Lookup via
# https://www.ultratools.com/tools/ipWhoisLookupResult
# https://www.whatismyip.com/ip-whois-lookup/
BLOCKED_IPS = Set.new(
[
"6.5.4.3",
"5.4.3.2",
"4.3.2.1",
]
).freeze
def blocked_ips
BLOCKED_IPS
end
SAFE_IPS = Set.new(
[
"6.6.6.6", # your IP
"1.3.3.7", # another IP
# Remove this IP if you need to test Rack::Attack
"127.0.0.1",
]
).freeze
def safe_ips
SAFE_IPS
end
end
end
# In development env, this middleware will only work with caching
# enabled (it is required to store count of access per IP).
#
# In test env, testing this middleware requires :
# - activate Rails' cache (in-memory)
# - remove 127.0.0.1 from AppConfig's list of safe ips
# - run an integration test many times
# - add back 127.0.0.1
# - clear cache and de-activate it
class Rack::Attack
AppConfig.safe_ips.each { |e| safelist_ip(e) }
AppConfig.blocked_ips.each { |e| blocklist_ip(e) }
# Cache the file on boot to make responses quick
THROTTLED_BODY = File.read(Rails.root.join("public", "429.html")).squish.freeze
# Be nice, reply with a legitimate alert
self.throttled_response = lambda do |_env|
[429, { "Content-Type" => "text/html" }, [THROTTLED_BODY]]
end
# Create the non-changing responses body, on boot, once and for all
BLOCKLISTED_BODY = SecureRandom.base64(1337).freeze
# Return status 200 to make their crawler think all went well
self.blocklisted_response = lambda do |_env|
[200, { "Content-Type" => "text/html" }, [BLOCKLISTED_BODY]]
end
# - only throttle a specific domain
#
# - guard condition excludes assets from throttling
# (imperative with a 12factor app that serves assets itself)
#
# - allow 100 requests in 5 minutes = 1 action every 3s
# a (crazy) bot will do that, a human probably never
#
# - do not use a too small windows (like 1 minute) beause a customer
# in a hurry might go quickly back and forth pages during checkout
# for a minute, then settle down (and using a browser's back button
# might reload the page)
throttle("request/ip", limit: 100, period: 5.minutes) do |request|
if request.host == HOST_TO_PROTECT && !request.path.start_with?("/assets/", "/packs/")
request.ip
end
end
# The most crawled URLs could be treated specifically :
#
# throttle("request/bots", limit: 1, period: 15) do |request|
# request.ip if AppConfig.blocked_ips.include?(request.ip)
# end
#
# prefix = %w(/a/ /b/ /c/ /d/ /e/ /compos/).freeze
# if request.path.start_with?(*THROTTLED_URLS_PREFIX)
# ...
# end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment