Skip to content

Commit 3a394d4

Browse files
author
j-corry
committed
- Add RackAttack gem
- Add rack_attack.rb initializer with Cloudflare IP ranges whitelist - Add staging.rb env file, remove azure_production.rb
1 parent 3ecb6c2 commit 3a394d4

File tree

6 files changed

+206
-96
lines changed

6 files changed

+206
-96
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ gem "propshaft"
1212
gem "library_design", github: "ukparliament/design-assets", glob: 'library_design/*.gemspec', tag: "0.6.10"
1313
gem "importmap-rails"
1414
gem "haml"
15+
gem 'rack-attack'
1516
gem "turbo-rails"
1617
gem "stimulus-rails"
1718
gem "jbuilder"

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ GEM
193193
public_suffix (6.0.1)
194194
racc (1.8.1)
195195
rack (3.2.5)
196+
rack-attack (6.8.0)
197+
rack (>= 1.0, < 4)
196198
rack-cors (3.0.0)
197199
logger
198200
rack (>= 3.0.14)
@@ -325,6 +327,7 @@ DEPENDENCIES
325327
passenger (>= 6)
326328
pg
327329
propshaft
330+
rack-attack
328331
rails (= 8.1.2)
329332
rails-controller-testing
330333
rspec-rails

config/environments/azure_production.rb

Lines changed: 0 additions & 96 deletions
This file was deleted.

config/environments/production.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
Rails.application.configure do
44
# Settings specified here will take precedence over those in config/application.rb.
55

6+
# Ensure Rails trusts Cloudflare headers
7+
config.action_dispatch.trusted_proxies = Rack::Attack::CLOUDFLARE_IPS
8+
69
# Code is not reloaded between requests.
710
config.enable_reloading = false
811

config/environments/staging.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
require "active_support/core_ext/integer/time"
2+
3+
Rails.application.configure do
4+
# Settings specified here will take precedence over those in config/application.rb.
5+
6+
# Code is not reloaded between requests.
7+
config.enable_reloading = false
8+
9+
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
10+
config.eager_load = true
11+
12+
# Full error reports are disabled.
13+
config.consider_all_requests_local = false
14+
15+
# Turn on fragment caching in view templates.
16+
config.action_controller.perform_caching = true
17+
18+
# Cache assets for far-future expiry since they are all digest stamped.
19+
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
20+
21+
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
22+
# config.asset_host = "http://assets.example.com"
23+
24+
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
25+
# config.assume_ssl = true
26+
27+
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
28+
# config.force_ssl = true
29+
30+
# Skip http-to-https redirect for the default health check endpoint.
31+
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
32+
33+
# Log to STDOUT with the current request id as a default log tag.
34+
config.log_tags = [ :request_id ]
35+
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
36+
37+
# Change to "debug" to log everything (including potentially personally-identifiable information!).
38+
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
39+
40+
# Prevent health checks from clogging up the logs.
41+
config.silence_healthcheck_path = "/up"
42+
43+
# Don't log any deprecations.
44+
config.active_support.report_deprecations = false
45+
46+
# Replace the default in-process memory cache store with a durable alternative.
47+
# config.cache_store = :mem_cache_store
48+
49+
# Replace the default in-process and non-durable queuing backend for Active Job.
50+
# config.active_job.queue_adapter = :resque
51+
52+
# Ignore bad email addresses and do not raise email delivery errors.
53+
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
54+
# config.action_mailer.raise_delivery_errors = false
55+
56+
# Set host to be used by links generated in mailer templates.
57+
config.action_mailer.default_url_options = { host: "example.com" }
58+
59+
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
60+
# config.action_mailer.smtp_settings = {
61+
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
62+
# password: Rails.application.credentials.dig(:smtp, :password),
63+
# address: "smtp.example.com",
64+
# port: 587,
65+
# authentication: :plain
66+
# }
67+
68+
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
69+
# the I18n.default_locale when a translation cannot be found).
70+
config.i18n.fallbacks = true
71+
72+
# Do not dump schema after migrations.
73+
config.active_record.dump_schema_after_migration = false
74+
75+
# Only use :id for inspections in production.
76+
config.active_record.attributes_for_inspect = [ :id ]
77+
78+
# Enable DNS rebinding protection and other `Host` header attacks.
79+
# config.hosts = [
80+
# "example.com", # Allow requests from example.com
81+
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
82+
# ]
83+
#
84+
# Skip DNS rebinding protection for the default health check endpoint.
85+
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
86+
end

