From dccce4ee3db688ba044f6c77db90eb65f94045b2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 17:33:58 -0300 Subject: [PATCH] Provide shorthand to blocklist an IP --- CHANGELOG.md | 1 + lib/rack/attack.rb | 17 ++++++++++--- lib/rack/attack/check.rb | 1 + spec/acceptance/blocking_ip_spec.rb | 38 +++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 spec/acceptance/blocking_ip_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e114486..95bd8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added +- Shorthand for blocking IP addresses `Rack::Attack.blocklist_ip("1.2.3.4")` - 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 dd151fd..f630167 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -37,6 +37,12 @@ class Rack::Attack self.blocklists[name] = Blocklist.new(name, block) end + def blocklist_ip(ip) + @ip_blocklists ||= [] + ip_blocklist_proc = lambda { |request| request.ip == ip } + @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) + end + def blacklist(name, &block) warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead." blocklist(name, &block) @@ -77,9 +83,8 @@ class Rack::Attack end def blocklisted?(req) - blocklists.any? do |name, blocklist| - blocklist[req] - end + ip_blocklists.any? { |blocklist| blocklist.match?(req) } || + blocklists.any? { |_name, blocklist| blocklist.match?(req) } end def blacklisted?(req) @@ -109,6 +114,7 @@ class Rack::Attack def clear! @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {} + @ip_blocklists = [] end def blacklisted_response=(res) @@ -121,6 +127,11 @@ class Rack::Attack blocklisted_response end + private + + def ip_blocklists + @ip_blocklists ||= [] + end end # Set defaults diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index 21451de..8ae9ba1 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -17,6 +17,7 @@ module Rack } end + alias_method :match?, :[] end end end diff --git a/spec/acceptance/blocking_ip_spec.rb b/spec/acceptance/blocking_ip_spec.rb new file mode 100644 index 0000000..72c599c --- /dev/null +++ b/spec/acceptance/blocking_ip_spec.rb @@ -0,0 +1,38 @@ +require_relative "../spec_helper" + +describe "Blocking an IP" do + before do + Rack::Attack.blocklist_ip("1.2.3.4") + end + + it "forbids request if IP matches" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if IP doesn't match" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "notifies when the request is blocked" do + notified = false + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notified = true + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + refute notified + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert notified + assert_equal :blocklist, notification_type + end +end