rack-attack 5.0.1 → 5.4.2
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 +5 -5
- data/README.md +190 -94
- data/Rakefile +11 -4
- data/bin/setup +8 -0
- data/lib/rack/attack.rb +83 -51
- data/lib/rack/attack/allow2ban.rb +2 -1
- data/lib/rack/attack/blocklist.rb +0 -1
- data/lib/rack/attack/cache.rb +24 -5
- data/lib/rack/attack/check.rb +6 -8
- data/lib/rack/attack/fail2ban.rb +2 -1
- data/lib/rack/attack/path_normalizer.rb +6 -11
- data/lib/rack/attack/safelist.rb +0 -1
- data/lib/rack/attack/store_proxy.rb +3 -12
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +2 -3
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +4 -5
- data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +19 -0
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +35 -0
- data/lib/rack/attack/store_proxy/redis_proxy.rb +54 -0
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -24
- data/lib/rack/attack/throttle.rb +16 -12
- data/lib/rack/attack/track.rb +3 -3
- data/lib/rack/attack/version.rb +1 -1
- data/spec/acceptance/allow2ban_spec.rb +71 -0
- data/spec/acceptance/blocking_ip_spec.rb +38 -0
- data/spec/acceptance/blocking_spec.rb +41 -0
- data/spec/acceptance/blocking_subnet_spec.rb +44 -0
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +126 -0
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +121 -0
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +48 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +31 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +41 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +59 -0
- data/spec/acceptance/extending_request_object_spec.rb +34 -0
- data/spec/acceptance/fail2ban_spec.rb +76 -0
- data/spec/acceptance/safelisting_ip_spec.rb +48 -0
- data/spec/acceptance/safelisting_spec.rb +53 -0
- data/spec/acceptance/safelisting_subnet_spec.rb +48 -0
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +19 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +22 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +16 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +18 -0
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +22 -0
- data/spec/acceptance/stores/dalli_client_spec.rb +19 -0
- data/spec/acceptance/stores/redis_spec.rb +20 -0
- data/spec/acceptance/stores/redis_store_spec.rb +18 -0
- data/spec/acceptance/throttling_spec.rb +159 -0
- data/spec/acceptance/track_spec.rb +27 -0
- data/spec/acceptance/track_throttle_spec.rb +53 -0
- data/spec/allow2ban_spec.rb +9 -8
- data/spec/fail2ban_spec.rb +11 -9
- data/spec/integration/offline_spec.rb +21 -23
- data/spec/rack_attack_dalli_proxy_spec.rb +0 -2
- data/spec/rack_attack_request_spec.rb +1 -1
- data/spec/rack_attack_spec.rb +13 -14
- data/spec/rack_attack_throttle_spec.rb +28 -18
- data/spec/rack_attack_track_spec.rb +11 -8
- data/spec/spec_helper.rb +35 -14
- data/spec/support/cache_store_helper.rb +82 -0
- metadata +150 -65
- data/spec/integration/rack_attack_cache_spec.rb +0 -122
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
require_relative 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe Rack::Attack::StoreProxy::DalliProxy do
|
|
4
|
-
|
|
5
4
|
it 'should stub Dalli::Client#with on older clients' do
|
|
6
5
|
proxy = Rack::Attack::StoreProxy::DalliProxy.new(Class.new)
|
|
7
6
|
proxy.with {} # will not raise an error
|
|
8
7
|
end
|
|
9
|
-
|
|
10
8
|
end
|
data/spec/rack_attack_spec.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
require_relative 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe 'Rack::Attack' do
|
|
4
|
-
|
|
4
|
+
it_allows_ok_requests
|
|
5
5
|
|
|
6
6
|
describe 'normalizing paths' do
|
|
7
7
|
before do
|
|
8
|
-
Rack::Attack.blocklist("banned_path") {|req| req.path == '/foo' }
|
|
8
|
+
Rack::Attack.blocklist("banned_path") { |req| req.path == '/foo' }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
it 'blocks requests with trailing slash' do
|
|
@@ -17,7 +17,7 @@ describe 'Rack::Attack' do
|
|
|
17
17
|
describe 'blocklist' do
|
|
18
18
|
before do
|
|
19
19
|
@bad_ip = '1.2.3.4'
|
|
20
|
-
Rack::Attack.blocklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
|
20
|
+
Rack::Attack.blocklist("ip #{@bad_ip}") { |req| req.ip == @bad_ip }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it('has a blocklist') {
|
|
@@ -25,7 +25,7 @@ describe 'Rack::Attack' do
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
it('has a blacklist with a deprication warning') {
|
|
28
|
-
_, stderror
|
|
28
|
+
_, stderror = capture_io do
|
|
29
29
|
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
|
|
30
30
|
end
|
|
31
31
|
assert_match "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead.", stderror
|
|
@@ -33,29 +33,30 @@ describe 'Rack::Attack' do
|
|
|
33
33
|
|
|
34
34
|
describe "a bad request" do
|
|
35
35
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
|
|
36
|
+
|
|
36
37
|
it "should return a blocklist response" do
|
|
37
|
-
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
|
38
38
|
last_response.status.must_equal 403
|
|
39
39
|
last_response.body.must_equal "Forbidden\n"
|
|
40
40
|
end
|
|
41
|
+
|
|
41
42
|
it "should tag the env" do
|
|
42
43
|
last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
|
|
43
44
|
last_request.env['rack.attack.match_type'].must_equal :blocklist
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
it_allows_ok_requests
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
describe "and safelist" do
|
|
50
51
|
before do
|
|
51
52
|
@good_ua = 'GoodUA'
|
|
52
|
-
Rack::Attack.safelist("good ua") {|req| req.user_agent == @good_ua }
|
|
53
|
+
Rack::Attack.safelist("good ua") { |req| req.user_agent == @good_ua }
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
it('has a safelist'){ Rack::Attack.safelists.key?("good ua") }
|
|
56
|
+
it('has a safelist') { Rack::Attack.safelists.key?("good ua") }
|
|
56
57
|
|
|
57
58
|
it('has a whitelist with a deprication warning') {
|
|
58
|
-
_, stderror
|
|
59
|
+
_, stderror = capture_io do
|
|
59
60
|
Rack::Attack.whitelists.key?("good ua")
|
|
60
61
|
end
|
|
61
62
|
assert_match "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead.", stderror
|
|
@@ -63,10 +64,11 @@ describe 'Rack::Attack' do
|
|
|
63
64
|
|
|
64
65
|
describe "with a request match both safelist & blocklist" do
|
|
65
66
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
|
|
67
|
+
|
|
66
68
|
it "should allow safelists before blocklists" do
|
|
67
|
-
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
|
68
69
|
last_response.status.must_equal 200
|
|
69
70
|
end
|
|
71
|
+
|
|
70
72
|
it "should tag the env" do
|
|
71
73
|
last_request.env['rack.attack.matched'].must_equal 'good ua'
|
|
72
74
|
last_request.env['rack.attack.match_type'].must_equal :safelist
|
|
@@ -80,11 +82,10 @@ describe 'Rack::Attack' do
|
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
it 'should give a deprication warning for blacklisted_response' do
|
|
83
|
-
_, stderror
|
|
85
|
+
_, stderror = capture_io do
|
|
84
86
|
Rack::Attack.blacklisted_response
|
|
85
87
|
end
|
|
86
88
|
assert_match "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead.", stderror
|
|
87
|
-
|
|
88
89
|
end
|
|
89
90
|
end
|
|
90
91
|
|
|
@@ -93,7 +94,5 @@ describe 'Rack::Attack' do
|
|
|
93
94
|
Rack::Attack.throttled_response.must_respond_to :call
|
|
94
95
|
end
|
|
95
96
|
end
|
|
96
|
-
|
|
97
97
|
end
|
|
98
|
-
|
|
99
98
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative 'spec_helper'
|
|
2
|
+
|
|
2
3
|
describe 'Rack::Attack.throttle' do
|
|
3
4
|
before do
|
|
4
5
|
@period = 60 # Use a long period; failures due to cache key rotation less likely
|
|
@@ -6,34 +7,40 @@ describe 'Rack::Attack.throttle' do
|
|
|
6
7
|
Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |req| req.ip }
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
it('should have a throttle'){ Rack::Attack.throttles.key?('ip/sec') }
|
|
10
|
-
|
|
10
|
+
it('should have a throttle') { Rack::Attack.throttles.key?('ip/sec') }
|
|
11
|
+
|
|
12
|
+
it_allows_ok_requests
|
|
11
13
|
|
|
12
14
|
describe 'a single request' do
|
|
13
15
|
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
16
|
+
|
|
14
17
|
it 'should set the counter for one request' do
|
|
15
|
-
key = "rack::attack:#{Time.now.to_i
|
|
18
|
+
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
|
|
16
19
|
Rack::Attack.cache.store.read(key).must_equal 1
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
it 'should populate throttle data' do
|
|
20
|
-
data = { :count => 1, :limit => 1, :period => @period }
|
|
23
|
+
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
|
|
21
24
|
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
|
|
22
25
|
end
|
|
23
26
|
end
|
|
27
|
+
|
|
24
28
|
describe "with 2 requests" do
|
|
25
29
|
before do
|
|
26
30
|
2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
27
31
|
end
|
|
32
|
+
|
|
28
33
|
it 'should block the last request' do
|
|
29
34
|
last_response.status.must_equal 429
|
|
30
35
|
end
|
|
36
|
+
|
|
31
37
|
it 'should tag the env' do
|
|
32
38
|
last_request.env['rack.attack.matched'].must_equal 'ip/sec'
|
|
33
39
|
last_request.env['rack.attack.match_type'].must_equal :throttle
|
|
34
|
-
last_request.env['rack.attack.match_data'].must_equal(
|
|
40
|
+
last_request.env['rack.attack.match_data'].must_equal(:count => 2, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i)
|
|
35
41
|
last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
|
|
36
42
|
end
|
|
43
|
+
|
|
37
44
|
it 'should set a Retry-After header' do
|
|
38
45
|
last_response.headers['Retry-After'].must_equal @period.to_s
|
|
39
46
|
end
|
|
@@ -44,20 +51,21 @@ describe 'Rack::Attack.throttle with limit as proc' do
|
|
|
44
51
|
before do
|
|
45
52
|
@period = 60 # Use a long period; failures due to cache key rotation less likely
|
|
46
53
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
47
|
-
Rack::Attack.throttle('ip/sec', :limit => lambda { |
|
|
54
|
+
Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => @period) { |req| req.ip }
|
|
48
55
|
end
|
|
49
56
|
|
|
50
|
-
|
|
57
|
+
it_allows_ok_requests
|
|
51
58
|
|
|
52
59
|
describe 'a single request' do
|
|
53
60
|
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
61
|
+
|
|
54
62
|
it 'should set the counter for one request' do
|
|
55
|
-
key = "rack::attack:#{Time.now.to_i
|
|
63
|
+
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
|
|
56
64
|
Rack::Attack.cache.store.read(key).must_equal 1
|
|
57
65
|
end
|
|
58
66
|
|
|
59
67
|
it 'should populate throttle data' do
|
|
60
|
-
data = { :count => 1, :limit => 1, :period => @period }
|
|
68
|
+
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
|
|
61
69
|
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
|
|
62
70
|
end
|
|
63
71
|
end
|
|
@@ -67,20 +75,21 @@ describe 'Rack::Attack.throttle with period as proc' do
|
|
|
67
75
|
before do
|
|
68
76
|
@period = 60 # Use a long period; failures due to cache key rotation less likely
|
|
69
77
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
70
|
-
Rack::Attack.throttle('ip/sec', :limit => lambda { |
|
|
78
|
+
Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => lambda { |_req| @period }) { |req| req.ip }
|
|
71
79
|
end
|
|
72
80
|
|
|
73
|
-
|
|
81
|
+
it_allows_ok_requests
|
|
74
82
|
|
|
75
83
|
describe 'a single request' do
|
|
76
84
|
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
85
|
+
|
|
77
86
|
it 'should set the counter for one request' do
|
|
78
|
-
key = "rack::attack:#{Time.now.to_i
|
|
87
|
+
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
|
|
79
88
|
Rack::Attack.cache.store.read(key).must_equal 1
|
|
80
89
|
end
|
|
81
90
|
|
|
82
91
|
it 'should populate throttle data' do
|
|
83
|
-
data = { :count => 1, :limit => 1, :period => @period }
|
|
92
|
+
data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
|
|
84
93
|
last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
|
|
85
94
|
end
|
|
86
95
|
end
|
|
@@ -93,17 +102,18 @@ describe 'Rack::Attack.throttle with block retuning nil' do
|
|
|
93
102
|
Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |_| nil }
|
|
94
103
|
end
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
it_allows_ok_requests
|
|
97
106
|
|
|
98
107
|
describe 'a single request' do
|
|
99
108
|
before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
109
|
+
|
|
100
110
|
it 'should not set the counter' do
|
|
101
|
-
key = "rack::attack:#{Time.now.to_i
|
|
102
|
-
Rack::Attack.cache.store.read(key)
|
|
111
|
+
key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
|
|
112
|
+
assert_nil Rack::Attack.cache.store.read(key)
|
|
103
113
|
end
|
|
104
114
|
|
|
105
115
|
it 'should not populate throttle data' do
|
|
106
|
-
last_request.env['rack.attack.throttle_data']
|
|
116
|
+
assert_nil last_request.env['rack.attack.throttle_data']
|
|
107
117
|
end
|
|
108
118
|
end
|
|
109
|
-
end
|
|
119
|
+
end
|
|
@@ -16,9 +16,11 @@ describe 'Rack::Attack.track' do
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
before do
|
|
19
|
-
Rack::Attack.track("everything"){ |
|
|
19
|
+
Rack::Attack.track("everything") { |_req| true }
|
|
20
20
|
end
|
|
21
|
-
|
|
21
|
+
|
|
22
|
+
it_allows_ok_requests
|
|
23
|
+
|
|
22
24
|
it "should tag the env" do
|
|
23
25
|
get '/'
|
|
24
26
|
last_request.env['rack.attack.matched'].must_equal 'everything'
|
|
@@ -29,11 +31,12 @@ describe 'Rack::Attack.track' do
|
|
|
29
31
|
before do
|
|
30
32
|
Counter.reset
|
|
31
33
|
# A second track
|
|
32
|
-
Rack::Attack.track("homepage"){ |req| req.path == "/"}
|
|
34
|
+
Rack::Attack.track("homepage") { |req| req.path == "/" }
|
|
33
35
|
|
|
34
|
-
ActiveSupport::Notifications.subscribe("rack.attack") do |*
|
|
36
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |*_args|
|
|
35
37
|
Counter.incr
|
|
36
38
|
end
|
|
39
|
+
|
|
37
40
|
get "/"
|
|
38
41
|
end
|
|
39
42
|
|
|
@@ -44,15 +47,15 @@ describe 'Rack::Attack.track' do
|
|
|
44
47
|
|
|
45
48
|
describe "without limit and period options" do
|
|
46
49
|
it "should assign the track filter to a Check instance" do
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
track = Rack::Attack.track("homepage") { |req| req.path == "/" }
|
|
51
|
+
track.filter.class.must_equal Rack::Attack::Check
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
describe "with limit and period options" do
|
|
53
56
|
it "should assign the track filter to a Throttle instance" do
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
track = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/" }
|
|
58
|
+
track.filter.class.must_equal Rack::Attack::Throttle
|
|
56
59
|
end
|
|
57
60
|
end
|
|
58
61
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require "rubygems"
|
|
2
1
|
require "bundler/setup"
|
|
3
2
|
|
|
4
3
|
require "minitest/autorun"
|
|
@@ -7,31 +6,53 @@ require "rack/test"
|
|
|
7
6
|
require 'active_support'
|
|
8
7
|
require 'action_dispatch'
|
|
9
8
|
|
|
10
|
-
# Load Journey for Rails 3.2
|
|
11
|
-
require 'journey' if ActionPack::VERSION::MAJOR == 3
|
|
12
|
-
|
|
13
9
|
require "rack/attack"
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
require
|
|
17
|
-
rescue LoadError
|
|
18
|
-
#nothing to do here
|
|
11
|
+
if RUBY_ENGINE == "ruby"
|
|
12
|
+
require "byebug"
|
|
19
13
|
end
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
def safe_require(name)
|
|
16
|
+
begin
|
|
17
|
+
require name
|
|
18
|
+
rescue LoadError
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
safe_require "connection_pool"
|
|
23
|
+
safe_require "dalli"
|
|
24
|
+
safe_require "redis"
|
|
25
|
+
safe_require "redis-activesupport"
|
|
26
|
+
safe_require "redis-store"
|
|
22
27
|
|
|
28
|
+
class MiniTest::Spec
|
|
23
29
|
include Rack::Test::Methods
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
before do
|
|
32
|
+
@_original_throttled_response = Rack::Attack.throttled_response
|
|
33
|
+
@_original_blocklisted_response = Rack::Attack.blocklisted_response
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
after do
|
|
37
|
+
Rack::Attack.clear_configuration
|
|
38
|
+
Rack::Attack.instance_variable_set(:@cache, nil)
|
|
39
|
+
|
|
40
|
+
Rack::Attack.throttled_response = @_original_throttled_response
|
|
41
|
+
Rack::Attack.blocklisted_response = @_original_blocklisted_response
|
|
42
|
+
end
|
|
26
43
|
|
|
27
44
|
def app
|
|
28
|
-
Rack::Builder.new
|
|
45
|
+
Rack::Builder.new do
|
|
46
|
+
# Use Rack::Lint to test that rack-attack is complying with the rack spec
|
|
47
|
+
use Rack::Lint
|
|
29
48
|
use Rack::Attack
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
use Rack::Lint
|
|
50
|
+
|
|
51
|
+
run lambda { |_env| [200, {}, ['Hello World']] }
|
|
52
|
+
end.to_app
|
|
32
53
|
end
|
|
33
54
|
|
|
34
|
-
def self.
|
|
55
|
+
def self.it_allows_ok_requests
|
|
35
56
|
it "must allow ok requests" do
|
|
36
57
|
get '/', {}, 'REMOTE_ADDR' => '127.0.0.1'
|
|
37
58
|
last_response.status.must_equal 200
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class Minitest::Spec
|
|
2
|
+
def self.it_works_for_cache_backed_features(options)
|
|
3
|
+
fetch_from_store = options.fetch(:fetch_from_store)
|
|
4
|
+
|
|
5
|
+
it "works for throttle" do
|
|
6
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
|
|
7
|
+
request.ip
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
11
|
+
assert_equal 200, last_response.status
|
|
12
|
+
|
|
13
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
14
|
+
assert_equal 429, last_response.status
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "works for fail2ban" do
|
|
18
|
+
Rack::Attack.blocklist("fail2ban pentesters") do |request|
|
|
19
|
+
Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do
|
|
20
|
+
request.path.include?("private-place")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
get "/"
|
|
25
|
+
assert_equal 200, last_response.status
|
|
26
|
+
|
|
27
|
+
get "/private-place"
|
|
28
|
+
assert_equal 403, last_response.status
|
|
29
|
+
|
|
30
|
+
get "/private-place"
|
|
31
|
+
assert_equal 403, last_response.status
|
|
32
|
+
|
|
33
|
+
get "/"
|
|
34
|
+
assert_equal 403, last_response.status
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "works for allow2ban" do
|
|
38
|
+
Rack::Attack.blocklist("allow2ban pentesters") do |request|
|
|
39
|
+
Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do
|
|
40
|
+
request.path.include?("scarce-resource")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
get "/"
|
|
45
|
+
assert_equal 200, last_response.status
|
|
46
|
+
|
|
47
|
+
get "/scarce-resource"
|
|
48
|
+
assert_equal 200, last_response.status
|
|
49
|
+
|
|
50
|
+
get "/scarce-resource"
|
|
51
|
+
assert_equal 200, last_response.status
|
|
52
|
+
|
|
53
|
+
get "/scarce-resource"
|
|
54
|
+
assert_equal 403, last_response.status
|
|
55
|
+
|
|
56
|
+
get "/"
|
|
57
|
+
assert_equal 403, last_response.status
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "doesn't leak keys" do
|
|
61
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request|
|
|
62
|
+
request.ip
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
key = nil
|
|
66
|
+
|
|
67
|
+
# Freeze time during these statement to be sure that the key used by rack attack is the same
|
|
68
|
+
# we pre-calculate in local variable `key`
|
|
69
|
+
Timecop.freeze do
|
|
70
|
+
key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4"
|
|
71
|
+
|
|
72
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
assert fetch_from_store.call(key)
|
|
76
|
+
|
|
77
|
+
sleep 2.1
|
|
78
|
+
|
|
79
|
+
assert_nil fetch_from_store.call(key)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|