rack-attack 4.3.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 +230 -113
- data/Rakefile +11 -3
- data/bin/setup +8 -0
- data/lib/rack/attack.rb +121 -48
- data/lib/rack/attack/allow2ban.rb +2 -1
- data/lib/rack/attack/{whitelist.rb → blocklist.rb} +2 -3
- data/lib/rack/attack/cache.rb +24 -5
- data/lib/rack/attack/check.rb +6 -8
- data/lib/rack/attack/fail2ban.rb +3 -2
- data/lib/rack/attack/path_normalizer.rb +6 -11
- data/lib/rack/attack/request.rb +1 -1
- data/lib/rack/attack/{blacklist.rb → safelist.rb} +2 -4
- data/lib/rack/attack/store_proxy.rb +13 -12
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +2 -3
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +50 -0
- 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 +10 -9
- data/spec/fail2ban_spec.rb +12 -10
- 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 +2 -2
- data/spec/rack_attack_spec.rb +53 -18
- data/spec/rack_attack_throttle_spec.rb +45 -13
- 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 +161 -61
- data/spec/integration/rack_attack_cache_spec.rb +0 -119
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(::ConnectionPool) && defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
|
|
4
|
+
require_relative "../../support/cache_store_helper"
|
|
5
|
+
require "timecop"
|
|
6
|
+
|
|
7
|
+
describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do
|
|
8
|
+
before do
|
|
9
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rack::Attack.cache.store.clear
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
|
|
4
|
+
require_relative "../../support/cache_store_helper"
|
|
5
|
+
require "timecop"
|
|
6
|
+
|
|
7
|
+
describe "ActiveSupport::Cache::RedisCacheStore as a cache backend" do
|
|
8
|
+
before do
|
|
9
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rack::Attack.cache.store.clear
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(::ActiveSupport::Cache::RedisStore)
|
|
4
|
+
require_relative "../../support/cache_store_helper"
|
|
5
|
+
require "timecop"
|
|
6
|
+
|
|
7
|
+
describe "ActiveSupport::Cache::RedisStore as a cache backend" do
|
|
8
|
+
before do
|
|
9
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::RedisStore.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rack::Attack.cache.store.flushdb
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.read(key) })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(::Dalli) && defined?(::ConnectionPool)
|
|
4
|
+
require_relative "../../support/cache_store_helper"
|
|
5
|
+
require "connection_pool"
|
|
6
|
+
require "dalli"
|
|
7
|
+
require "timecop"
|
|
8
|
+
|
|
9
|
+
describe "ConnectionPool with Dalli::Client as a cache backend" do
|
|
10
|
+
before do
|
|
11
|
+
Rack::Attack.cache.store = ConnectionPool.new { Dalli::Client.new }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
Rack::Attack.cache.store.with { |client| client.flush_all }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) {
|
|
19
|
+
Rack::Attack.cache.store.with { |client| client.fetch(key) }
|
|
20
|
+
})
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
|
|
3
|
+
if defined?(::Dalli)
|
|
4
|
+
require_relative "../../support/cache_store_helper"
|
|
5
|
+
require "dalli"
|
|
6
|
+
require "timecop"
|
|
7
|
+
|
|
8
|
+
describe "Dalli::Client as a cache backend" do
|
|
9
|
+
before do
|
|
10
|
+
Rack::Attack.cache.store = Dalli::Client.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after do
|
|
14
|
+
Rack::Attack.cache.store.flush_all
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) })
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../spec_helper"
|
|
4
|
+
|
|
5
|
+
if defined?(::Redis)
|
|
6
|
+
require_relative "../../support/cache_store_helper"
|
|
7
|
+
require "timecop"
|
|
8
|
+
|
|
9
|
+
describe "Plain redis as a cache backend" do
|
|
10
|
+
before do
|
|
11
|
+
Rack::Attack.cache.store = Redis.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
Rack::Attack.cache.store.flushdb
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.get(key) })
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative "../../spec_helper"
|
|
2
|
+
require_relative "../../support/cache_store_helper"
|
|
3
|
+
|
|
4
|
+
if defined?(::Redis::Store)
|
|
5
|
+
require "timecop"
|
|
6
|
+
|
|
7
|
+
describe "ActiveSupport::Cache::RedisStore as a cache backend" do
|
|
8
|
+
before do
|
|
9
|
+
Rack::Attack.cache.store = ::Redis::Store.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after do
|
|
13
|
+
Rack::Attack.cache.store.flushdb
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.read(key) })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "timecop"
|
|
3
|
+
|
|
4
|
+
describe "#throttle" do
|
|
5
|
+
before do
|
|
6
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "allows one request per minute by IP" do
|
|
10
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
|
|
11
|
+
request.ip
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
15
|
+
|
|
16
|
+
assert_equal 200, last_response.status
|
|
17
|
+
|
|
18
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
19
|
+
|
|
20
|
+
assert_equal 429, last_response.status
|
|
21
|
+
assert_equal "60", last_response.headers["Retry-After"]
|
|
22
|
+
assert_equal "Retry later\n", last_response.body
|
|
23
|
+
|
|
24
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
25
|
+
|
|
26
|
+
assert_equal 200, last_response.status
|
|
27
|
+
|
|
28
|
+
Timecop.travel(60) do
|
|
29
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
30
|
+
|
|
31
|
+
assert_equal 200, last_response.status
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "supports limit to be dynamic" do
|
|
36
|
+
# Could be used to have different rate limits for authorized
|
|
37
|
+
# vs general requests
|
|
38
|
+
limit_proc = lambda do |request|
|
|
39
|
+
if request.env["X-APIKey"] == "private-secret"
|
|
40
|
+
2
|
|
41
|
+
else
|
|
42
|
+
1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Rack::Attack.throttle("by ip", limit: limit_proc, period: 60) do |request|
|
|
47
|
+
request.ip
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
51
|
+
assert_equal 200, last_response.status
|
|
52
|
+
|
|
53
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
54
|
+
assert_equal 429, last_response.status
|
|
55
|
+
|
|
56
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
57
|
+
assert_equal 200, last_response.status
|
|
58
|
+
|
|
59
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
60
|
+
assert_equal 200, last_response.status
|
|
61
|
+
|
|
62
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
63
|
+
assert_equal 429, last_response.status
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "supports period to be dynamic" do
|
|
67
|
+
# Could be used to have different rate limits for authorized
|
|
68
|
+
# vs general requests
|
|
69
|
+
period_proc = lambda do |request|
|
|
70
|
+
if request.env["X-APIKey"] == "private-secret"
|
|
71
|
+
10
|
|
72
|
+
else
|
|
73
|
+
30
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Rack::Attack.throttle("by ip", limit: 1, period: period_proc) do |request|
|
|
78
|
+
request.ip
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Using Time#at to align to start/end of periods exactly
|
|
82
|
+
# to achieve consistenty in different test runs
|
|
83
|
+
|
|
84
|
+
Timecop.travel(Time.at(0)) do
|
|
85
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
86
|
+
assert_equal 200, last_response.status
|
|
87
|
+
|
|
88
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
89
|
+
assert_equal 429, last_response.status
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Timecop.travel(Time.at(10)) do
|
|
93
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
94
|
+
assert_equal 429, last_response.status
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Timecop.travel(Time.at(30)) do
|
|
98
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
99
|
+
assert_equal 200, last_response.status
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
Timecop.travel(Time.at(0)) do
|
|
103
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
104
|
+
assert_equal 200, last_response.status
|
|
105
|
+
|
|
106
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
107
|
+
assert_equal 429, last_response.status
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Timecop.travel(Time.at(10)) do
|
|
111
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret"
|
|
112
|
+
assert_equal 200, last_response.status
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "notifies when the request is throttled" do
|
|
117
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
|
|
118
|
+
request.ip
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
notification_matched = nil
|
|
122
|
+
notification_type = nil
|
|
123
|
+
notification_data = nil
|
|
124
|
+
notification_discriminator = nil
|
|
125
|
+
|
|
126
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
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']
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
134
|
+
|
|
135
|
+
assert_equal 200, last_response.status
|
|
136
|
+
assert_nil notification_matched
|
|
137
|
+
assert_nil notification_type
|
|
138
|
+
assert_nil notification_data
|
|
139
|
+
assert_nil notification_discriminator
|
|
140
|
+
|
|
141
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
142
|
+
|
|
143
|
+
assert_equal 200, last_response.status
|
|
144
|
+
assert_nil notification_matched
|
|
145
|
+
assert_nil notification_type
|
|
146
|
+
assert_nil notification_data
|
|
147
|
+
assert_nil notification_discriminator
|
|
148
|
+
|
|
149
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
150
|
+
|
|
151
|
+
assert_equal 429, last_response.status
|
|
152
|
+
assert_equal "by ip", notification_matched
|
|
153
|
+
assert_equal :throttle, notification_type
|
|
154
|
+
assert_equal 60, notification_data[:period]
|
|
155
|
+
assert_equal 1, notification_data[:limit]
|
|
156
|
+
assert_equal 2, notification_data[:count]
|
|
157
|
+
assert_equal "1.2.3.4", notification_discriminator
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "#track" do
|
|
4
|
+
it "notifies when track block returns true" do
|
|
5
|
+
Rack::Attack.track("ip 1.2.3.4") do |request|
|
|
6
|
+
request.ip == "1.2.3.4"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
notification_matched = nil
|
|
10
|
+
notification_type = nil
|
|
11
|
+
|
|
12
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
13
|
+
notification_matched = request.env["rack.attack.matched"]
|
|
14
|
+
notification_type = request.env["rack.attack.match_type"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
18
|
+
|
|
19
|
+
assert_nil notification_matched
|
|
20
|
+
assert_nil notification_type
|
|
21
|
+
|
|
22
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
23
|
+
|
|
24
|
+
assert_equal "ip 1.2.3.4", notification_matched
|
|
25
|
+
assert_equal :track, notification_type
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "timecop"
|
|
3
|
+
|
|
4
|
+
describe "#track with throttle-ish options" do
|
|
5
|
+
it "notifies when throttle goes over the limit without actually throttling requests" do
|
|
6
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
7
|
+
|
|
8
|
+
Rack::Attack.track("by ip", limit: 1, period: 60) do |request|
|
|
9
|
+
request.ip
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
notification_matched = nil
|
|
13
|
+
notification_type = nil
|
|
14
|
+
|
|
15
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
16
|
+
notification_matched = request.env["rack.attack.matched"]
|
|
17
|
+
notification_type = request.env["rack.attack.match_type"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
21
|
+
|
|
22
|
+
assert_nil notification_matched
|
|
23
|
+
assert_nil notification_type
|
|
24
|
+
|
|
25
|
+
assert_equal 200, last_response.status
|
|
26
|
+
|
|
27
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
28
|
+
|
|
29
|
+
assert_nil notification_matched
|
|
30
|
+
assert_nil notification_type
|
|
31
|
+
|
|
32
|
+
assert_equal 200, last_response.status
|
|
33
|
+
|
|
34
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
35
|
+
|
|
36
|
+
assert_equal "by ip", notification_matched
|
|
37
|
+
assert_equal :track, notification_type
|
|
38
|
+
|
|
39
|
+
assert_equal 200, last_response.status
|
|
40
|
+
|
|
41
|
+
Timecop.travel(60) do
|
|
42
|
+
notification_matched = nil
|
|
43
|
+
notification_type = nil
|
|
44
|
+
|
|
45
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
46
|
+
|
|
47
|
+
assert_nil notification_matched
|
|
48
|
+
assert_nil notification_type
|
|
49
|
+
|
|
50
|
+
assert_equal 200, last_response.status
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/spec/allow2ban_spec.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative 'spec_helper'
|
|
2
|
+
|
|
2
3
|
describe 'Rack::Attack.Allow2Ban' do
|
|
3
4
|
before do
|
|
4
5
|
# Use a long findtime; failures due to cache key rotation less likely
|
|
@@ -6,9 +7,10 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
6
7
|
@findtime = 60
|
|
7
8
|
@bantime = 60
|
|
8
9
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
|
9
|
-
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
@f2b_options = { :bantime => @bantime, :findtime => @findtime, :maxretry => 2 }
|
|
11
|
+
|
|
12
|
+
Rack::Attack.blocklist('pentest') do |req|
|
|
13
|
+
Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options) { req.query_string =~ /OMGHAX/ }
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
@@ -23,12 +25,13 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
23
25
|
describe 'making qualifying request' do
|
|
24
26
|
describe 'when not at maxretry' do
|
|
25
27
|
before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
|
|
28
|
+
|
|
26
29
|
it 'succeeds' do
|
|
27
30
|
last_response.status.must_equal 200
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
it 'increases fail count' do
|
|
31
|
-
key = "rack::attack:#{Time.now.to_i
|
|
34
|
+
key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
|
|
32
35
|
@cache.store.read(key).must_equal 1
|
|
33
36
|
end
|
|
34
37
|
|
|
@@ -50,7 +53,7 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
it 'increases fail count' do
|
|
53
|
-
key = "rack::attack:#{Time.now.to_i
|
|
56
|
+
key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
|
|
54
57
|
@cache.store.read(key).must_equal 2
|
|
55
58
|
end
|
|
56
59
|
|
|
@@ -58,7 +61,6 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
58
61
|
key = "rack::attack:allow2ban:ban:1.2.3.4"
|
|
59
62
|
@cache.store.read(key).must_equal 1
|
|
60
63
|
end
|
|
61
|
-
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -87,7 +89,7 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
it 'does not increase fail count' do
|
|
90
|
-
key = "rack::attack:#{Time.now.to_i
|
|
92
|
+
key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
|
|
91
93
|
@cache.store.read(key).must_equal 2
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -107,7 +109,7 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
it 'does not increase fail count' do
|
|
110
|
-
key = "rack::attack:#{Time.now.to_i
|
|
112
|
+
key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
|
|
111
113
|
@cache.store.read(key).must_equal 2
|
|
112
114
|
end
|
|
113
115
|
|
|
@@ -116,6 +118,5 @@ describe 'Rack::Attack.Allow2Ban' do
|
|
|
116
118
|
@cache.store.read(key).must_equal 1
|
|
117
119
|
end
|
|
118
120
|
end
|
|
119
|
-
|
|
120
121
|
end
|
|
121
122
|
end
|