wkimeria-rack-attack 4.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.Fail2Ban' do
3
+ before do
4
+ # Use a long findtime; failures due to cache key rotation less likely
5
+ @cache = Rack::Attack.cache
6
+ @findtime = 60
7
+ @bantime = 60
8
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
9
+ @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
10
+ Rack::Attack.blacklist('pentest') do |req|
11
+ Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
12
+ end
13
+ end
14
+
15
+ describe 'discriminator has not been banned' do
16
+ describe 'making ok request' do
17
+ it 'succeeds' do
18
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
19
+ last_response.status.must_equal 200
20
+ end
21
+ end
22
+
23
+ describe 'making failing request' do
24
+ describe 'when not at maxretry' do
25
+ before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
26
+ it 'fails' do
27
+ last_response.status.must_equal 403
28
+ end
29
+
30
+ it 'increases fail count' do
31
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
32
+ @cache.store.read(key).must_equal 1
33
+ end
34
+
35
+ it 'is not banned' do
36
+ key = "rack::attack:fail2ban:1.2.3.4"
37
+ @cache.store.read(key).must_be_nil
38
+ end
39
+ end
40
+
41
+ describe 'when at maxretry' do
42
+ before do
43
+ # maxretry is 2 - so hit with an extra failed request first
44
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
45
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
46
+ end
47
+
48
+ it 'fails' do
49
+ last_response.status.must_equal 403
50
+ end
51
+
52
+ it 'increases fail count' do
53
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
54
+ @cache.store.read(key).must_equal 2
55
+ end
56
+
57
+ it 'is banned' do
58
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
59
+ @cache.store.read(key).must_equal 1
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'discriminator has been banned' do
67
+ before do
68
+ # maxretry is 2 - so hit enough times to get banned
69
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
70
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
71
+ end
72
+
73
+ describe 'making request for other discriminator' do
74
+ it 'succeeds' do
75
+ get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
76
+ last_response.status.must_equal 200
77
+ end
78
+ end
79
+
80
+ describe 'making ok request' do
81
+ before do
82
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
83
+ end
84
+
85
+ it 'fails' do
86
+ last_response.status.must_equal 403
87
+ end
88
+
89
+ it 'does not increase fail count' do
90
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
91
+ @cache.store.read(key).must_equal 2
92
+ end
93
+
94
+ it 'is still banned' do
95
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
96
+ @cache.store.read(key).must_equal 1
97
+ end
98
+ end
99
+
100
+ describe 'making failing request' do
101
+ before do
102
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
103
+ end
104
+
105
+ it 'fails' do
106
+ last_response.status.must_equal 403
107
+ end
108
+
109
+ it 'does not increase fail count' do
110
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
111
+ @cache.store.read(key).must_equal 2
112
+ end
113
+
114
+ it 'is still banned' do
115
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
116
+ @cache.store.read(key).must_equal 1
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support/cache'
2
+ require 'active_support/cache/redis_store'
3
+ require 'dalli'
4
+ require_relative '../spec_helper'
5
+
6
+ OfflineExamples = Minitest::SharedExamples.new do
7
+
8
+ it 'should write' do
9
+ @cache.write('cache-test-key', 'foobar', 1)
10
+ end
11
+
12
+ it 'should read' do
13
+ @cache.read('cache-test-key')
14
+ end
15
+
16
+ it 'should count' do
17
+ @cache.send(:do_count, 'rack::attack::cache-test-key', 1)
18
+ end
19
+
20
+ end
21
+
22
+ describe 'when Redis is offline' do
23
+ include OfflineExamples
24
+
25
+ before {
26
+ @cache = Rack::Attack::Cache.new
27
+ # Use presumably unused port for Redis client
28
+ @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333)
29
+ }
30
+
31
+ end
32
+
33
+ describe 'when Memcached is offline' do
34
+ include OfflineExamples
35
+
36
+ before {
37
+ Dalli.logger.level = Logger::FATAL
38
+
39
+ @cache = Rack::Attack::Cache.new
40
+ @cache.store = Dalli::Client.new('127.0.0.1:22122')
41
+ }
42
+
43
+ after {
44
+ Dalli.logger.level = Logger::INFO
45
+ }
46
+
47
+ end
@@ -0,0 +1,86 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Rack::Attack::Cache do
4
+ def delete(key)
5
+ if @cache.store.respond_to?(:delete)
6
+ @cache.store.delete(key)
7
+ else
8
+ @cache.store.del(key)
9
+ end
10
+ end
11
+
12
+ def sleep_until_expired
13
+ sleep(@expires_in * 1.1) # Add 10% to reduce errors
14
+ end
15
+
16
+ require 'active_support/cache/dalli_store'
17
+ require 'active_support/cache/redis_store'
18
+ require 'connection_pool'
19
+ cache_stores = [
20
+ ActiveSupport::Cache::MemoryStore.new,
21
+ ActiveSupport::Cache::DalliStore.new("127.0.0.1"),
22
+ ActiveSupport::Cache::RedisStore.new("127.0.0.1"),
23
+ Dalli::Client.new,
24
+ ConnectionPool.new { Dalli::Client.new },
25
+ Redis::Store.new
26
+ ]
27
+
28
+ cache_stores.each do |store|
29
+ store = Rack::Attack::StoreProxy.build(store)
30
+ describe "with #{store.class}" do
31
+
32
+ before {
33
+ @cache = Rack::Attack::Cache.new
34
+ @key = "rack::attack:cache-test-key"
35
+ @expires_in = 1
36
+ @cache.store = store
37
+ delete(@key)
38
+ }
39
+
40
+ after { delete(@key) }
41
+
42
+ describe "do_count once" do
43
+ it "should be 1" do
44
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
45
+ end
46
+ end
47
+
48
+ describe "do_count twice" do
49
+ it "must be 2" do
50
+ @cache.send(:do_count, @key, @expires_in)
51
+ @cache.send(:do_count, @key, @expires_in).must_equal 2
52
+ end
53
+ end
54
+ describe "do_count after expires_in" do
55
+ it "must be 1" do
56
+ @cache.send(:do_count, @key, @expires_in)
57
+ sleep_until_expired
58
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
59
+ end
60
+ end
61
+
62
+ describe "write" do
63
+ it "should write a value to the store with prefix" do
64
+ @cache.write("cache-test-key", "foobar", 1)
65
+ store.read(@key).must_equal "foobar"
66
+ end
67
+ end
68
+
69
+ describe "write after expiry" do
70
+ it "must not have a value" do
71
+ @cache.write("cache-test-key", "foobar", @expires_in)
72
+ sleep_until_expired
73
+ store.read(@key).must_be :nil?
74
+ end
75
+ end
76
+
77
+ describe "read" do
78
+ it "must read the value with a prefix" do
79
+ store.write(@key, "foobar", :expires_in => @expires_in)
80
+ @cache.read("cache-test-key").must_equal "foobar"
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.conditional_throttle' do
3
+ before do
4
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
5
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
6
+ Rack::Attack.conditional_throttle('login', :limit => 1, :period => @period) { |req| req.ip }
7
+ end
8
+
9
+ it('should have a throttle'){ Rack::Attack.throttles.key?('login') }
10
+ allow_ok_requests
11
+
12
+ describe 'a single successful request' do
13
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
14
+ it 'should not set the counter for one request' do
15
+ key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
16
+ Rack::Attack.cache.store.read(key).must_equal nil
17
+ end
18
+ end
19
+ describe "with 2 requests" do
20
+ before do
21
+ 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
22
+ end
23
+ it 'should not block the last request' do
24
+ last_response.status.must_equal 200
25
+ end
26
+ end
27
+
28
+ describe 'a successful request followed by failed request' do
29
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
30
+ it 'should increment counter for failed request' do
31
+ key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
32
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
33
+ Rack::Attack.cache.store.read(key).must_equal 1
34
+ end
35
+ end
36
+
37
+ describe 'a successful request followed by two failed request followed by successful request' do
38
+ before {
39
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
40
+ @key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
41
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
42
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
43
+ }
44
+ it 'should increment counter for failed request' do
45
+
46
+ Rack::Attack.cache.store.read(@key).must_equal 2
47
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
48
+ Rack::Attack.cache.store.read(@key).must_equal 2
49
+ last_response.status.must_equal 429
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,10 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Rack::Attack::StoreProxy::DalliProxy do
4
+
5
+ it 'should stub Dalli::Client#with on older clients' do
6
+ proxy = Rack::Attack::StoreProxy::DalliProxy.new(Class.new)
7
+ proxy.with {} # will not raise an error
8
+ end
9
+
10
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'Rack::Attack' do
4
+ describe 'helpers' do
5
+ before do
6
+ class Rack::Attack::Request
7
+ def remote_ip
8
+ ip
9
+ end
10
+ end
11
+
12
+ Rack::Attack.whitelist('valid IP') do |req|
13
+ req.remote_ip == "127.0.0.1"
14
+ end
15
+ end
16
+
17
+ allow_ok_requests
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'Rack::Attack' do
4
+ allow_ok_requests
5
+
6
+ describe 'blacklist' do
7
+ before do
8
+ @bad_ip = '1.2.3.4'
9
+ Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
10
+ end
11
+
12
+ it('has a blacklist') { Rack::Attack.blacklists.key?("ip #{@bad_ip}") }
13
+
14
+ describe "a bad request" do
15
+ before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
16
+ it "should return a blacklist response" do
17
+ get '/', {}, 'REMOTE_ADDR' => @bad_ip
18
+ last_response.status.must_equal 403
19
+ last_response.body.must_equal "Forbidden\n"
20
+ end
21
+ it "should tag the env" do
22
+ last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
23
+ last_request.env['rack.attack.match_type'].must_equal :blacklist
24
+ end
25
+
26
+ allow_ok_requests
27
+ end
28
+
29
+ describe "and whitelist" do
30
+ before do
31
+ @good_ua = 'GoodUA'
32
+ Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua }
33
+ end
34
+
35
+ it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
36
+ describe "with a request match both whitelist & blacklist" do
37
+ before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
38
+ it "should allow whitelists before blacklists" do
39
+ get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
40
+ last_response.status.must_equal 200
41
+ end
42
+ it "should tag the env" do
43
+ last_request.env['rack.attack.matched'].must_equal 'good ua'
44
+ last_request.env['rack.attack.match_type'].must_equal :whitelist
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.throttle' do
3
+ before do
4
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
5
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
6
+ Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |req| req.ip }
7
+ end
8
+
9
+ it('should have a throttle'){ Rack::Attack.throttles.key?('ip/sec') }
10
+ allow_ok_requests
11
+
12
+ describe 'a single request' do
13
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
14
+ it 'should set the counter for one request' do
15
+ key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
16
+ Rack::Attack.cache.store.read(key).must_equal 1
17
+ end
18
+
19
+ it 'should populate throttle data' do
20
+ data = { :count => 1, :limit => 1, :period => @period }
21
+ last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
22
+ end
23
+ end
24
+ describe "with 2 requests" do
25
+ before do
26
+ 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
27
+ end
28
+ it 'should block the last request' do
29
+ last_response.status.must_equal 429
30
+ end
31
+ it 'should tag the env' do
32
+ last_request.env['rack.attack.matched'].must_equal 'ip/sec'
33
+ last_request.env['rack.attack.match_type'].must_equal :throttle
34
+ last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period})
35
+ last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
36
+ end
37
+ it 'should set a Retry-After header' do
38
+ last_response.headers['Retry-After'].must_equal @period.to_s
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'Rack::Attack.throttle with limit as proc' do
44
+ before do
45
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
46
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
47
+ Rack::Attack.throttle('ip/sec', :limit => lambda {|req| 1}, :period => @period) { |req| req.ip }
48
+ end
49
+
50
+ allow_ok_requests
51
+
52
+ describe 'a single request' do
53
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
54
+ it 'should set the counter for one request' do
55
+ key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
56
+ Rack::Attack.cache.store.read(key).must_equal 1
57
+ end
58
+
59
+ it 'should populate throttle data' do
60
+ data = { :count => 1, :limit => 1, :period => @period }
61
+ last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
62
+ end
63
+ end
64
+ end