FEATURE: Rate limit exceptions via ENV (#14033)

FEATURE: Rate limit exceptions via ENV (#14033)

Allow admins to configure exceptions to our Rails rate limiter.

Configuration happens in the environment variables, and work with both IPs and CIDR blocks.

Example:

env:
  DISCOURSE_MAX_REQS_PER_IP_EXCEPTIONS: >-
    14.15.16.32/27
    216.148.1.2
diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb
index 0612345..b5fe321 100644
--- a/lib/middleware/request_tracker.rb
+++ b/lib/middleware/request_tracker.rb
@@ -8,6 +8,16 @@ class Middleware::RequestTracker
   @@detailed_request_loggers = nil
   @@ip_skipper = nil
 
+  # You can add exceptions to our app rate limiter in the app.yml ENV section.
+  # example:
+  #
+  # env:
+  #   DISCOURSE_MAX_REQS_PER_IP_EXCEPTIONS: >-
+  #     14.15.16.32/27
+  #     216.148.1.2
+  #
+  STATIC_IP_SKIPPER = ENV['DISCOURSE_MAX_REQS_PER_IP_EXCEPTIONS']&.split&.map { |ip| IPAddr.new(ip) }
+
   # register callbacks for detailed request loggers called on every request
   # example:
   #
@@ -234,6 +244,7 @@ class Middleware::RequestTracker
       end
 
       return false if @@ip_skipper&.call(ip)
+      return false if STATIC_IP_SKIPPER&.any? { |entry| entry.include?(ip) }
 
       limiter10 = RateLimiter.new(
         nil,
diff --git a/spec/components/middleware/request_tracker_spec.rb b/spec/components/middleware/request_tracker_spec.rb
index c4ee17d..63d9411 100644
--- a/spec/components/middleware/request_tracker_spec.rb
+++ b/spec/components/middleware/request_tracker_spec.rb
@@ -245,6 +245,38 @@ describe Middleware::RequestTracker do
       end
     end
 
+    it "blocks if the ip isn't static skipped" do
+      global_setting :max_reqs_per_ip_per_10_seconds, 1
+      global_setting :max_reqs_per_ip_mode, 'block'
+
+      env1 = env("REMOTE_ADDR" => "1.1.1.1")
+      status, _ = middleware.call(env1)
+      status, _ = middleware.call(env1)
+      expect(status).to eq(429)
+    end
+
+    it "doesn't block if rate limiter is enabled but IP is on the static exception list" do
+      stub_const(Middleware::RequestTracker, "STATIC_IP_SKIPPER", "177.33.14.73 191.209.88.192/30"&.split&.map { |ip| IPAddr.new(ip) }) do
+        global_setting :max_reqs_per_ip_per_10_seconds, 1
+        global_setting :max_reqs_per_ip_mode, 'block'
+
+        env1 = env("REMOTE_ADDR" => "177.33.14.73")
+        env2 = env("REMOTE_ADDR" => "191.209.88.194")
+
+        status, _ = middleware.call(env1)
+        expect(status).to eq(200)
+
+        status, _ = middleware.call(env1)
+        expect(status).to eq(200)
+
+        status, _ = middleware.call(env2)
+        expect(status).to eq(200)
+
+        status, _ = middleware.call(env2)
+        expect(status).to eq(200)
+      end
+    end
+
     describe "register_ip_skipper" do
       before do
         Middleware::RequestTracker.register_ip_skipper do |ip|

GitHub sha: b1363755827b6701930007ab10ca061ba389480e

This commit appears in #14033 which was approved by eviltrout. It was merged by Falco.