diff --git a/CHANGELOG.md b/CHANGELOG.md index 59223bc..afce862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ All notable changes to this project will be documented in this file. ### Added - Shorthand for blocking an IP address `Rack::Attack.blocklist_ip("1.2.3.4")` -- Shorthand for blocking IP subnets `Rack::Attack.blocklist_ip("1.2.0.0/16")` +- Shorthand for blocking an IP subnet `Rack::Attack.blocklist_ip("1.2.0.0/16")` - Shorthand for safelisting an IP address `Rack::Attack.safelist_ip("5.6.7.8")` +- Shorthand for safelisting an IP subnet `Rack::Attack.safelist_ip("5.6.0.0/16")` - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 4abca17..cf1379d 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -45,7 +45,7 @@ class Rack::Attack def safelist_ip(ip) @ip_safelists ||= [] - ip_safelist_proc = lambda { |request| ip == request.ip } + ip_safelist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) } @ip_safelists << Safelist.new(nil, ip_safelist_proc) end diff --git a/spec/acceptance/safelisting_subnet_spec.rb b/spec/acceptance/safelisting_subnet_spec.rb new file mode 100644 index 0000000..920f265 --- /dev/null +++ b/spec/acceptance/safelisting_subnet_spec.rb @@ -0,0 +1,48 @@ +require_relative "../spec_helper" + +describe "Safelisting an IP subnet" do + before do + Rack::Attack.blocklist("admin") do |request| + request.path == "/admin" + end + + Rack::Attack.safelist_ip("5.6.0.0/16") + end + + it "forbids request if blocklist condition is true and safelist is false" do + get "/admin", {}, "REMOTE_ADDR" => "5.7.0.0" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "5.7.0.0" + + assert_equal 200, last_response.status + end + + it "succeeds request if blocklist condition is false and safelist is true" do + get "/", {}, "REMOTE_ADDR" => "5.6.0.0" + + assert_equal 200, last_response.status + end + + it "succeeds request if both blocklist and safelist conditions are true" do + get "/admin", {}, "REMOTE_ADDR" => "5.6.255.255" + + assert_equal 200, last_response.status + end + + it "notifies when the request is safe" do + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_type = request.env["rack.attack.match_type"] + end + + get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0" + + assert_equal 200, last_response.status + assert_equal :safelist, notification_type + end +end