rack-attack 5.4.2 → 6.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +78 -27
  3. data/Rakefile +3 -1
  4. data/lib/rack/attack.rb +138 -149
  5. data/lib/rack/attack/allow2ban.rb +2 -0
  6. data/lib/rack/attack/blocklist.rb +3 -1
  7. data/lib/rack/attack/cache.rb +9 -4
  8. data/lib/rack/attack/check.rb +5 -2
  9. data/lib/rack/attack/fail2ban.rb +2 -0
  10. data/lib/rack/attack/path_normalizer.rb +22 -18
  11. data/lib/rack/attack/railtie.rb +13 -0
  12. data/lib/rack/attack/request.rb +2 -0
  13. data/lib/rack/attack/safelist.rb +3 -1
  14. data/lib/rack/attack/store_proxy.rb +12 -14
  15. data/lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb +39 -0
  16. data/lib/rack/attack/store_proxy/dalli_proxy.rb +27 -13
  17. data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +3 -1
  18. data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +22 -8
  19. data/lib/rack/attack/store_proxy/redis_proxy.rb +16 -14
  20. data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -5
  21. data/lib/rack/attack/throttle.rb +12 -8
  22. data/lib/rack/attack/track.rb +9 -6
  23. data/lib/rack/attack/version.rb +3 -1
  24. data/spec/acceptance/allow2ban_spec.rb +2 -0
  25. data/spec/acceptance/blocking_ip_spec.rb +4 -2
  26. data/spec/acceptance/blocking_spec.rb +45 -3
  27. data/spec/acceptance/blocking_subnet_spec.rb +4 -2
  28. data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +8 -12
  29. data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +8 -12
  30. data/spec/acceptance/cache_store_config_for_throttle_spec.rb +2 -0
  31. data/spec/acceptance/cache_store_config_with_rails_spec.rb +2 -0
  32. data/spec/acceptance/customizing_blocked_response_spec.rb +2 -0
  33. data/spec/acceptance/customizing_throttled_response_spec.rb +2 -0
  34. data/spec/acceptance/extending_request_object_spec.rb +2 -0
  35. data/spec/acceptance/fail2ban_spec.rb +2 -0
  36. data/spec/acceptance/rails_middleware_spec.rb +35 -0
  37. data/spec/acceptance/safelisting_ip_spec.rb +4 -2
  38. data/spec/acceptance/safelisting_spec.rb +57 -3
  39. data/spec/acceptance/safelisting_subnet_spec.rb +4 -2
  40. data/spec/acceptance/stores/active_support_dalli_store_spec.rb +2 -0
  41. data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +1 -3
  42. data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +2 -0
  43. data/spec/acceptance/stores/active_support_memory_store_spec.rb +2 -0
  44. data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +9 -1
  45. data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +8 -1
  46. data/spec/acceptance/stores/active_support_redis_store_spec.rb +3 -1
  47. data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +5 -3
  48. data/spec/acceptance/stores/dalli_client_spec.rb +2 -0
  49. data/spec/acceptance/stores/redis_store_spec.rb +2 -0
  50. data/spec/acceptance/throttling_spec.rb +7 -5
  51. data/spec/acceptance/track_spec.rb +5 -3
  52. data/spec/acceptance/track_throttle_spec.rb +5 -3
  53. data/spec/allow2ban_spec.rb +20 -15
  54. data/spec/fail2ban_spec.rb +20 -17
  55. data/spec/integration/offline_spec.rb +15 -1
  56. data/spec/rack_attack_dalli_proxy_spec.rb +2 -0
  57. data/spec/rack_attack_instrumentation_spec.rb +42 -0
  58. data/spec/rack_attack_path_normalizer_spec.rb +4 -2
  59. data/spec/rack_attack_request_spec.rb +2 -0
  60. data/spec/rack_attack_spec.rb +38 -34
  61. data/spec/rack_attack_throttle_spec.rb +50 -19
  62. data/spec/rack_attack_track_spec.rb +12 -7
  63. data/spec/spec_helper.rb +12 -8
  64. data/spec/support/cache_store_helper.rb +2 -0
  65. metadata +71 -56
  66. data/bin/setup +0 -8
  67. 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
 
