rack-attack 5.4.2 → 6.0.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 +65 -23
- data/Rakefile +3 -1
- data/lib/rack/attack.rb +46 -70
- data/lib/rack/attack/allow2ban.rb +2 -0
- data/lib/rack/attack/blocklist.rb +3 -1
- data/lib/rack/attack/cache.rb +5 -3
- data/lib/rack/attack/check.rb +3 -1
- data/lib/rack/attack/fail2ban.rb +2 -0
- data/lib/rack/attack/path_normalizer.rb +2 -0
- data/lib/rack/attack/request.rb +2 -0
- data/lib/rack/attack/safelist.rb +3 -1
- data/lib/rack/attack/store_proxy.rb +12 -14
- data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +37 -0
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +27 -13
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +2 -4
- data/lib/rack/attack/store_proxy/redis_proxy.rb +16 -10
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -5
- data/lib/rack/attack/throttle.rb +8 -6
- data/lib/rack/attack/track.rb +5 -3
- data/lib/rack/attack/version.rb +3 -1
- data/spec/acceptance/allow2ban_spec.rb +2 -0
- data/spec/acceptance/blocking_ip_spec.rb +4 -2
- data/spec/acceptance/blocking_spec.rb +45 -3
- data/spec/acceptance/blocking_subnet_spec.rb +4 -2
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +8 -12
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +8 -12
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +2 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +2 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +2 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +2 -0
- data/spec/acceptance/extending_request_object_spec.rb +2 -0
- data/spec/acceptance/fail2ban_spec.rb +2 -0
- data/spec/acceptance/safelisting_ip_spec.rb +4 -2
- data/spec/acceptance/safelisting_spec.rb +57 -3
- data/spec/acceptance/safelisting_subnet_spec.rb +4 -2
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +2 -0
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +3 -1
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +2 -0
- data/spec/acceptance/stores/dalli_client_spec.rb +2 -0
- data/spec/acceptance/stores/redis_store_spec.rb +2 -0
- data/spec/acceptance/throttling_spec.rb +7 -5
- data/spec/acceptance/track_spec.rb +5 -3
- data/spec/acceptance/track_throttle_spec.rb +5 -3
- data/spec/allow2ban_spec.rb +3 -1
- data/spec/fail2ban_spec.rb +3 -1
- data/spec/integration/offline_spec.rb +3 -1
- data/spec/rack_attack_dalli_proxy_spec.rb +2 -0
- data/spec/rack_attack_instrumentation_spec.rb +42 -0
- data/spec/rack_attack_path_normalizer_spec.rb +2 -0
- data/spec/rack_attack_request_spec.rb +2 -0
- data/spec/rack_attack_spec.rb +2 -21
- data/spec/rack_attack_throttle_spec.rb +10 -8
- data/spec/rack_attack_track_spec.rb +4 -2
- data/spec/spec_helper.rb +5 -4
- data/spec/support/cache_store_helper.rb +2 -0
- metadata +21 -14
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +0 -50
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
require "minitest/stub_const"
|
3
5
|
|
@@ -20,11 +22,9 @@ describe "Cache store config when using fail2ban" do
|
|
20
22
|
raised_exception = nil
|
21
23
|
|
22
24
|
fake_store_class = Class.new do
|
23
|
-
def write(key, value)
|
24
|
-
end
|
25
|
+
def write(key, value); end
|
25
26
|
|
26
|
-
def increment(key, count, options = {})
|
27
|
-
end
|
27
|
+
def increment(key, count, options = {}); end
|
28
28
|
end
|
29
29
|
|
30
30
|
Object.stub_const(:FakeStore, fake_store_class) do
|
@@ -42,11 +42,9 @@ describe "Cache store config when using fail2ban" do
|
|
42
42
|
raised_exception = nil
|
43
43
|
|
44
44
|
fake_store_class = Class.new do
|
45
|
-
def read(key)
|
46
|
-
end
|
45
|
+
def read(key); end
|
47
46
|
|
48
|
-
def increment(key, count, options = {})
|
49
|
-
end
|
47
|
+
def increment(key, count, options = {}); end
|
50
48
|
end
|
51
49
|
|
52
50
|
Object.stub_const(:FakeStore, fake_store_class) do
|
@@ -64,11 +62,9 @@ describe "Cache store config when using fail2ban" do
|
|
64
62
|
raised_exception = nil
|
65
63
|
|
66
64
|
fake_store_class = Class.new do
|
67
|
-
def read(key)
|
68
|
-
end
|
65
|
+
def read(key); end
|
69
66
|
|
70
|
-
def write(key, value)
|
71
|
-
end
|
67
|
+
def write(key, value); end
|
72
68
|
end
|
73
69
|
|
74
70
|
Object.stub_const(:FakeStore, fake_store_class) do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
|
3
5
|
describe "Safelist an IP" do
|
@@ -36,8 +38,8 @@ describe "Safelist an IP" do
|
|
36
38
|
it "notifies when the request is safe" do
|
37
39
|
notification_type = nil
|
38
40
|
|
39
|
-
ActiveSupport::Notifications.subscribe("
|
40
|
-
notification_type = request.env["rack.attack.match_type"]
|
41
|
+
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
42
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
41
43
|
end
|
42
44
|
|
43
45
|
get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8"
|
@@ -1,6 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
|
3
5
|
describe "#safelist" do
|
6
|
+
before do
|
7
|
+
Rack::Attack.blocklist do |request|
|
8
|
+
request.ip == "1.2.3.4"
|
9
|
+
end
|
10
|
+
|
11
|
+
Rack::Attack.safelist do |request|
|
12
|
+
request.path == "/safe_space"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "forbids request if blocklist condition is true and safelist is false" do
|
17
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
18
|
+
|
19
|
+
assert_equal 403, last_response.status
|
20
|
+
end
|
21
|
+
|
22
|
+
it "succeeds if blocklist condition is false and safelist is false" do
|
23
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
24
|
+
|
25
|
+
assert_equal 200, last_response.status
|
26
|
+
end
|
27
|
+
|
28
|
+
it "succeeds request if blocklist condition is false and safelist is true" do
|
29
|
+
get "/safe_space", {}, "REMOTE_ADDR" => "5.6.7.8"
|
30
|
+
|
31
|
+
assert_equal 200, last_response.status
|
32
|
+
end
|
33
|
+
|
34
|
+
it "succeeds request if both blocklist and safelist conditions are true" do
|
35
|
+
get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4"
|
36
|
+
|
37
|
+
assert_equal 200, last_response.status
|
38
|
+
end
|
39
|
+
|
40
|
+
it "notifies when the request is safe" do
|
41
|
+
notification_matched = nil
|
42
|
+
notification_type = nil
|
43
|
+
|
44
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload|
|
45
|
+
notification_matched = payload[:request].env["rack.attack.matched"]
|
46
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
47
|
+
end
|
48
|
+
|
49
|
+
get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4"
|
50
|
+
|
51
|
+
assert_equal 200, last_response.status
|
52
|
+
assert_nil notification_matched
|
53
|
+
assert_equal :safelist, notification_type
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#safelist with name" do
|
4
58
|
before do
|
5
59
|
Rack::Attack.blocklist("block 1.2.3.4") do |request|
|
6
60
|
request.ip == "1.2.3.4"
|
@@ -39,9 +93,9 @@ describe "#safelist" do
|
|
39
93
|
notification_matched = nil
|
40
94
|
notification_type = nil
|
41
95
|
|
42
|
-
ActiveSupport::Notifications.subscribe("
|
43
|
-
notification_matched = request.env["rack.attack.matched"]
|
44
|
-
notification_type = request.env["rack.attack.match_type"]
|
96
|
+
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
97
|
+
notification_matched = payload[:request].env["rack.attack.matched"]
|
98
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
45
99
|
end
|
46
100
|
|
47
101
|
get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
|
3
5
|
describe "Safelisting an IP subnet" do
|
@@ -36,8 +38,8 @@ describe "Safelisting an IP subnet" do
|
|
36
38
|
it "notifies when the request is safe" do
|
37
39
|
notification_type = nil
|
38
40
|
|
39
|
-
ActiveSupport::Notifications.subscribe("
|
40
|
-
notification_type = request.env["rack.attack.match_type"]
|
41
|
+
ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload|
|
42
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
41
43
|
end
|
42
44
|
|
43
45
|
get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../../spec_helper"
|
2
4
|
|
3
5
|
if defined?(::ActiveSupport::Cache::RedisStore)
|
@@ -10,7 +12,7 @@ if defined?(::ActiveSupport::Cache::RedisStore)
|
|
10
12
|
end
|
11
13
|
|
12
14
|
after do
|
13
|
-
Rack::Attack.cache.store.
|
15
|
+
Rack::Attack.cache.store.clear
|
14
16
|
end
|
15
17
|
|
16
18
|
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.read(key) })
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
require "timecop"
|
3
5
|
|
@@ -123,11 +125,11 @@ describe "#throttle" do
|
|
123
125
|
notification_data = nil
|
124
126
|
notification_discriminator = nil
|
125
127
|
|
126
|
-
ActiveSupport::Notifications.subscribe("
|
127
|
-
notification_matched = request.env["rack.attack.matched"]
|
128
|
-
notification_type = request.env["rack.attack.match_type"]
|
129
|
-
notification_data = request.env['rack.attack.match_data']
|
130
|
-
notification_discriminator = request.env['rack.attack.match_discriminator']
|
128
|
+
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |_name, _start, _finish, _id, payload|
|
129
|
+
notification_matched = payload[:request].env["rack.attack.matched"]
|
130
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
131
|
+
notification_data = payload[:request].env['rack.attack.match_data']
|
132
|
+
notification_discriminator = payload[:request].env['rack.attack.match_discriminator']
|
131
133
|
end
|
132
134
|
|
133
135
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
|
3
5
|
describe "#track" do
|
@@ -9,9 +11,9 @@ describe "#track" do
|
|
9
11
|
notification_matched = nil
|
10
12
|
notification_type = nil
|
11
13
|
|
12
|
-
ActiveSupport::Notifications.subscribe("
|
13
|
-
notification_matched = request.env["rack.attack.matched"]
|
14
|
-
notification_type = request.env["rack.attack.match_type"]
|
14
|
+
ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload|
|
15
|
+
notification_matched = payload[:request].env["rack.attack.matched"]
|
16
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
15
17
|
end
|
16
18
|
|
17
19
|
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../spec_helper"
|
2
4
|
require "timecop"
|
3
5
|
|
@@ -12,9 +14,9 @@ describe "#track with throttle-ish options" do
|
|
12
14
|
notification_matched = nil
|
13
15
|
notification_type = nil
|
14
16
|
|
15
|
-
ActiveSupport::Notifications.subscribe("
|
16
|
-
notification_matched = request.env["rack.attack.matched"]
|
17
|
-
notification_type = request.env["rack.attack.match_type"]
|
17
|
+
ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload|
|
18
|
+
notification_matched = payload[:request].env["rack.attack.matched"]
|
19
|
+
notification_type = payload[:request].env["rack.attack.match_type"]
|
18
20
|
end
|
19
21
|
|
20
22
|
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
data/spec/allow2ban_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'spec_helper'
|
2
4
|
|
3
5
|
describe 'Rack::Attack.Allow2Ban' do
|
@@ -7,7 +9,7 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
7
9
|
@findtime = 60
|
8
10
|
@bantime = 60
|
9
11
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
10
|
-
@f2b_options = { :
|
12
|
+
@f2b_options = { bantime: @bantime, findtime: @findtime, maxretry: 2 }
|
11
13
|
|
12
14
|
Rack::Attack.blocklist('pentest') do |req|
|
13
15
|
Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options) { req.query_string =~ /OMGHAX/ }
|
data/spec/fail2ban_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'spec_helper'
|
2
4
|
|
3
5
|
describe 'Rack::Attack.Fail2Ban' do
|
@@ -7,7 +9,7 @@ describe 'Rack::Attack.Fail2Ban' do
|
|
7
9
|
@findtime = 60
|
8
10
|
@bantime = 60
|
9
11
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
10
|
-
@f2b_options = { :
|
12
|
+
@f2b_options = { bantime: @bantime, findtime: @findtime, maxretry: 2 }
|
11
13
|
|
12
14
|
Rack::Attack.blocklist('pentest') do |req|
|
13
15
|
Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options) { req.query_string =~ /OMGHAX/ }
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/cache'
|
2
4
|
require_relative '../spec_helper'
|
3
5
|
|
@@ -22,7 +24,7 @@ if defined?(::ActiveSupport::Cache::RedisStore)
|
|
22
24
|
before do
|
23
25
|
@cache = Rack::Attack::Cache.new
|
24
26
|
# Use presumably unused port for Redis client
|
25
|
-
@cache.store = ActiveSupport::Cache::RedisStore.new(:
|
27
|
+
@cache.store = ActiveSupport::Cache::RedisStore.new(host: '127.0.0.1', port: 3333)
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "spec_helper"
|
4
|
+
|
5
|
+
# ActiveSupport::Subscribers added in ~> 4.0.2.0
|
6
|
+
if ActiveSupport::VERSION::MAJOR > 3
|
7
|
+
require_relative 'spec_helper'
|
8
|
+
require 'active_support/subscriber'
|
9
|
+
class CustomSubscriber < ActiveSupport::Subscriber
|
10
|
+
@notification_count = 0
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :notification_count
|
14
|
+
end
|
15
|
+
|
16
|
+
def throttle(_event)
|
17
|
+
self.class.notification_count += 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'Rack::Attack.instrument' do
|
22
|
+
before do
|
23
|
+
@period = 60 # Use a long period; failures due to cache key rotation less likely
|
24
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
25
|
+
Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "with throttling" do
|
29
|
+
before do
|
30
|
+
ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do
|
31
|
+
CustomSubscriber.attach_to("rack_attack")
|
32
|
+
2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should instrument without error' do
|
37
|
+
last_response.status.must_equal 429
|
38
|
+
assert_equal 1, CustomSubscriber.notification_count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/rack_attack_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'spec_helper'
|
2
4
|
|
3
5
|
describe 'Rack::Attack' do
|
@@ -24,13 +26,6 @@ describe 'Rack::Attack' do
|
|
24
26
|
Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true
|
25
27
|
}
|
26
28
|
|
27
|
-
it('has a blacklist with a deprication warning') {
|
28
|
-
_, stderror = capture_io do
|
29
|
-
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
|
30
|
-
end
|
31
|
-
assert_match "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead.", stderror
|
32
|
-
}
|
33
|
-
|
34
29
|
describe "a bad request" do
|
35
30
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
|
36
31
|
|
@@ -55,13 +50,6 @@ describe 'Rack::Attack' do
|
|
55
50
|
|
56
51
|
it('has a safelist') { Rack::Attack.safelists.key?("good ua") }
|
57
52
|
|
58
|
-
it('has a whitelist with a deprication warning') {
|
59
|
-
_, stderror = capture_io do
|
60
|
-
Rack::Attack.whitelists.key?("good ua")
|
61
|
-
end
|
62
|
-
assert_match "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead.", stderror
|
63
|
-
}
|
64
|
-
|
65
53
|
describe "with a request match both safelist & blocklist" do
|
66
54
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
|
67
55
|
|
@@ -80,13 +68,6 @@ describe 'Rack::Attack' do
|
|
80
68
|
it 'should exist' do
|
81
69
|
Rack::Attack.blocklisted_response.must_respond_to :call
|
82
70
|
end
|
83
|
-
|
84
|
-
it 'should give a deprication warning for blacklisted_response' do
|
85
|
-
_, stderror = capture_io do
|
86
|
-
Rack::Attack.blacklisted_response
|
87
|
-
end
|
88
|
-
assert_match "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead.", stderror
|
89
|
-
end
|
90
71
|
end
|
91
72
|
|
92
73
|
describe '#throttled_response' do
|