diff --git a/README.md b/README.md index 354e0c36..6294e667 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ may have unexpected consequences if applied to other TIMDEX UI apps. - `RESULTS_THROTTLE_PERIOD` - time in minutes for `/results` and `/record` endpoint throttle (default 1 minute). Throttled requests are redirected to Turnstile for verification. - `TURNSTILE_GRACE_PERIOD` - time in minutes that an IP is whitelisted from throttling after successfully passing Turnstile verification (default 15 minutes). This prevents users from being re-challenged repeatedly. - `RACK_ATTACK_VERBOSE_LOGGING` - Set to `false` to disable detailed Rack::Attack throttle event logging to stdout (default `true`). Useful for reducing test output noise while still maintaining throttle functionality. +- `RACK_ATTACK_REDIS_URL` - Optional. If set, uses a dedicated Redis cache store for Rack Attack throttle counters, isolating throttle traffic from application cache. On Heroku, provision with: `heroku addons:create heroku-redis:mini --as RACK_ATTACK_REDIS` (Heroku automatically appends `_URL` to create this env var). If unset, falls back to `Rails.cache`. - `BLOCKED_USER_AGENTS` - comma-separated list of user agent strings to hard-block with 403 Forbidden responses (bypasses throttling; much cheaper). Default blocks `Sogou web spider` which was responsible for 76.94k spoofed attack requests from non-Chinese IPs. Example: `"Sogou web spider,BadBot/2.0"` - `REDIRECT_REQUESTS_PER_PERIOD`- number of requests that can be made that the query string starts with our legacy redirect parameter to throttle per `REQUEST_PERIOD` - `REDIRECT_REQUEST_PERIOD`- time in minutes used along with `REDIRECT_REQUESTS_PER_PERIOD` diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 6e59bb59..ef6b306a 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -1,4 +1,6 @@ class Rack::Attack + Rails.logger.info "Rack Attack initializer loading" + # List of throttles that honor the Turnstile grace cookie. These throttles will redirect to the Turnstile challenge # page instead of returning 429, allowing verified users to continue during the grace period without increasing # limits for unverified traffic. @@ -53,6 +55,32 @@ def self.valid_format_token?(req) # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + # If we have a dedicated Rack Attack cache, use it. Separate cache in prod allows us to ensure that throttling does + # not interfere with our core result caching. + # + # To provision a dedicated Redis for Rack Attack on Heroku, use: + # heroku addons:create heroku-redis:mini --as RACK_ATTACK_REDIS + # Note: Heroku automatically appends "_URL" to the addon name, creating the RACK_ATTACK_REDIS_URL env var. + rack_attack_redis_url = ENV.fetch('RACK_ATTACK_REDIS_URL', '').presence + if rack_attack_redis_url + store = ActiveSupport::Cache::RedisCacheStore.new( + url: rack_attack_redis_url, + ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } + ) + Rack::Attack.cache.store = store + # Verify the cache can be written to + begin + store.write('rack_attack_test', 'ok', expires_in: 1.second) + Rails.logger.info("Rack Attack Redis cache initialized successfully") + rescue => e + Rails.logger.error("Rack Attack Redis cache initialization failed: #{e.class} - #{e.message}") + end + # Otherwise fall back to the Rails.cache. Not recommended in production, but fine everywhere else + else + Rack::Attack.cache.store = Rails.cache + Rails.logger.info("Rack Attack using Rails.cache (consider setting RACK_ATTACK_REDIS_URL in production)") + end + ### Safelist MIT IP addresses # http://kb.mit.edu/confluence/x/F4DCAg # Main IP range (includes campus, NAT pool, and VPNs)