rack-attack 6.7.0 → 6.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -6
- data/lib/rack/attack/base_proxy.rb +1 -0
- data/lib/rack/attack/cache.rb +1 -1
- data/lib/rack/attack/configuration.rb +7 -3
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +16 -10
- data/lib/rack/attack/store_proxy/redis_proxy.rb +2 -1
- data/lib/rack/attack/throttle.rb +2 -1
- data/lib/rack/attack/version.rb +1 -1
- data/lib/rack/attack.rb +3 -1
- data/spec/acceptance/blocking_ip_spec.rb +13 -8
- data/spec/acceptance/blocking_spec.rb +16 -18
- data/spec/acceptance/blocking_subnet_spec.rb +7 -8
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +5 -3
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +7 -5
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +5 -3
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +6 -4
- data/spec/acceptance/extending_request_object_spec.rb +3 -7
- data/spec/acceptance/fail2ban_spec.rb +42 -0
- data/spec/acceptance/safelisting_ip_spec.rb +12 -4
- data/spec/acceptance/safelisting_spec.rb +14 -14
- data/spec/acceptance/safelisting_subnet_spec.rb +6 -4
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +5 -2
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +0 -1
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +0 -2
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +5 -2
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +0 -1
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +0 -1
- data/spec/acceptance/stores/dalli_client_spec.rb +0 -1
- data/spec/acceptance/stores/redis_spec.rb +0 -1
- data/spec/acceptance/stores/redis_store_spec.rb +1 -3
- data/spec/acceptance/throttling_spec.rb +14 -23
- data/spec/acceptance/track_spec.rb +8 -9
- data/spec/acceptance/track_throttle_spec.rb +10 -16
- data/spec/configuration_spec.rb +33 -0
- data/spec/integration/offline_spec.rb +0 -12
- data/spec/rack_attack_instrumentation_spec.rb +24 -28
- data/spec/rack_attack_request_spec.rb +2 -4
- data/spec/rack_attack_reset_spec.rb +90 -0
- data/spec/rack_attack_spec.rb +0 -22
- data/spec/rack_attack_throttle_spec.rb +49 -28
- data/spec/rack_attack_track_spec.rb +4 -17
- data/spec/spec_helper.rb +4 -3
- data/spec/support/cache_store_helper.rb +31 -25
- data/spec/support/freeze_time_helper.rb +9 -0
- metadata +41 -15
- data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +0 -39
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +0 -25
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cca196b4f54fee6e576f7afd081dcc0311c25f2a9705a498bf5c319ec27a0d79
|
4
|
+
data.tar.gz: c758c6d6c9a10eac5ae1b20b1d501c3f9ded73d1c2dd77777a57a1727addf0fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f8a0c70cb9744d842786b4960d9ec82f971c54a2fa4c036c44919be5695310cb192f22fa5910edd187f1655e537e894f092db0b16bda41744ecac2cde9e1bb3
|
7
|
+
data.tar.gz: 6e999c3c981c90130aac0bffddd69feb09ff480cd1b791dea33f580080a2cf7ffc4e4949c93ec2e4dfe61dd1cdd957291e7893c0ca2b879b2092ac6984a45169
|
data/README.md
CHANGED
@@ -11,7 +11,6 @@ See the [Backing & Hacking blog post](https://www.kickstarter.com/backing-and-ha
|
|
11
11
|
|
12
12
|
[](https://badge.fury.io/rb/rack-attack)
|
13
13
|
[](https://github.com/rack/rack-attack/actions/workflows/build.yml)
|
14
|
-
[](https://codeclimate.com/github/kickstarter/rack-attack)
|
15
14
|
[](https://gitter.im/rack-attack/rack-attack)
|
16
15
|
|
17
16
|
## Table of contents
|
@@ -56,7 +55,7 @@ Add this line to your application's Gemfile:
|
|
56
55
|
```ruby
|
57
56
|
# In your Gemfile
|
58
57
|
|
59
|
-
gem
|
58
|
+
gem "rack-attack", "~> 6.8"
|
60
59
|
```
|
61
60
|
|
62
61
|
And then execute:
|
@@ -291,7 +290,7 @@ Rack::Attack.track("special_agent", limit: 6, period: 60) do |req|
|
|
291
290
|
end
|
292
291
|
|
293
292
|
# Track it using ActiveSupport::Notification
|
294
|
-
ActiveSupport::Notifications.subscribe("track.rack_attack") do |name, start, finish,
|
293
|
+
ActiveSupport::Notifications.subscribe("track.rack_attack") do |name, start, finish, instrumenter_id, payload|
|
295
294
|
req = payload[:request]
|
296
295
|
if req.env['rack.attack.matched'] == "special_agent"
|
297
296
|
Rails.logger.info "special_agent: #{req.path}"
|
@@ -302,7 +301,7 @@ end
|
|
302
301
|
|
303
302
|
### Cache store configuration
|
304
303
|
|
305
|
-
Throttle, allow2ban and fail2ban state is stored in a configurable cache (which defaults to `Rails.cache` if present), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
|
304
|
+
Throttle, track, allow2ban and fail2ban state is stored in a configurable cache (which defaults to `Rails.cache` if present), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
|
306
305
|
|
307
306
|
```ruby
|
308
307
|
# This is the default
|
@@ -383,7 +382,7 @@ To get notified about specific type of events, subscribe to the event name follo
|
|
383
382
|
E.g. for throttles use:
|
384
383
|
|
385
384
|
```ruby
|
386
|
-
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish,
|
385
|
+
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |name, start, finish, instrumenter_id, payload|
|
387
386
|
# request object available in payload[:request]
|
388
387
|
|
389
388
|
# Your code here
|
@@ -393,7 +392,7 @@ end
|
|
393
392
|
If you want to subscribe to every `rack_attack` event, use:
|
394
393
|
|
395
394
|
```ruby
|
396
|
-
ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish,
|
395
|
+
ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, instrumenter_id, payload|
|
397
396
|
# request object available in payload[:request]
|
398
397
|
|
399
398
|
# Your code here
|
data/lib/rack/attack/cache.rb
CHANGED
@@ -19,7 +19,7 @@ module Rack
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
attr_reader :safelists, :blocklists, :throttles, :anonymous_blocklists, :anonymous_safelists
|
22
|
+
attr_reader :safelists, :blocklists, :throttles, :tracks, :anonymous_blocklists, :anonymous_safelists
|
23
23
|
attr_accessor :blocklisted_responder, :throttled_responder, :throttled_response_retry_after_header
|
24
24
|
|
25
25
|
attr_reader :blocklisted_response, :throttled_response # Keeping these for backwards compatibility
|
@@ -61,11 +61,15 @@ module Rack
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def blocklist_ip(ip_address)
|
64
|
-
@anonymous_blocklists << Blocklist.new
|
64
|
+
@anonymous_blocklists << Blocklist.new do |request|
|
65
|
+
request.ip && !request.ip.empty? && IPAddr.new(ip_address).include?(IPAddr.new(request.ip))
|
66
|
+
end
|
65
67
|
end
|
66
68
|
|
67
69
|
def safelist_ip(ip_address)
|
68
|
-
@anonymous_safelists << Safelist.new
|
70
|
+
@anonymous_safelists << Safelist.new do |request|
|
71
|
+
request.ip && !request.ip.empty? && IPAddr.new(ip_address).include?(IPAddr.new(request.ip))
|
72
|
+
end
|
69
73
|
end
|
70
74
|
|
71
75
|
def throttle(name, options, &block)
|
@@ -10,17 +10,19 @@ module Rack
|
|
10
10
|
store.class.name == "ActiveSupport::Cache::RedisCacheStore"
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
if defined?(::ActiveSupport) && ::ActiveSupport::VERSION::MAJOR < 6
|
14
|
+
def increment(name, amount = 1, **options)
|
15
|
+
# RedisCacheStore#increment ignores options[:expires_in] in versions prior to 6.
|
16
|
+
#
|
17
|
+
# So in order to workaround this we use RedisCacheStore#write (which sets expiration) to initialize
|
18
|
+
# the counter. After that we continue using the original RedisCacheStore#increment.
|
19
|
+
if options[:expires_in] && !read(name)
|
20
|
+
write(name, amount, options)
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
amount
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -31,6 +33,10 @@ module Rack
|
|
31
33
|
def write(name, value, options = {})
|
32
34
|
super(name, value, options.merge!(raw: true))
|
33
35
|
end
|
36
|
+
|
37
|
+
def delete_matched(matcher, options = nil)
|
38
|
+
super(matcher.source, options)
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
36
42
|
end
|
@@ -45,11 +45,12 @@ module Rack
|
|
45
45
|
|
46
46
|
def delete_matched(matcher, _options = nil)
|
47
47
|
cursor = "0"
|
48
|
+
source = matcher.source
|
48
49
|
|
49
50
|
rescuing do
|
50
51
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
51
52
|
loop do
|
52
|
-
cursor, keys = scan(cursor, match:
|
53
|
+
cursor, keys = scan(cursor, match: source, count: 1000)
|
53
54
|
del(*keys) unless keys.empty?
|
54
55
|
break if cursor == "0"
|
55
56
|
end
|
data/lib/rack/attack/throttle.rb
CHANGED
@@ -38,8 +38,9 @@ module Rack
|
|
38
38
|
epoch_time: cache.last_epoch_time
|
39
39
|
}
|
40
40
|
|
41
|
+
annotate_request_with_throttle_data(request, data)
|
42
|
+
|
41
43
|
(count > current_limit).tap do |throttled|
|
42
|
-
annotate_request_with_throttle_data(request, data)
|
43
44
|
if throttled
|
44
45
|
annotate_request_with_matched_data(request, data)
|
45
46
|
Rack::Attack.instrument(request)
|
data/lib/rack/attack/version.rb
CHANGED
data/lib/rack/attack.rb
CHANGED
@@ -11,15 +11,17 @@ require 'rack/attack/store_proxy/mem_cache_store_proxy'
|
|
11
11
|
require 'rack/attack/store_proxy/redis_proxy'
|
12
12
|
require 'rack/attack/store_proxy/redis_store_proxy'
|
13
13
|
require 'rack/attack/store_proxy/redis_cache_store_proxy'
|
14
|
-
require 'rack/attack/store_proxy/active_support_redis_store_proxy'
|
15
14
|
|
16
15
|
require 'rack/attack/railtie' if defined?(::Rails)
|
17
16
|
|
18
17
|
module Rack
|
19
18
|
class Attack
|
20
19
|
class Error < StandardError; end
|
20
|
+
|
21
21
|
class MisconfiguredStoreError < Error; end
|
22
|
+
|
22
23
|
class MissingStoreError < Error; end
|
24
|
+
|
23
25
|
class IncompatibleStoreError < Error; end
|
24
26
|
|
25
27
|
autoload :Check, 'rack/attack/check'
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "Blocking an IP" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist_ip("1.2.3.4")
|
8
10
|
end
|
@@ -19,22 +21,25 @@ describe "Blocking an IP" do
|
|
19
21
|
assert_equal 200, last_response.status
|
20
22
|
end
|
21
23
|
|
22
|
-
it "
|
23
|
-
|
24
|
-
|
24
|
+
it "succeeds if IP is missing" do
|
25
|
+
get "/", {}, "REMOTE_ADDR" => ""
|
26
|
+
|
27
|
+
assert_equal 200, last_response.status
|
28
|
+
end
|
25
29
|
|
30
|
+
it "notifies when the request is blocked" do
|
26
31
|
ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload|
|
27
|
-
|
28
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
32
|
+
notifications.push(payload)
|
29
33
|
end
|
30
34
|
|
31
35
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
32
36
|
|
33
|
-
|
37
|
+
assert notifications.empty?
|
34
38
|
|
35
39
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
36
40
|
|
37
|
-
|
38
|
-
|
41
|
+
assert_equal 1, notifications.size
|
42
|
+
notification = notifications.pop
|
43
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
39
44
|
end
|
40
45
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "#blocklist" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist do |request|
|
8
10
|
request.ip == "1.2.3.4"
|
@@ -22,27 +24,26 @@ describe "#blocklist" do
|
|
22
24
|
end
|
23
25
|
|
24
26
|
it "notifies when the request is blocked" do
|
25
|
-
notification_matched = nil
|
26
|
-
notification_type = nil
|
27
|
-
|
28
27
|
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload|
|
29
|
-
|
30
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
28
|
+
notifications.push(payload)
|
31
29
|
end
|
32
30
|
|
33
31
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
34
32
|
|
35
|
-
|
36
|
-
assert_nil notification_type
|
33
|
+
assert notifications.empty?
|
37
34
|
|
38
35
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
39
36
|
|
40
|
-
|
41
|
-
|
37
|
+
assert_equal 1, notifications.size
|
38
|
+
notification = notifications.pop
|
39
|
+
assert_nil notification[:request].env["rack.attack.matched"]
|
40
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
44
|
describe "#blocklist with name" do
|
45
|
+
let(:notifications) { [] }
|
46
|
+
|
46
47
|
before do
|
47
48
|
Rack::Attack.blocklist("block 1.2.3.4") do |request|
|
48
49
|
request.ip == "1.2.3.4"
|
@@ -62,22 +63,19 @@ describe "#blocklist with name" do
|
|
62
63
|
end
|
63
64
|
|
64
65
|
it "notifies when the request is blocked" do
|
65
|
-
notification_matched = nil
|
66
|
-
notification_type = nil
|
67
|
-
|
68
66
|
ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload|
|
69
|
-
|
70
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
67
|
+
notifications.push(payload)
|
71
68
|
end
|
72
69
|
|
73
70
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
74
71
|
|
75
|
-
|
76
|
-
assert_nil notification_type
|
72
|
+
assert notifications.empty?
|
77
73
|
|
78
74
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
79
75
|
|
80
|
-
assert_equal
|
81
|
-
|
76
|
+
assert_equal 1, notifications.size
|
77
|
+
notification = notifications.pop
|
78
|
+
assert_equal "block 1.2.3.4", notification[:request].env["rack.attack.matched"]
|
79
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
82
80
|
end
|
83
81
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "Blocking an IP subnet" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist_ip("1.2.3.4/31")
|
8
10
|
end
|
@@ -26,21 +28,18 @@ describe "Blocking an IP subnet" do
|
|
26
28
|
end
|
27
29
|
|
28
30
|
it "notifies when the request is blocked" do
|
29
|
-
notified = false
|
30
|
-
notification_type = nil
|
31
|
-
|
32
31
|
ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload|
|
33
|
-
|
34
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
32
|
+
notifications.push(payload)
|
35
33
|
end
|
36
34
|
|
37
35
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
38
36
|
|
39
|
-
|
37
|
+
assert notifications.empty?
|
40
38
|
|
41
39
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
42
40
|
|
43
|
-
|
44
|
-
|
41
|
+
assert_equal 1, notifications.size
|
42
|
+
notification = notifications.pop
|
43
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
45
44
|
end
|
46
45
|
end
|
@@ -12,9 +12,11 @@ describe "Cache store config when using allow2ban" do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
unless defined?(Rails)
|
16
|
+
it "gives semantic error if no store was configured" do
|
17
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
18
|
+
get "/scarce-resource"
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -12,9 +12,11 @@ describe "Cache store config when using fail2ban" do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
unless defined?(Rails)
|
16
|
+
it "gives semantic error if no store was configured" do
|
17
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
18
|
+
get "/private-place"
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -79,7 +81,7 @@ describe "Cache store config when using fail2ban" do
|
|
79
81
|
end
|
80
82
|
|
81
83
|
it "works with any object that responds to #read, #write and #increment" do
|
82
|
-
|
84
|
+
fake_store_class = Class.new do
|
83
85
|
attr_accessor :backend
|
84
86
|
|
85
87
|
def initialize
|
@@ -100,7 +102,7 @@ describe "Cache store config when using fail2ban" do
|
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
103
|
-
Rack::Attack.cache.store =
|
105
|
+
Rack::Attack.cache.store = fake_store_class.new
|
104
106
|
|
105
107
|
get "/"
|
106
108
|
assert_equal 200, last_response.status
|
@@ -9,9 +9,11 @@ describe "Cache store config when throttling without Rails" do
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
unless defined?(Rails)
|
13
|
+
it "gives semantic error if no store was configured" do
|
14
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
15
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -11,10 +11,12 @@ describe "Cache store config with Rails" do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
unless defined?(Rails)
|
15
|
+
it "fails when Rails.cache is not set" do
|
16
|
+
Object.stub_const(:Rails, OpenStruct.new(cache: nil)) do
|
17
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
18
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -4,10 +4,8 @@ require_relative "../spec_helper"
|
|
4
4
|
|
5
5
|
describe "Extending the request object" do
|
6
6
|
before do
|
7
|
-
|
8
|
-
|
9
|
-
env["APIKey"] == "private-secret"
|
10
|
-
end
|
7
|
+
Rack::Attack::Request.define_method :authorized? do
|
8
|
+
env["APIKey"] == "private-secret"
|
11
9
|
end
|
12
10
|
|
13
11
|
Rack::Attack.blocklist("unauthorized requests") do |request|
|
@@ -17,9 +15,7 @@ describe "Extending the request object" do
|
|
17
15
|
|
18
16
|
# We don't want the extension to leak to other test cases
|
19
17
|
after do
|
20
|
-
|
21
|
-
remove_method :authorized?
|
22
|
-
end
|
18
|
+
Rack::Attack::Request.undef_method :authorized?
|
23
19
|
end
|
24
20
|
|
25
21
|
it "forbids request if blocklist condition is true" do
|
@@ -4,6 +4,8 @@ require_relative "../spec_helper"
|
|
4
4
|
require "timecop"
|
5
5
|
|
6
6
|
describe "fail2ban" do
|
7
|
+
let(:notifications) { [] }
|
8
|
+
|
7
9
|
before do
|
8
10
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
9
11
|
|
@@ -75,4 +77,44 @@ describe "fail2ban" do
|
|
75
77
|
assert_equal 200, last_response.status
|
76
78
|
end
|
77
79
|
end
|
80
|
+
|
81
|
+
it "notifies when the request is blocked" do
|
82
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload|
|
83
|
+
notifications.push(payload)
|
84
|
+
end
|
85
|
+
|
86
|
+
get "/"
|
87
|
+
|
88
|
+
assert_equal 200, last_response.status
|
89
|
+
assert notifications.empty?
|
90
|
+
|
91
|
+
get "/private-place"
|
92
|
+
|
93
|
+
assert_equal 403, last_response.status
|
94
|
+
assert_equal 1, notifications.size
|
95
|
+
notification = notifications.pop
|
96
|
+
assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"]
|
97
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
98
|
+
|
99
|
+
get "/"
|
100
|
+
|
101
|
+
assert_equal 200, last_response.status
|
102
|
+
assert notifications.empty?
|
103
|
+
|
104
|
+
get "/private-place"
|
105
|
+
|
106
|
+
assert_equal 403, last_response.status
|
107
|
+
assert_equal 1, notifications.size
|
108
|
+
notification = notifications.pop
|
109
|
+
assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"]
|
110
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
111
|
+
|
112
|
+
get "/"
|
113
|
+
|
114
|
+
assert_equal 403, last_response.status
|
115
|
+
assert_equal 1, notifications.size
|
116
|
+
notification = notifications.pop
|
117
|
+
assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"]
|
118
|
+
assert_equal :blocklist, notification[:request].env["rack.attack.match_type"]
|
119
|
+
end
|
78
120
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "Safelist an IP" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist("admin") do |request|
|
8
10
|
request.path == "/admin"
|
@@ -17,6 +19,12 @@ describe "Safelist an IP" do
|
|
17
19
|
assert_equal 403, last_response.status
|
18
20
|
end
|
19
21
|
|
22
|
+
it "forbids request if blocklist condition is true and safelist is false (missing IP)" do
|
23
|
+
get "/admin", {}, "REMOTE_ADDR" => ""
|
24
|
+
|
25
|
+
assert_equal 403, last_response.status
|
26
|
+
end
|
27
|
+
|
20
28
|
it "succeeds if blocklist condition is false and safelist is false" do
|
21
29
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
22
30
|
|
@@ -36,15 +44,15 @@ describe "Safelist an IP" do
|
|
36
44
|
end
|
37
45
|
|
38
46
|
it "notifies when the request is safe" do
|
39
|
-
notification_type = nil
|
40
|
-
|
41
47
|
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
42
|
-
|
48
|
+
notifications.push(payload)
|
43
49
|
end
|
44
50
|
|
45
51
|
get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8"
|
46
52
|
|
47
53
|
assert_equal 200, last_response.status
|
48
|
-
assert_equal
|
54
|
+
assert_equal 1, notifications.size
|
55
|
+
notification = notifications.pop
|
56
|
+
assert_equal :safelist, notification[:request].env["rack.attack.match_type"]
|
49
57
|
end
|
50
58
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "#safelist" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist do |request|
|
8
10
|
request.ip == "1.2.3.4"
|
@@ -38,23 +40,23 @@ describe "#safelist" do
|
|
38
40
|
end
|
39
41
|
|
40
42
|
it "notifies when the request is safe" do
|
41
|
-
notification_matched = nil
|
42
|
-
notification_type = nil
|
43
|
-
|
44
43
|
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload|
|
45
|
-
|
46
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
44
|
+
notifications.push(payload)
|
47
45
|
end
|
48
46
|
|
49
47
|
get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4"
|
50
48
|
|
51
49
|
assert_equal 200, last_response.status
|
52
|
-
|
53
|
-
|
50
|
+
assert_equal 1, notifications.size
|
51
|
+
notification = notifications.pop
|
52
|
+
assert_nil notification[:request].env["rack.attack.matched"]
|
53
|
+
assert_equal :safelist, notification[:request].env["rack.attack.match_type"]
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
57
|
describe "#safelist with name" do
|
58
|
+
let(:notifications) { [] }
|
59
|
+
|
58
60
|
before do
|
59
61
|
Rack::Attack.blocklist("block 1.2.3.4") do |request|
|
60
62
|
request.ip == "1.2.3.4"
|
@@ -90,18 +92,16 @@ describe "#safelist with name" do
|
|
90
92
|
end
|
91
93
|
|
92
94
|
it "notifies when the request is safe" do
|
93
|
-
notification_matched = nil
|
94
|
-
notification_type = nil
|
95
|
-
|
96
95
|
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
97
|
-
|
98
|
-
notification_type = payload[:request].env["rack.attack.match_type"]
|
96
|
+
notifications.push(payload)
|
99
97
|
end
|
100
98
|
|
101
99
|
get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4"
|
102
100
|
|
103
101
|
assert_equal 200, last_response.status
|
104
|
-
assert_equal
|
105
|
-
|
102
|
+
assert_equal 1, notifications.size
|
103
|
+
notification = notifications.pop
|
104
|
+
assert_equal "safe path", notification[:request].env["rack.attack.matched"]
|
105
|
+
assert_equal :safelist, notification[:request].env["rack.attack.match_type"]
|
106
106
|
end
|
107
107
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "../spec_helper"
|
4
4
|
|
5
5
|
describe "Safelisting an IP subnet" do
|
6
|
+
let(:notifications) { [] }
|
7
|
+
|
6
8
|
before do
|
7
9
|
Rack::Attack.blocklist("admin") do |request|
|
8
10
|
request.path == "/admin"
|
@@ -36,15 +38,15 @@ describe "Safelisting an IP subnet" do
|
|
36
38
|
end
|
37
39
|
|
38
40
|
it "notifies when the request is safe" do
|
39
|
-
notification_type = nil
|
40
|
-
|
41
41
|
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
42
|
-
|
42
|
+
notifications.push(payload)
|
43
43
|
end
|
44
44
|
|
45
45
|
get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0"
|
46
46
|
|
47
47
|
assert_equal 200, last_response.status
|
48
|
-
assert_equal
|
48
|
+
assert_equal 1, notifications.size
|
49
|
+
notification = notifications.pop
|
50
|
+
assert_equal :safelist, notification[:request].env["rack.attack.match_type"]
|
49
51
|
end
|
50
52
|
end
|