wkimeria-rack-attack 4.1.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 +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
|
+
[](http://badge.fury.io/rb/rack-attack)
|
12
|
+
[](https://travis-ci.org/kickstarter/rack-attack)
|
13
|
+
[](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
|