FEATURE: global rate limiter can bypass local IPs

This commit is contained in:
Sam 2018-01-08 08:39:17 +11:00
parent e3f8182125
commit cecd7d0d07
3 changed files with 62 additions and 4 deletions

View File

@ -178,5 +178,8 @@ max_admin_api_reqs_per_key_per_minute = 60
max_requests_per_ip_per_minute = 200
max_requests_per_ip_per_10_seconds = 50
# global rate limiter will simply warn if the limit is exceeded, can be warn, block or none
# global rate limiter will simply warn if the limit is exceeded, can be warn+block, warn, block or none
max_requests_per_ip_mode = none
# bypass rate limiting any IP resolved as a private IP
max_requests_rate_limit_on_private = false

View File

@ -142,14 +142,27 @@ class Middleware::RequestTracker
log_request_info(env, result, info) unless env["discourse.request_tracker.skip"]
end
PRIVATE_IP = /^(127\.)|(192\.168\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(::1$)|([fF][cCdD])/
def is_private_ip?(ip)
ip = IPAddr.new(ip) rescue nil
!!(ip && ip.to_s.match?(PRIVATE_IP))
end
def rate_limit(env)
if (
GlobalSetting.max_requests_per_ip_mode == "block" ||
GlobalSetting.max_requests_per_ip_mode == "warn"
GlobalSetting.max_requests_per_ip_mode == "warn" ||
GlobalSetting.max_requests_per_ip_mode == "warn+block"
)
ip = Rack::Request.new(env).ip
if !GlobalSetting.max_requests_rate_limit_on_private
return false if is_private_ip?(ip)
end
limiter10 = RateLimiter.new(
nil,
"global_ip_limit_10_#{ip}",
@ -172,9 +185,12 @@ class Middleware::RequestTracker
type = 60
limiter60.performed!
rescue RateLimiter::LimitExceeded
if GlobalSetting.max_requests_per_ip_mode == "warn"
if (
GlobalSetting.max_requests_per_ip_mode == "warn" ||
GlobalSetting.max_requests_per_ip_mode == "warn+block"
)
Rails.logger.warn("Global IP rate limit exceeded for #{ip}: #{type} second rate limit, uri: #{env["REQUEST_URI"]}")
false
!(GlobalSetting.max_requests_per_ip_mode == "warn")
else
true
end

View File

@ -112,6 +112,45 @@ describe Middleware::RequestTracker do
expect(status).to eq(200)
end
it "blocks private IPs if not skipped" do
global_setting :max_requests_per_ip_per_10_seconds, 1
global_setting :max_requests_per_ip_mode, 'warn+block'
global_setting :max_requests_rate_limit_on_private, true
env1 = env("REMOTE_ADDR" => "127.0.0.2")
status, _ = middleware.call(env1)
status, _ = middleware.call(env1)
expect(Rails.logger.warnings).to eq(1)
expect(status).to eq(429)
end
it "does nothing for private IPs if skipped" do
global_setting :max_requests_per_ip_per_10_seconds, 1
global_setting :max_requests_per_ip_mode, 'warn+block'
global_setting :max_requests_rate_limit_on_private, false
env1 = env("REMOTE_ADDR" => "127.0.3.1")
status, _ = middleware.call(env1)
status, _ = middleware.call(env1)
expect(Rails.logger.warnings).to eq(0)
expect(status).to eq(200)
end
it "does warn if rate limiter is enabled via warn+block" do
global_setting :max_requests_per_ip_per_10_seconds, 1
global_setting :max_requests_per_ip_mode, 'warn+block'
status, _ = middleware.call(env)
status, _ = middleware.call(env)
expect(Rails.logger.warnings).to eq(1)
expect(status).to eq(429)
end
it "does warn if rate limiter is enabled" do
global_setting :max_requests_per_ip_per_10_seconds, 1
global_setting :max_requests_per_ip_mode, 'warn'