rack-attack 4.2.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack-attack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +4 -4
- data/Rakefile +1 -0
- data/lib/rack/attack/cache.rb +18 -4
- data/lib/rack/attack/fail2ban.rb +11 -4
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +7 -3
- data/lib/rack/attack/version.rb +1 -1
- data/spec/fail2ban_spec.rb +20 -0
- data/spec/integration/rack_attack_cache_spec.rb +33 -0
- data/spec/rack_attack_spec.rb +3 -1
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da0016c3e3d7fee696a96f8f1e3a493b0b197518
|
4
|
+
data.tar.gz: 6b337c7d2ed9c48dbfd4cfc031ab6157fbceda3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f84ef0262ee5f64ed98d745bedf49f7e8ef38693a3bc8e0eddb643dc7165e881ee11642944cf75d52769e13f6da934be69bf3156c6945e938caa139bf886252
|
7
|
+
data.tar.gz: b952d3bd6061eaf8c7b5172c0b6620d4e3797d383c7a00d9d6d80eef78d470ba3ce8dbb1c57f801f3685164f7076a587236fc5bc3550b433c3771bddbfe589ef
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac
|
|
15
15
|
|
16
16
|
## Getting started
|
17
17
|
|
18
|
-
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to
|
18
|
+
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to your Gemfile with bundler:
|
19
19
|
|
20
20
|
```ruby
|
21
21
|
# In your Gemfile
|
@@ -36,7 +36,7 @@ Or for Rackup files:
|
|
36
36
|
use Rack::Attack
|
37
37
|
```
|
38
38
|
|
39
|
-
Add a `rack-attack.rb` file to `config/
|
39
|
+
Add a `rack-attack.rb` file to `config/initializers/`:
|
40
40
|
```ruby
|
41
41
|
# In config/initializers/rack-attack.rb
|
42
42
|
class Rack::Attack
|
@@ -95,7 +95,7 @@ can cleanly monkey patch helper methods onto the
|
|
95
95
|
|
96
96
|
Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
|
97
97
|
these go in an initializer in `config/initializers/`.
|
98
|
-
A [Rack::Request](http://
|
98
|
+
A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples).
|
99
99
|
|
100
100
|
### Whitelists
|
101
101
|
|
@@ -199,7 +199,7 @@ Rack::Attack.track("special_agent") do |req|
|
|
199
199
|
end
|
200
200
|
|
201
201
|
# Supports optional limit and period, triggers the notification only when the limit is reached.
|
202
|
-
Rack::Attack.track("special_agent", :limit 6, :period => 60.seconds) do |req|
|
202
|
+
Rack::Attack.track("special_agent", :limit => 6, :period => 60.seconds) do |req|
|
203
203
|
req.user_agent == "SpecialAgent"
|
204
204
|
end
|
205
205
|
|
data/Rakefile
CHANGED
data/lib/rack/attack/cache.rb
CHANGED
@@ -15,10 +15,7 @@ module Rack
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def count(unprefixed_key, period)
|
18
|
-
|
19
|
-
# Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
|
20
|
-
expires_in = period - (epoch_time % period) + 1
|
21
|
-
key = "#{prefix}:#{(epoch_time/period).to_i}:#{unprefixed_key}"
|
18
|
+
key, expires_in = key_and_expiry(unprefixed_key, period)
|
22
19
|
do_count(key, expires_in)
|
23
20
|
end
|
24
21
|
|
@@ -30,7 +27,24 @@ module Rack
|
|
30
27
|
store.write("#{prefix}:#{unprefixed_key}", value, :expires_in => expires_in)
|
31
28
|
end
|
32
29
|
|
30
|
+
def reset_count(unprefixed_key, period)
|
31
|
+
key, _ = key_and_expiry(unprefixed_key, period)
|
32
|
+
store.delete(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(unprefixed_key)
|
36
|
+
store.delete("#{prefix}:#{unprefixed_key}")
|
37
|
+
end
|
38
|
+
|
33
39
|
private
|
40
|
+
|
41
|
+
def key_and_expiry(unprefixed_key, period)
|
42
|
+
epoch_time = Time.now.to_i
|
43
|
+
# Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA
|
44
|
+
expires_in = (period - (epoch_time % period) + 1).to_i
|
45
|
+
["#{prefix}:#{(epoch_time / period).to_i}:#{unprefixed_key}", expires_in]
|
46
|
+
end
|
47
|
+
|
34
48
|
def do_count(key, expires_in)
|
35
49
|
result = store.increment(key, 1, :expires_in => expires_in)
|
36
50
|
|
data/lib/rack/attack/fail2ban.rb
CHANGED
@@ -15,6 +15,17 @@ module Rack
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def reset(discriminator, options)
|
19
|
+
findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option"
|
20
|
+
cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime)
|
21
|
+
# Clear ban flag just in case it's there
|
22
|
+
cache.delete("#{key_prefix}:ban:#{discriminator}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def banned?(discriminator)
|
26
|
+
cache.read("#{key_prefix}:ban:#{discriminator}") ? true : false
|
27
|
+
end
|
28
|
+
|
18
29
|
protected
|
19
30
|
def key_prefix
|
20
31
|
'fail2ban'
|
@@ -35,10 +46,6 @@ module Rack
|
|
35
46
|
cache.write("#{key_prefix}:ban:#{discriminator}", 1, bantime)
|
36
47
|
end
|
37
48
|
|
38
|
-
def banned?(discriminator)
|
39
|
-
cache.read("#{key_prefix}:ban:#{discriminator}")
|
40
|
-
end
|
41
|
-
|
42
49
|
def cache
|
43
50
|
Rack::Attack.cache
|
44
51
|
end
|
@@ -13,15 +13,15 @@ module Rack
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def read(key)
|
16
|
-
self.get(key)
|
16
|
+
self.get(key, raw: true)
|
17
17
|
rescue Redis::BaseError
|
18
18
|
end
|
19
19
|
|
20
20
|
def write(key, value, options={})
|
21
21
|
if (expires_in = options[:expires_in])
|
22
|
-
self.setex(key, expires_in, value)
|
22
|
+
self.setex(key, expires_in, value, raw: true)
|
23
23
|
else
|
24
|
-
self.set(key, value)
|
24
|
+
self.set(key, value, raw: true)
|
25
25
|
end
|
26
26
|
rescue Redis::BaseError
|
27
27
|
end
|
@@ -36,6 +36,10 @@ module Rack
|
|
36
36
|
rescue Redis::BaseError
|
37
37
|
end
|
38
38
|
|
39
|
+
def delete(key, options={})
|
40
|
+
self.del(key)
|
41
|
+
rescue Redis::BaseError
|
42
|
+
end
|
39
43
|
end
|
40
44
|
end
|
41
45
|
end
|
data/lib/rack/attack/version.rb
CHANGED
data/spec/fail2ban_spec.rb
CHANGED
@@ -58,7 +58,27 @@ describe 'Rack::Attack.Fail2Ban' do
|
|
58
58
|
key = "rack::attack:fail2ban:ban:1.2.3.4"
|
59
59
|
@cache.store.read(key).must_equal 1
|
60
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'reset after success' do
|
64
|
+
before do
|
65
|
+
get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
|
66
|
+
Rack::Attack::Fail2Ban.reset('1.2.3.4', @f2b_options)
|
67
|
+
get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
|
68
|
+
end
|
61
69
|
|
70
|
+
it 'succeeds' do
|
71
|
+
last_response.status.must_equal 200
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'resets fail count' do
|
75
|
+
key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
|
76
|
+
@cache.store.read(key).must_equal nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'IP is not banned' do
|
80
|
+
Rack::Attack::Fail2Ban.banned?('1.2.3.4').must_equal false
|
81
|
+
end
|
62
82
|
end
|
63
83
|
end
|
64
84
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
3
|
describe Rack::Attack::Cache do
|
4
|
+
|
5
|
+
# A convenience method for deleting a key from cache.
|
6
|
+
# Slightly differnet than @cache.delete, which adds a prefix.
|
4
7
|
def delete(key)
|
5
8
|
if @cache.store.respond_to?(:delete)
|
6
9
|
@cache.store.delete(key)
|
@@ -80,6 +83,36 @@ describe Rack::Attack::Cache do
|
|
80
83
|
@cache.read("cache-test-key").must_equal "foobar"
|
81
84
|
end
|
82
85
|
end
|
86
|
+
|
87
|
+
describe "delete" do
|
88
|
+
it "must delete the value" do
|
89
|
+
store.write(@key, "foobar", :expires_in => @expires_in)
|
90
|
+
@cache.read('cache-test-key').must_equal "foobar"
|
91
|
+
store.delete(@key)
|
92
|
+
@cache.read('cache-test-key').must_equal nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "cache#delete" do
|
97
|
+
it "must delete the value" do
|
98
|
+
@cache.write("cache-test-key", "foobar", 1)
|
99
|
+
store.read(@key).must_equal "foobar"
|
100
|
+
@cache.delete('cache-test-key')
|
101
|
+
store.read(@key).must_be :nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "reset_count" do
|
106
|
+
it "must delete the value" do
|
107
|
+
period = 1.minute
|
108
|
+
unprefixed_key = 'cache-test-key'
|
109
|
+
@cache.count(unprefixed_key, period)
|
110
|
+
period_key, _ = @cache.send(:key_and_expiry, 'cache-test-key', period)
|
111
|
+
store.read(period_key).to_i.must_equal 1
|
112
|
+
@cache.reset_count(unprefixed_key, period)
|
113
|
+
store.read(period_key).must_equal nil
|
114
|
+
end
|
115
|
+
end
|
83
116
|
end
|
84
117
|
|
85
118
|
end
|
data/spec/rack_attack_spec.rb
CHANGED
@@ -9,7 +9,9 @@ describe 'Rack::Attack' do
|
|
9
9
|
Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
10
10
|
end
|
11
11
|
|
12
|
-
it('has a blacklist') {
|
12
|
+
it('has a blacklist') {
|
13
|
+
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
|
14
|
+
}
|
13
15
|
|
14
16
|
describe "a bad request" do
|
15
17
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-attack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Suggs
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -189,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
189
|
version: '0'
|
190
190
|
requirements: []
|
191
191
|
rubyforge_project:
|
192
|
-
rubygems_version: 2.
|
192
|
+
rubygems_version: 2.4.5
|
193
193
|
signing_key:
|
194
194
|
specification_version: 4
|
195
195
|
summary: Block & throttle abusive requests
|
@@ -204,4 +204,3 @@ test_files:
|
|
204
204
|
- spec/rack_attack_throttle_spec.rb
|
205
205
|
- spec/rack_attack_track_spec.rb
|
206
206
|
- spec/spec_helper.rb
|
207
|
-
has_rdoc:
|