3
5
  if defined?(::Dalli)
@@ -15,8 +15,6 @@ if defined?(::ConnectionPool) && defined?(::Dalli)
15
15
  Rack::Attack.cache.store.clear
16
16
  end
17
17
 
18
- it_works_for_cache_backed_features(fetch_from_store: ->(key) {
19
- Rack::Attack.cache.store.read(key)
20
- })
18
+ it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.read(key) })
21
19
  end
22
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
 
3
5
  if defined?(::Dalli)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
  require_relative "../../support/cache_store_helper"
3
5
 
@@ -1,6 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
 
3
- if defined?(::ConnectionPool) && defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
5
+ should_run =
6
+ defined?(::ConnectionPool) &&
7
+ defined?(::Redis) &&
8
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") &&
9
+ defined?(::ActiveSupport::Cache::RedisCacheStore)
10
+
11
+ if should_run
4
12
  require_relative "../../support/cache_store_helper"
5
13
  require "timecop"
6
14
 
@@ -1,6 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
 
3
- if defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
5
+ should_run =
6
+ defined?(::Redis) &&
7
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") &&
8
+ defined?(::ActiveSupport::Cache::RedisCacheStore)
9
+
10
+ if should_run
4
11
  require_relative "../../support/cache_store_helper"
5
12
  require "timecop"
6
13
 
@@ -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.flushdb
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
 
3
5
  if defined?(::Dalli) && defined?(::ConnectionPool)
@@ -15,8 +17,8 @@ if defined?(::Dalli) && defined?(::ConnectionPool)
15
17
  Rack::Attack.cache.store.with { |client| client.flush_all }
16
18
  end
17
19
 
18
- it_works_for_cache_backed_features(fetch_from_store: ->(key) {
19
- Rack::Attack.cache.store.with { |client| client.fetch(key) }
20
- })
20
+ it_works_for_cache_backed_features(
21
+ fetch_from_store: ->(key) { Rack::Attack.cache.store.with { |client| client.fetch(key) } }
22
+ )
21
23
  end
22
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
 
3
5
  if defined?(::Dalli)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../../spec_helper"
2
4
  require_relative "../../support/cache_store_helper"
3
5
 
@@ -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("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']
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("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"]
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("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"]
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"
@@ -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 = { :bantime => @bantime, :findtime => @findtime, :maxretry => 2 }
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/ }
@@ -18,7 +20,8 @@ describe 'Rack::Attack.Allow2Ban' do
18
20
  describe 'making ok request' do
19
21
  it 'succeeds' do
20
22
  get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
21
- last_response.status.must_equal 200
23
+
24
+ _(last_response.status).must_equal 200
22
25
  end
23
26
  end
24
27
 
@@ -27,17 +30,18 @@ describe 'Rack::Attack.Allow2Ban' do
27
30
  before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
28
31
 
29
32
  it 'succeeds' do
30
- last_response.status.must_equal 200
33
+ _(last_response.status).must_equal 200
31
34
  end
32
35
 
33
36
  it 'increases fail count' do
34
37
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
35
- @cache.store.read(key).must_equal 1
38
+
39
+ _(@cache.store.read(key)).must_equal 1
36
40
  end
37
41
 
38
42
  it 'is not banned' do
39
43
  key = "rack::attack:allow2ban:1.2.3.4"
40
- @cache.store.read(key).must_be_nil
44
+ _(@cache.store.read(key)).must_be_nil
41
45
  end
42
46
  end
43
47
 
@@ -49,17 +53,17 @@ describe 'Rack::Attack.Allow2Ban' do
49
53
  end
50
54
 
51
55
  it 'succeeds' do