config/initializers/rack_attack.rb

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
return unless Rails.env.production?
2+
3+
class Rack::Attack
4+
5+
# IP list available at https://www.cloudflare.com/en-gb/ips/
6+
CLOUDFLARE_IPS = [
7+
IPAddr.new("173.245.48.0/20"),
8+
IPAddr.new("103.21.244.0/22"),
9+
IPAddr.new("103.22.200.0/22"),
10+
IPAddr.new("103.31.4.0/22"),
11+
IPAddr.new("141.101.64.0/18"),
12+
IPAddr.new("108.162.192.0/18"),
13+
IPAddr.new("190.93.240.0/20"),
14+
IPAddr.new("188.114.96.0/20"),
15+
IPAddr.new("197.234.240.0/22"),
16+
IPAddr.new("198.41.128.0/17"),
17+
IPAddr.new("162.158.0.0/15"),
18+
IPAddr.new("104.16.0.0/13"),
19+
IPAddr.new("104.24.0.0/14"),
20+
IPAddr.new("172.64.0.0/13"),
21+
IPAddr.new("131.0.72.0/22"),
22+
IPAddr.new("2400:cb00::/32"),
23+
IPAddr.new("2606:4700::/32"),
24+
IPAddr.new("2803:f800::/32"),
25+
IPAddr.new("2405:b500::/32"),
26+
IPAddr.new("2405:8100::/32"),
27+
IPAddr.new("2a06:98c0::/29"),
28+
IPAddr.new("2c0f:f248::/32")
29+
]
30+
31+
blocklist("block non-cloudflare traffic") do |req|
32+
ip = IPAddr.new(req.ip)
33+
allowed = CLOUDFLARE_IPS.any? { |range| range.include?(ip) }
34+
!allowed
35+
end
36+
37+
# Below: default Rack Attack config setup (mostly unused)
38+
### Configure Cache ###
39+
40+
# If you don't want to use Rails.cache (Rack::Attack's default), then
41+
# configure it here.
42+
#
43+
# Note: The store is only used for throttling (not blocklisting and
44+
# safelisting). It must implement .increment and .write like
45+
# ActiveSupport::Cache::Store
46+
47+
# Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
48+
49+
### Throttle Spammy Clients ###
50+
51+
# If any single client IP is making tons of requests, then they're
52+
# probably malicious or a poorly-configured scraper. Either way, they
53+
# don't deserve to hog all of the app server's CPU. Cut them off!
54+
#
55+
# Note: If you're serving assets through rack, those requests may be
56+
# counted by rack-attack and this throttle may be activated too
57+
# quickly. If so, enable the condition to exclude them from tracking.
58+
59+
# Throttle all requests by IP (60rpm)
60+
#
61+
# Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
62+
throttle('req/ip', limit: 300, period: 5.minutes) do |req|
63+
req.ip # unless req.path.start_with?('/assets')
64+
end
65+
66+
### Prevent Brute-Force Login Attacks ###
67+
68+
# The most common brute-force login attack is a brute-force password
69+
# attack where an attacker simply tries a large number of emails and
70+
# passwords to see if any credentials match.
71+
#
72+
# Another common method of attack is to use a swarm of computers with
73+
# different IPs to try brute-forcing a password for a specific account.
74+
75+
# Throttle POST requests to /login by IP address
76+
#
77+
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}"
78+
# throttle('logins/ip', limit: 5, period: 20.seconds) do |req|
79+
# if req.path == '/login' && req.post?
80+
# req.ip
81+
# end
82+
# end
83+
84+
# Throttle POST requests to /login by email param
85+
#
86+
# Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{normalized_email}"
87+
#
88+
# Note: This creates a problem where a malicious user could intentionally
89+
# throttle logins for another user and force their login requests to be
90+
# denied, but that's not very common and shouldn't happen to you. (Knock
91+
# on wood!)
92+
# throttle('logins/email', limit: 5, period: 20.seconds) do |req|
93+
# if req.path == '/login' && req.post?
94+
# # Normalize the email, using the same logic as your authentication process, to
95+
# # protect against rate limit bypasses. Return the normalized email if present, nil otherwise.
96+
# req.params['email'].to_s.downcase.gsub(/\s+/, "").presence
97+
# end
98+
# end
99+
100+
### Custom Throttle Response ###
101+
102+
# By default, Rack::Attack returns an HTTP 429 for throttled responses,
103+
# which is just fine.
104+
#
105+
# If you want to return 503 so that the attacker might be fooled into
106+
# believing that they've successfully broken your app (or you just want to
107+
# customize the response), then uncomment these lines.
108+
# self.throttled_responder = lambda do |env|
109+
# [ 503, # status
110+
# {}, # headers
111+
# ['']] # body
112+
# end
113+
end

0 commit comments

Comments
 (0)