Skip to content

Commit

Permalink
Merge pull request #2495 from sul-dlss/rack-attack
Browse files Browse the repository at this point in the history
Add rack-attack.
  • Loading branch information
corylown authored Jul 26, 2024
2 parents 514889f + bdb066f commit bb9d53b
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,5 @@ gem 'view_component', '~> 2.82' # ViewComponent 3.x breaks blacklight_range_limi
# Used for shared reporting https://github.com/sul-dlss/exhibits/issues/2069
gem 'redis', '~> 5.0'
gem 'recaptcha', '~> 5.16'

gem 'rack-attack'
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ GEM
stanford-mods
racc (1.8.0)
rack (2.2.9)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-mini-profiler (3.3.1)
rack (>= 1.2.0)
rack-test (2.1.0)
Expand Down Expand Up @@ -848,6 +850,7 @@ DEPENDENCIES
okcomputer
puma (~> 5.0)
purl_fetcher-client (~> 0.4)
rack-attack
rack-mini-profiler
rails (~> 7.0)
rails-controller-testing
Expand Down
71 changes: 71 additions & 0 deletions config/initializers/rack_attack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module Rack
##
# See https://github.com/kickstarter/rack-attack/blob/master/docs/example_configuration.md
# for more configuration options
class Attack
### Throttle Spammy Clients ###

# 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.

# Throttle all requests by IP (60rpm)
#
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
throttle('req/ip', limit: 300, period: 5.minutes, &:ip)

# Throttle search requests by IP (15rpm)
Rack::Attack.throttle('req/search/ip', limit: 15, period: 1.minute) do |req|
route = begin
Rails.application.routes.recognize_path(req.path) || {}
rescue StandardError
{}
end

req.ip if route[:controller]&.match?('catalog') && (route[:action] == 'index' || route[:action] == 'facet')
end

# Throttle document actions requests by IP (15rpm)
Rack::Attack.throttle('req/actions/ip', limit: 15, period: 1.minute) do |req|
route = begin
Rails.application.routes.recognize_path(req.path) || {}
rescue StandardError
{}
end

req.ip if route[:action].in? %w[email sms]
end

# Inform throttled clients about limits and when they will get out of jail
Rack::Attack.throttled_response_retry_after_header = true
Rack::Attack.throttled_responder = lambda do |request|
match_data = request.env['rack.attack.match_data']
now = match_data[:epoch_time]

if Settings.throttling.notify_honeybadger && (
((match_data[:limit] - match_data[:count]) < 5) || (match_data[:count] % 10).zero?
)
Honeybadger.notify('Throttling request', context: { ip: request.ip, path: request.path }.merge(match_data))
end

headers = {
'RateLimit-Limit' => match_data[:limit].to_s,
'RateLimit-Remaining' => '0',
'RateLimit-Reset' => (now + (match_data[:period] - (now % match_data[:period]))).to_s
}

[429, headers, ["Throttled\n"]]
end

# Safelist the following IP ranges:
Rack::Attack.safelist_ip('171.64.0.0/14')
Rack::Attack.safelist_ip('10.0.0.0/8')
Rack::Attack.safelist_ip('172.16.0.0/12')
end
end
2 changes: 2 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ action_mailer:
from: "[email protected]"
default_url_options:
host: "example.com"
throttling:
notify_honeybadger: true

nondiscoverable_exhibit_slugs:
- exhibits-documentation
Expand Down

0 comments on commit bb9d53b

Please sign in to comment.