52
- last_response.status.must_equal 200
56
+ _(last_response.status).must_equal 200
53
57
  end
54
58
 
55
59
  it 'increases fail count' do
56
60
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
57
- @cache.store.read(key).must_equal 2
61
+ _(@cache.store.read(key)).must_equal 2
58
62
  end
59
63
 
60
64
  it 'is banned' do
61
65
  key = "rack::attack:allow2ban:ban:1.2.3.4"
62
- @cache.store.read(key).must_equal 1
66
+ _(@cache.store.read(key)).must_equal 1
63
67
  end
64
68
  end
65
69
  end
@@ -75,7 +79,8 @@ describe 'Rack::Attack.Allow2Ban' do
75
79
  describe 'making request for other discriminator' do
76
80
  it 'succeeds' do
77
81
  get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
78
- last_response.status.must_equal 200
82
+
83
+ _(last_response.status).must_equal 200
79
84
  end
80
85
  end
81
86
 
@@ -85,17 +90,17 @@ describe 'Rack::Attack.Allow2Ban' do
85
90
  end
86
91
 
87
92
  it 'fails' do
88
- last_response.status.must_equal 403
93
+ _(last_response.status).must_equal 403
89
94
  end
90
95
 
91
96
  it 'does not increase fail count' do
92
97
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
93
- @cache.store.read(key).must_equal 2
98
+ _(@cache.store.read(key)).must_equal 2
94
99
  end
95
100
 
96
101
  it 'is still banned' do
97
102
  key = "rack::attack:allow2ban:ban:1.2.3.4"
98
- @cache.store.read(key).must_equal 1
103
+ _(@cache.store.read(key)).must_equal 1
99
104
  end
100
105
  end
101
106
 
@@ -105,17 +110,17 @@ describe 'Rack::Attack.Allow2Ban' do
105
110
  end
106
111
 
107
112
  it 'fails' do
108
- last_response.status.must_equal 403
113
+ _(last_response.status).must_equal 403
109
114
  end
110
115
 
111
116
  it 'does not increase fail count' do
112
117
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
113
- @cache.store.read(key).must_equal 2
118
+ _(@cache.store.read(key)).must_equal 2
114
119
  end
115
120
 
116
121
  it 'is still banned' do
117
122
  key = "rack::attack:allow2ban:ban:1.2.3.4"
118
- @cache.store.read(key).must_equal 1
123
+ _(@cache.store.read(key)).must_equal 1
119
124
  end
120
125
  end
121
126
  end
@@ -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 = { :bantime => @bantime, :findtime => @findtime, :maxretry => 2 }
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/ }
@@ -18,7 +20,7 @@ describe 'Rack::Attack.Fail2Ban' do
18
20
  describe 'making ok request' do
19
21
  it 'succeeds' do
20
22
  get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
21
- last_response.status.must_equal 200
23
+ _(last_response.status).must_equal 200
22
24
  end
23
25
  end
24
26
 
@@ -27,17 +29,17 @@ describe 'Rack::Attack.Fail2Ban' do
27
29
  before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
28
30
 
29
31
  it 'fails' do
30
- last_response.status.must_equal 403
32
+ _(last_response.status).must_equal 403
31
33
  end
32
34
 
33
35
  it 'increases fail count' do
34
36
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
35
- @cache.store.read(key).must_equal 1
37
+ _(@cache.store.read(key)).must_equal 1
36
38
  end
37
39
 
38
40
  it 'is not banned' do
39
41
  key = "rack::attack:fail2ban:1.2.3.4"
40
- @cache.store.read(key).must_be_nil
42
+ _(@cache.store.read(key)).must_be_nil
41
43
  end
42
44
  end
43
45
 
@@ -49,17 +51,17 @@ describe 'Rack::Attack.Fail2Ban' do
49
51
  end
50
52
 
51
53
  it 'fails' do
52
- last_response.status.must_equal 403
54
+ _(last_response.status).must_equal 403
53
55
  end
