wkimeria-rack-attack 4.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/README.md +308 -0
- data/Rakefile +18 -0
- data/lib/rack/attack.rb +122 -0
- data/lib/rack/attack/allow2ban.rb +23 -0
- data/lib/rack/attack/blacklist.rb +12 -0
- data/lib/rack/attack/cache.rb +57 -0
- data/lib/rack/attack/check.rb +23 -0
- data/lib/rack/attack/conditional_throttle.rb +17 -0
- data/lib/rack/attack/fail2ban.rb +48 -0
- data/lib/rack/attack/request.rb +19 -0
- data/lib/rack/attack/store_proxy.rb +22 -0
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +65 -0
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +49 -0
- data/lib/rack/attack/throttle.rb +50 -0
- data/lib/rack/attack/track.rb +21 -0
- data/lib/rack/attack/version.rb +5 -0
- data/lib/rack/attack/whitelist.rb +11 -0
- data/spec/allow2ban_spec.rb +121 -0
- data/spec/fail2ban_spec.rb +121 -0
- data/spec/integration/offline_spec.rb +47 -0
- data/spec/integration/rack_attack_cache_spec.rb +86 -0
- data/spec/rack_attack_conditional_throttle_spec.rb +53 -0
- data/spec/rack_attack_dalli_proxy_spec.rb +10 -0
- data/spec/rack_attack_request_spec.rb +19 -0
- data/spec/rack_attack_spec.rb +50 -0
- data/spec/rack_attack_throttle_spec.rb +64 -0
- data/spec/rack_attack_track_spec.rb +58 -0
- data/spec/spec_helper.rb +40 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODBkODliMDY4NmQ1MzAyZmNlMWZjOWU2ZWQ2YmQwOGU3MjY0MzY4NA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YTIxNWEyMzk4MTE0MDEwODQyMTdlYjY4YTc1M2FiZDhiYjQ3YmFjOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjUzMTMyMWJiOWM5MWYzMTI0Zjg3NzYyMDM4M2FhM2RmNjkxMjJjMjViMGRi
|
10
|
+
ZWJjMjNjYzc4MDBjZjY1NzkzOWM5ZDA3NWNlYjQ0NThiMTA1OTg0OWE5ZmFh
|
11
|
+
N2RmYjdmMDJlYjVhYjMyMDdjN2M3MmRkOGIzYTYzYTVhMTJiMGY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzdmNmE4NDhlNGJkYjgzNTMwYjQzYjlkMzM1YTQ4NjJhYzllZGJkZDE0YTM5
|
14
|
+
YWVlYjFhNDBkYjFhZjRiOGYwNjM4YzA1MWQ3MjlhYmUyYzEzZjIzN2EwM2U5
|
15
|
+
OGMyMjIwMTdlZGI4OWE3Zjc0ZTdjOTFkZTIyZmIyYTM3ZTc0OGE=
|
data/README.md
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
# Rack::Attack!!!
|
2
|
+
*Rack middleware for blocking & throttling abusive requests*
|
3
|
+
|
4
|
+
Rack::Attack is a rack middleware to protect your web app from bad clients.
|
5
|
+
It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
|
6
|
+
|
7
|
+
Throttle state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
|
8
|
+
|
9
|
+
See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
|
10
|
+
|
11
|
+
[![Gem Version](https://badge.fury.io/rb/rack-attack.png)](http://badge.fury.io/rb/rack-attack)
|
12
|
+
[![Build Status](https://travis-ci.org/kickstarter/rack-attack.png?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
|
13
|
+
[![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.png)](https://codeclimate.com/github/kickstarter/rack-attack)
|
14
|
+
|
15
|
+
|
16
|
+
## Getting started
|
17
|
+
|
18
|
+
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# In your Gemfile
|
22
|
+
gem 'rack-attack'
|
23
|
+
```
|
24
|
+
Tell your app to use the Rack::Attack middleware.
|
25
|
+
For Rails 3+ apps:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# In config/application.rb
|
29
|
+
config.middleware.use Rack::Attack
|
30
|
+
```
|
31
|
+
|
32
|
+
Or for Rackup files:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# In config.ru
|
36
|
+
use Rack::Attack
|
37
|
+
```
|
38
|
+
|
39
|
+
Add a `rack-attack.rb` file to `config/initalizers/`:
|
40
|
+
```ruby
|
41
|
+
# In config/initializers/rack-attack.rb
|
42
|
+
class Rack::Attack
|
43
|
+
# your custom configuration...
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
*Tip:* The example in the wiki is a great way to get started:
|
48
|
+
[Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
|
49
|
+
|
50
|
+
Optionally configure the cache store for throttling:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
54
|
+
```
|
55
|
+
|
56
|
+
Note that `Rack::Attack.cache` is only used for throttling; not blacklisting & whitelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
|
57
|
+
|
58
|
+
## How it works
|
59
|
+
|
60
|
+
The Rack::Attack middleware compares each request against *whitelists*, *blacklists*, *throttles*, and *tracks* that you define. There are none by default.
|
61
|
+
|
62
|
+
* If the request matches any **whitelist**, it is allowed.
|
63
|
+
* Otherwise, if the request matches any **blacklist**, it is blocked.
|
64
|
+
* Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
|
65
|
+
* Otherwise, all **tracks** are checked, and the request is allowed.
|
66
|
+
|
67
|
+
The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
def call(env)
|
71
|
+
req = Rack::Attack::Request.new(env)
|
72
|
+
|
73
|
+
if whitelisted?(req)
|
74
|
+
@app.call(env)
|
75
|
+
elsif blacklisted?(req)
|
76
|
+
blacklisted_response[env]
|
77
|
+
elsif throttled?(req)
|
78
|
+
throttled_response[env]
|
79
|
+
else
|
80
|
+
tracked?(req)
|
81
|
+
@app.call(env)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Note: `Rack::Attack::Request` is just a subclass of `Rack::Attack` so that you
|
87
|
+
can cleanly monkey patch helper methods onto the
|
88
|
+
[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
|
89
|
+
|
90
|
+
## About Tracks
|
91
|
+
|
92
|
+
`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
|
93
|
+
|
94
|
+
## Usage
|
95
|
+
|
96
|
+
Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
|
97
|
+
these go in an initializer in `config/initializers/`.
|
98
|
+
A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) object is passed to the block (named 'req' in the examples).
|
99
|
+
|
100
|
+
### Whitelists
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# Always allow requests from localhost
|
104
|
+
# (blacklist & throttles are skipped)
|
105
|
+
Rack::Attack.whitelist('allow from localhost') do |req|
|
106
|
+
# Requests are allowed if the return value is truthy
|
107
|
+
'127.0.0.1' == req.ip
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
### Blacklists
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
# Block requests from 1.2.3.4
|
115
|
+
Rack::Attack.blacklist('block 1.2.3.4') do |req|
|
116
|
+
# Requests are blocked if the return value is truthy
|
117
|
+
'1.2.3.4' == req.ip
|
118
|
+
end
|
119
|
+
|
120
|
+
# Block logins from a bad user agent
|
121
|
+
Rack::Attack.blacklist('block bad UA logins') do |req|
|
122
|
+
req.path == '/login' && req.post? && req.user_agent == 'BadUA'
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
#### Fail2Ban
|
127
|
+
|
128
|
+
`Fail2Ban.filter` can be used within a blacklist to block all requests from misbehaving clients.
|
129
|
+
This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
|
130
|
+
See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
|
131
|
+
how the parameters work.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
# Block requests containing '/etc/password' in the params.
|
135
|
+
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
|
136
|
+
Rack::Attack.blacklist('fail2ban pentesters') do |req|
|
137
|
+
# `filter` returns truthy value if request fails, or if it's from a previously banned IP
|
138
|
+
# so the request is blocked
|
139
|
+
Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
|
140
|
+
# The count for the IP is incremented if the return value is truthy.
|
141
|
+
CGI.unescape(req.query_string) =~ %r{/etc/passwd}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
#### Allow2Ban
|
147
|
+
`Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
|
148
|
+
clients until such time as they reach maxretry at which they are cut off as per normal.
|
149
|
+
```ruby
|
150
|
+
# Lockout IP addresses that are hammering your login page.
|
151
|
+
# After 20 requests in 1 minute, block all requests from that IP for 1 hour.
|
152
|
+
Rack::Attack.blacklist('allow2ban login scrapers') do |req|
|
153
|
+
# `filter` returns false value if request is to your login page (but still
|
154
|
+
# increments the count) so request below the limit are not blocked until
|
155
|
+
# they hit the limit. At that point, filter will return true and block.
|
156
|
+
Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
|
157
|
+
# The count for the IP is incremented if the return value is truthy.
|
158
|
+
req.path == '/login' and req.post?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
|
164
|
+
### Throttles
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# Throttle requests to 5 requests per second per ip
|
168
|
+
Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
|
169
|
+
# If the return value is truthy, the cache key for the return value
|
170
|
+
# is incremented and compared with the limit. In this case:
|
171
|
+
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
172
|
+
#
|
173
|
+
# If falsy, the cache key is neither incremented nor checked.
|
174
|
+
|
175
|
+
req.ip
|
176
|
+
end
|
177
|
+
|
178
|
+
# Throttle login attempts for a given email parameter to 6 reqs/minute
|
179
|
+
# Return the email as a discriminator on POST /login requests
|
180
|
+
Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
|
181
|
+
req.params['email'] if req.path == '/login' && req.post?
|
182
|
+
end
|
183
|
+
|
184
|
+
# You can also set a limit using a proc instead of a number. For
|
185
|
+
# instance, after Rack::Auth::Basic has authenticated the user:
|
186
|
+
limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
|
187
|
+
Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
|
188
|
+
req.ip
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
### Conditional Throttles
|
193
|
+
# Throttle failed logins
|
194
|
+
Rack::Attack.conditional_throttle('login', :limit =>5, :period => 1.hour) do |req|
|
195
|
+
# If the return value is truthy, the cache key for the return value is checked to determine
|
196
|
+
# whether to throttle. The value for the key is not incremented
|
197
|
+
|
198
|
+
# To increment the value returned by key
|
199
|
+
To increment counter
|
200
|
+
if(failed_login)
|
201
|
+
Rack::Attack.increment_throttle_counter('login', request.params['...
|
202
|
+
end
|
203
|
+
|
204
|
+
### Tracks
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
# Track requests from a special user agent.
|
208
|
+
Rack::Attack.track("special_agent") do |req|
|
209
|
+
req.user_agent == "SpecialAgent"
|
210
|
+
end
|
211
|
+
|
212
|
+
# Supports optional limit and period, triggers the notification only when the limit is reached.
|
213
|
+
Rack::Attack.track("special_agent", :limit 6, :period => 60.seconds) do |req|
|
214
|
+
req.user_agent == "SpecialAgent"
|
215
|
+
end
|
216
|
+
|
217
|
+
# Track it using ActiveSupport::Notification
|
218
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
|
219
|
+
if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
|
220
|
+
Rails.logger.info "special_agent: #{req.path}"
|
221
|
+
STATSD.increment("special_agent")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
## Responses
|
227
|
+
|
228
|
+
Customize the response of blacklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
Rack::Attack.blacklisted_response = lambda do |env|
|
232
|
+
# Using 503 because it may make attacker think that they have successfully
|
233
|
+
# DOSed the site. Rack::Attack returns 403 for blacklists by default
|
234
|
+
[ 503, {}, ['Blocked']]
|
235
|
+
end
|
236
|
+
|
237
|
+
Rack::Attack.throttled_response = lambda do |env|
|
238
|
+
# name and other data about the matched throttle
|
239
|
+
body = [
|
240
|
+
env['rack.attack.matched'],
|
241
|
+
env['rack.attack.match_type'],
|
242
|
+
env['rack.attack.match_data']
|
243
|
+
].inspect
|
244
|
+
|
245
|
+
# Using 503 because it may make attacker think that they have successfully
|
246
|
+
# DOSed the site. Rack::Attack returns 429 for throttling by default
|
247
|
+
[ 503, {}, [body]]
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
|
255
|
+
```
|
256
|
+
|
257
|
+
## Logging & Instrumentation
|
258
|
+
|
259
|
+
Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API if available.
|
260
|
+
|
261
|
+
You can subscribe to 'rack.attack' events and log it, graph it, etc:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
|
265
|
+
puts req.inspect
|
266
|
+
end
|
267
|
+
```
|
268
|
+
|
269
|
+
## Testing
|
270
|
+
|
271
|
+
A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
|
272
|
+
need to enable the cache in your development environment. See [Caching with Rails](http://guides.rubyonrails.org/caching_with_rails.html)
|
273
|
+
for more on how to do this.
|
274
|
+
|
275
|
+
## Performance
|
276
|
+
|
277
|
+
The overhead of running Rack::Attack is typically negligible (a few milliseconds per request),
|
278
|
+
but it depends on how many checks you've configured, and how long they take.
|
279
|
+
Throttles usually require a network roundtrip to your cache server(s),
|
280
|
+
so try to keep the number of throttle checks per request low.
|
281
|
+
|
282
|
+
If a request is blacklisted or throttled, the response is a very simple Rack response.
|
283
|
+
A single typical ruby web server thread can block several hundred requests per second.
|
284
|
+
|
285
|
+
Rack::Attack complements tools like `iptables` and nginx's [limit_conn_zone module](http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone).
|
286
|
+
|
287
|
+
## Motivation
|
288
|
+
|
289
|
+
Abusive clients range from malicious login crackers to naively-written scrapers.
|
290
|
+
They hinder the security, performance, & availability of web applications.
|
291
|
+
|
292
|
+
It is impractical if not impossible to block abusive clients completely.
|
293
|
+
|
294
|
+
Rack::Attack aims to let developers quickly mitigate abusive requests and rely
|
295
|
+
less on short-term, one-off hacks to block a particular attack.
|
296
|
+
|
297
|
+
## Mailing list
|
298
|
+
|
299
|
+
New releases of Rack::Attack are announced on
|
300
|
+
<rack.attack.announce@librelist.com>. To subscribe, just send an email to
|
301
|
+
<rack.attack.announce@librelist.com>. See the
|
302
|
+
[archives](http://librelist.com/browser/rack.attack.announce/).
|
303
|
+
|
304
|
+
## License
|
305
|
+
|
306
|
+
Copyright Kickstarter, Inc.
|
307
|
+
|
308
|
+
Released under an [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
namespace :test do
|
6
|
+
Rake::TestTask.new(:units) do |t|
|
7
|
+
t.pattern = "spec/*_spec.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::TestTask.new(:integration) do |t|
|
11
|
+
t.pattern = "spec/integration/*_spec.rb"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Run tests'
|
16
|
+
task :test => %w[test:units test:integration]
|
17
|
+
|
18
|
+
task :default => :test
|
data/lib/rack/attack.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
class Rack::Attack
|
5
|
+
autoload :Cache, 'rack/attack/cache'
|
6
|
+
autoload :Check, 'rack/attack/check'
|
7
|
+
autoload :Throttle, 'rack/attack/throttle'
|
8
|
+
autoload :ConditionalThrottle, 'rack/attack/conditional_throttle'
|
9
|
+
autoload :Whitelist, 'rack/attack/whitelist'
|
10
|
+
autoload :Blacklist, 'rack/attack/blacklist'
|
11
|
+
autoload :Track, 'rack/attack/track'
|
12
|
+
autoload :StoreProxy, 'rack/attack/store_proxy'
|
13
|
+
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
|
14
|
+
autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
|
15
|
+
autoload :Fail2Ban, 'rack/attack/fail2ban'
|
16
|
+
autoload :Allow2Ban, 'rack/attack/allow2ban'
|
17
|
+
autoload :Request, 'rack/attack/request'
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
attr_accessor :notifier, :blacklisted_response, :throttled_response
|
22
|
+
|
23
|
+
def increment_throttle_counter(name, discriminator)
|
24
|
+
self.throttles[name].increment_counter(discriminator)
|
25
|
+
end
|
26
|
+
|
27
|
+
def whitelist(name, &block)
|
28
|
+
self.whitelists[name] = Whitelist.new(name, block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def blacklist(name, &block)
|
32
|
+
self.blacklists[name] = Blacklist.new(name, block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def throttle(name, options, &block)
|
36
|
+
self.throttles[name] = Throttle.new(name, options, block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def conditional_throttle(name, options, &block)
|
40
|
+
self.throttles[name] = ConditionalThrottle.new(name, options, block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def track(name, options = {}, &block)
|
44
|
+
self.tracks[name] = Track.new(name, options, block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def whitelists; @whitelists ||= {}; end
|
48
|
+
def blacklists; @blacklists ||= {}; end
|
49
|
+
def throttles; @throttles ||= {}; end
|
50
|
+
def tracks; @tracks ||= {}; end
|
51
|
+
|
52
|
+
def whitelisted?(req)
|
53
|
+
whitelists.any? do |name, whitelist|
|
54
|
+
whitelist[req]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def blacklisted?(req)
|
59
|
+
blacklists.any? do |name, blacklist|
|
60
|
+
blacklist[req]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def throttled?(req)
|
65
|
+
throttles.any? do |name, throttle|
|
66
|
+
throttle[req]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def tracked?(req)
|
71
|
+
tracks.each_value do |tracker|
|
72
|
+
tracker[req]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def instrument(req)
|
77
|
+
notifier.instrument('rack.attack', req) if notifier
|
78
|
+
end
|
79
|
+
|
80
|
+
def cache
|
81
|
+
@cache ||= Cache.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def clear!
|
85
|
+
@whitelists, @blacklists, @throttles, @tracks = {}, {}, {}, {}
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set defaults
|
91
|
+
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
92
|
+
@blacklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
|
93
|
+
@throttled_response = lambda {|env|
|
94
|
+
retry_after = env['rack.attack.match_data'][:period] rescue nil
|
95
|
+
[429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
|
96
|
+
}
|
97
|
+
|
98
|
+
def initialize(app)
|
99
|
+
@app = app
|
100
|
+
end
|
101
|
+
|
102
|
+
def call(env)
|
103
|
+
req = Rack::Attack::Request.new(env)
|
104
|
+
|
105
|
+
if whitelisted?(req)
|
106
|
+
@app.call(env)
|
107
|
+
elsif blacklisted?(req)
|
108
|
+
self.class.blacklisted_response[env]
|
109
|
+
elsif throttled?(req)
|
110
|
+
self.class.throttled_response[env]
|
111
|
+
else
|
112
|
+
tracked?(req)
|
113
|
+
@app.call(env)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
extend Forwardable
|
118
|
+
def_delegators self, :whitelisted?,
|
119
|
+
:blacklisted?,
|
120
|
+
:throttled?,
|
121
|
+
:tracked?
|
122
|
+
end
|