127 lines
4.9 KiB
Ruby
127 lines
4.9 KiB
Ruby
class Rack::Attack
|
|
|
|
# The following is a useful resource.
|
|
# https://www.driftingruby.com/episodes/rails-api-throttling-with-rack-attack
|
|
#
|
|
### Configure Cache ###
|
|
|
|
# If you don't want to use Rails.cache (Rack::Attack's default), then
|
|
# configure it here.
|
|
#
|
|
# Note: The store is only used for throttling (not blocklisting and
|
|
# safelisting). It must implement .increment and .write like
|
|
# ActiveSupport::Cache::Store
|
|
|
|
# Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
|
|
### Throttle Spammy Clients ###
|
|
|
|
# If we fail to unmask the remote IP for a request, the
|
|
# frontends will pass the internal network (10.0.0.0/8) to the
|
|
# unicorns. We need to ensure that we don't block these requests.
|
|
|
|
ArchiveConfig.RATE_LIMIT_SAFELIST.each do |ip|
|
|
Rack::Attack.safelist_ip(ip)
|
|
end
|
|
|
|
# If any single client IP is making tons of requests, then they're
|
|
# probably malicious or a poorly-configured scraper. Either way, they
|
|
# don't deserve to hog all of the app server's CPU. Cut them off!
|
|
#
|
|
# Note: If you're serving assets through rack, those requests may be
|
|
# counted by rack-attack and this throttle may be activated too
|
|
# quickly. If so, enable the condition to exclude them from tracking.
|
|
#
|
|
|
|
# This stanza allows us to limit by user which backend is selected by nginx.
|
|
|
|
ArchiveConfig.RATE_LIMIT_PER_NGINX_UPSTREAM_USER.each do |k, v|
|
|
throttle("req/#{k}/user", limit: v["limit"], period: v["period"]) do |req|
|
|
req.env["HTTP_X_AO3_SESSION_USER"] if req.env["HTTP_X_UNICORNS"] == k && req.env["HTTP_X_AO3_SESSION_USER"].present?
|
|
end
|
|
end
|
|
|
|
# This stanza allows us to limit by ip which backend is selected by nginx.
|
|
|
|
ArchiveConfig.RATE_LIMIT_PER_NGINX_UPSTREAM.each do |k, v|
|
|
throttle("req/#{k}/ip", limit: v["limit"], period: v["period"]) do |req|
|
|
req.ip if req.env["HTTP_X_UNICORNS"] == k
|
|
end
|
|
end
|
|
|
|
# Throttle all requests by IP (60rpm)
|
|
#
|
|
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
|
|
limit = ArchiveConfig.RATE_LIMIT_NUMBER
|
|
period = ArchiveConfig.RATE_LIMIT_PERIOD
|
|
throttle('req/ip', limit: limit, period: period) do |req|
|
|
req.ip
|
|
end
|
|
|
|
### Prevent Brute-Force Login Attacks ###
|
|
|
|
# The most common brute-force login attack is a brute-force password
|
|
# attack where an attacker simply tries a large number of emails and
|
|
# passwords to see if any credentials match.
|
|
#
|
|
# Another common method of attack is to use a swarm of computers with
|
|
# different IPs to try brute-forcing a password for a specific account.
|
|
|
|
login_limit = ArchiveConfig.RATE_LIMIT_LOGIN_ATTEMPTS
|
|
login_period = ArchiveConfig.RATE_LIMIT_LOGIN_PERIOD
|
|
|
|
# Throttle POST requests to /users/login by IP address
|
|
#
|
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
|
|
throttle("logins/ip", limit: login_limit, period: login_period) do |req|
|
|
req.ip if req.path == "/users/login" && req.post?
|
|
end
|
|
|
|
# Throttle POST requests to /users/login by login param (username or email)
|
|
#
|
|
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{login}"
|
|
#
|
|
# Note: This creates a problem where a malicious user could intentionally
|
|
# throttle logins for another user and force their login requests to be
|
|
# denied, but that's not very common and shouldn't happen to you. (Knock
|
|
# on wood!)
|
|
throttle("logins/email", limit: login_limit, period: login_period) do |req|
|
|
req.params.dig("user", "login").presence if req.path == "/users/login" && req.post?
|
|
end
|
|
|
|
### Add Rate Limits for Admin Login ###
|
|
admin_login_limit = ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_ATTEMPTS
|
|
admin_login_period = ArchiveConfig.RATE_LIMIT_ADMIN_LOGIN_PERIOD
|
|
|
|
# Throttle POST requests to /admin/login by IP address
|
|
#
|
|
# Key: "rack::attack:#{Time.now.to_i/:period}:admin_logins/ip:#{req.ip}"
|
|
throttle("admin_logins/ip", limit: admin_login_limit, period: admin_login_period) do |req|
|
|
req.ip if req.path == "/admin/login" && req.post?
|
|
end
|
|
|
|
# Throttle POST requests to /admin/login by login param (user name or email)
|
|
#
|
|
# Key: "rack::attack:#{Time.now.to_i/:period}:admin_logins/email:#{login}"
|
|
throttle("admin_logins/email", limit: admin_login_limit, period: admin_login_period) do |req|
|
|
req.params.dig("admin", "login").presence if req.path == "/admin/login" && req.post?
|
|
end
|
|
|
|
# Add Retry-After response header to let polite clients know
|
|
# how many seconds they should wait before trying again
|
|
Rack::Attack.throttled_response_retry_after_header = true
|
|
|
|
### Custom Throttle Response ###
|
|
|
|
# By default, Rack::Attack returns an HTTP 429 for throttled responses,
|
|
# which is just fine.
|
|
#
|
|
# If you want to return 503 so that the attacker might be fooled into
|
|
# believing that they've successfully broken your app (or you just want to
|
|
# customize the response), then uncomment these lines.
|
|
# self.throttled_response = lambda do |env|
|
|
# [ 503, # status
|
|
# {}, # headers
|
|
# ['']] # body
|
|
# end
|
|
end
|