54
56
 
55
57
  it 'increases fail count' do
56
58
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
57
- @cache.store.read(key).must_equal 2
59
+ _(@cache.store.read(key)).must_equal 2
58
60
  end
59
61
 
60
62
  it 'is banned' do
61
63
  key = "rack::attack:fail2ban:ban:1.2.3.4"
62
- @cache.store.read(key).must_equal 1
64
+ _(@cache.store.read(key)).must_equal 1
63
65
  end
64
66
  end
65
67
 
@@ -71,7 +73,7 @@ describe 'Rack::Attack.Fail2Ban' do
71
73
  end
72
74
 
73
75
  it 'succeeds' do
74
- last_response.status.must_equal 200
76
+ _(last_response.status).must_equal 200
75
77
  end
76
78
 
77
79
  it 'resets fail count' do
@@ -80,7 +82,7 @@ describe 'Rack::Attack.Fail2Ban' do
80
82
  end
81
83
 
82
84
  it 'IP is not banned' do
83
- Rack::Attack::Fail2Ban.banned?('1.2.3.4').must_equal false
85
+ _(Rack::Attack::Fail2Ban.banned?('1.2.3.4')).must_equal false
84
86
  end
85
87
  end
86
88
  end
@@ -96,7 +98,8 @@ describe 'Rack::Attack.Fail2Ban' do
96
98
  describe 'making request for other discriminator' do
97
99
  it 'succeeds' do
98
100
  get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
99
- last_response.status.must_equal 200
101
+
102
+ _(last_response.status).must_equal 200
100
103
  end
101
104
  end
102
105
 
@@ -106,17 +109,17 @@ describe 'Rack::Attack.Fail2Ban' do
106
109
  end
107
110
 
108
111
  it 'fails' do
109
- last_response.status.must_equal 403
112
+ _(last_response.status).must_equal 403
110
113
  end
111
114
 
112
115
  it 'does not increase fail count' do
113
116
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
114
- @cache.store.read(key).must_equal 2
117
+ _(@cache.store.read(key)).must_equal 2
115
118
  end
116
119
 
117
120
  it 'is still banned' do
118
121
  key = "rack::attack:fail2ban:ban:1.2.3.4"
119
- @cache.store.read(key).must_equal 1
122
+ _(@cache.store.read(key)).must_equal 1
120
123
  end
121
124
  end
122
125
 
@@ -126,17 +129,17 @@ describe 'Rack::Attack.Fail2Ban' do
126
129
  end
127
130
 
128
131
  it 'fails' do
129
- last_response.status.must_equal 403
132
+ _(last_response.status).must_equal 403
130
133
  end
131
134
 
132
135
  it 'does not increase fail count' do
133
136
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
134
- @cache.store.read(key).must_equal 2
137
+ _(@cache.store.read(key)).must_equal 2
135
138
  end
136
139
 
137
140
  it 'is still banned' do
138
141
  key = "rack::attack:fail2ban:ban:1.2.3.4"
139
- @cache.store.read(key).must_equal 1
142
+ _(@cache.store.read(key)).must_equal 1
140
143
  end
141
144
  end
142
145
  end
@@ -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(:host => '127.0.0.1', :port => 3333)
27
+ @cache.store = ActiveSupport::Cache::RedisStore.new(host: '127.0.0.1', port: 3333)
26
28
  end
27
29
  end
28
30
  end
@@ -43,3 +45,15 @@ if defined?(::Dalli)
43
45
  end
44
46
  end
45
47
  end
48
+
49
+ if defined?(Redis)
50
+ describe 'when Redis is offline' do
51
+ include OfflineExamples
52
+
53
+ before do
54
+ @cache = Rack::Attack::Cache.new
55
+ # Use presumably unused port for Redis client
56
+ @cache.store = Redis.new(host: '127.0.0.1', port: 3333)
57
+ end
58
+ end
59
+ end