mirror of
https://github.com/samsonjs/rack-attack.git
synced 2026-04-27 15:07:41 +00:00
Merge pull request #35 from schneems/patch-1
properly indent code sections
This commit is contained in:
commit
4a9222e03a
1 changed files with 107 additions and 107 deletions
104
README.md
104
README.md
|
|
@ -18,28 +18,28 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac
|
||||||
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
|
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# In your Gemfile
|
# In your Gemfile
|
||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
```
|
```
|
||||||
Tell your app to use the Rack::Attack middleware.
|
Tell your app to use the Rack::Attack middleware.
|
||||||
For Rails 3+ apps:
|
For Rails 3+ apps:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# In config/application.rb
|
# In config/application.rb
|
||||||
config.middleware.use Rack::Attack
|
config.middleware.use Rack::Attack
|
||||||
```
|
```
|
||||||
|
|
||||||
Or for Rackup files:
|
Or for Rackup files:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# In config.ru
|
# In config.ru
|
||||||
use Rack::Attack
|
use Rack::Attack
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally configure the cache store for throttling:
|
Optionally configure the cache store for throttling:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
@ -56,7 +56,7 @@ The Rack::Attack middleware compares each request against *whitelists*, *blackli
|
||||||
The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
|
The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
def call(env)
|
def call(env)
|
||||||
req = Rack::Request.new(env)
|
req = Rack::Request.new(env)
|
||||||
|
|
||||||
if whitelisted?(req)
|
if whitelisted?(req)
|
||||||
|
|
@ -69,7 +69,7 @@ The algorithm is actually more concise in code: See [Rack::Attack.call](https://
|
||||||
tracked?(req)
|
tracked?(req)
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## About Tracks
|
## About Tracks
|
||||||
|
|
@ -85,27 +85,27 @@ A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) objec
|
||||||
### Whitelists
|
### Whitelists
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Always allow requests from localhost
|
# Always allow requests from localhost
|
||||||
# (blacklist & throttles are skipped)
|
# (blacklist & throttles are skipped)
|
||||||
Rack::Attack.whitelist('allow from localhost') do |req|
|
Rack::Attack.whitelist('allow from localhost') do |req|
|
||||||
# Requests are allowed if the return value is truthy
|
# Requests are allowed if the return value is truthy
|
||||||
'127.0.0.1' == req.ip
|
'127.0.0.1' == req.ip
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blacklists
|
### Blacklists
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Block requests from 1.2.3.4
|
# Block requests from 1.2.3.4
|
||||||
Rack::Attack.blacklist('block 1.2.3.4') do |req|
|
Rack::Attack.blacklist('block 1.2.3.4') do |req|
|
||||||
# Request are blocked if the return value is truthy
|
# Request are blocked if the return value is truthy
|
||||||
'1.2.3.4' == req.ip
|
'1.2.3.4' == req.ip
|
||||||
end
|
end
|
||||||
|
|
||||||
# Block logins from a bad user agent
|
# Block logins from a bad user agent
|
||||||
Rack::Attack.blacklist('block bad UA logins') do |req|
|
Rack::Attack.blacklist('block bad UA logins') do |req|
|
||||||
req.path == '/login' && req.post? && req.user_agent == 'BadUA'
|
req.path == '/login' && req.post? && req.user_agent == 'BadUA'
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fail2Ban
|
#### Fail2Ban
|
||||||
|
|
@ -116,25 +116,25 @@ See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0
|
||||||
how the parameters work.
|
how the parameters work.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Block requests containing '/etc/password' in the params.
|
# Block requests containing '/etc/password' in the params.
|
||||||
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
|
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
|
||||||
Rack::Attack.blacklist('fail2ban pentesters') do |req|
|
Rack::Attack.blacklist('fail2ban pentesters') do |req|
|
||||||
# `filter` returns truthy value if request fails, or if it's from a previously banned IP
|
# `filter` returns truthy value if request fails, or if it's from a previously banned IP
|
||||||
# so the request is blocked
|
# so the request is blocked
|
||||||
Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
|
Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
|
||||||
# The count for the IP is incremented if the return value is truthy.
|
# The count for the IP is incremented if the return value is truthy.
|
||||||
CGI.unescape(req.query_string) =~ %r{/etc/passwd}
|
CGI.unescape(req.query_string) =~ %r{/etc/passwd}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Allow2Ban
|
#### Allow2Ban
|
||||||
`Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
|
`Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
|
||||||
clients until such time as they reach maxretry at which they are cut off as per normal.
|
clients until such time as they reach maxretry at which they are cut off as per normal.
|
||||||
```ruby
|
```ruby
|
||||||
# Lockout IP addresses that are hammering your login page.
|
# Lockout IP addresses that are hammering your login page.
|
||||||
# After 20 requests in 1 minute, block all requests from that IP for 1 hour.
|
# After 20 requests in 1 minute, block all requests from that IP for 1 hour.
|
||||||
Rack::Attack.blacklist('allow2ban login scrapers') do |req|
|
Rack::Attack.blacklist('allow2ban login scrapers') do |req|
|
||||||
# `filter` returns false value if request is to your login page (but still
|
# `filter` returns false value if request is to your login page (but still
|
||||||
# increments the count) so request below the limit are not blocked until
|
# increments the count) so request below the limit are not blocked until
|
||||||
# they hit the limit. At that point, filter will return true and block.
|
# they hit the limit. At that point, filter will return true and block.
|
||||||
|
|
@ -142,15 +142,15 @@ clients until such time as they reach maxretry at which they are cut off as per
|
||||||
# The count for the IP is incremented if the return value is truthy.
|
# The count for the IP is incremented if the return value is truthy.
|
||||||
req.path = '/login' and req.method == 'post'
|
req.path = '/login' and req.method == 'post'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Throttles
|
### Throttles
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Throttle requests to 5 requests per second per ip
|
# Throttle requests to 5 requests per second per ip
|
||||||
Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
|
Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
|
||||||
# If the return value is truthy, the cache key for the return value
|
# If the return value is truthy, the cache key for the return value
|
||||||
# is incremented and compared with the limit. In this case:
|
# is incremented and compared with the limit. In this case:
|
||||||
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
||||||
|
|
@ -158,37 +158,37 @@ clients until such time as they reach maxretry at which they are cut off as per
|
||||||
# If falsy, the cache key is neither incremented nor checked.
|
# If falsy, the cache key is neither incremented nor checked.
|
||||||
|
|
||||||
req.ip
|
req.ip
|
||||||
end
|
end
|
||||||
|
|
||||||
# Throttle login attempts for a given email parameter to 6 reqs/minute
|
# Throttle login attempts for a given email parameter to 6 reqs/minute
|
||||||
# Return the email as a discriminator on POST /login requests
|
# Return the email as a discriminator on POST /login requests
|
||||||
Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
|
Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
|
||||||
req.params['email'] if req.path == '/login' && req.post?
|
req.params['email'] if req.path == '/login' && req.post?
|
||||||
end
|
end
|
||||||
|
|
||||||
# You can also set a limit using a proc instead of a number. For
|
# You can also set a limit using a proc instead of a number. For
|
||||||
# instance, after Rack::Auth::Basic has authenticated the user:
|
# instance, after Rack::Auth::Basic has authenticated the user:
|
||||||
limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
|
limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
|
||||||
Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
|
Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
|
||||||
req.ip
|
req.ip
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tracks
|
### Tracks
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Track requests from a special user agent
|
# Track requests from a special user agent
|
||||||
Rack::Attack.track("special_agent") do |req|
|
Rack::Attack.track("special_agent") do |req|
|
||||||
req.user_agent == "SpecialAgent"
|
req.user_agent == "SpecialAgent"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Track it using ActiveSupport::Notification
|
# Track it using ActiveSupport::Notification
|
||||||
ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
|
ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
|
||||||
if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
|
if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
|
||||||
Rails.logger.info "special_agent: #{req.path}"
|
Rails.logger.info "special_agent: #{req.path}"
|
||||||
STATSD.increment("special_agent")
|
STATSD.increment("special_agent")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|
@ -196,13 +196,13 @@ clients until such time as they reach maxretry at which they are cut off as per
|
||||||
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).
|
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).
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
Rack::Attack.blacklisted_response = lambda do |env|
|
Rack::Attack.blacklisted_response = lambda do |env|
|
||||||
# Using 503 because it may make attacker think that they have successfully
|
# Using 503 because it may make attacker think that they have successfully
|
||||||
# DOSed the site. Rack::Attack returns 401 for blacklists by default
|
# DOSed the site. Rack::Attack returns 401 for blacklists by default
|
||||||
[ 503, {}, ['Blocked']]
|
[ 503, {}, ['Blocked']]
|
||||||
end
|
end
|
||||||
|
|
||||||
Rack::Attack.throttled_response = lambda do |env|
|
Rack::Attack.throttled_response = lambda do |env|
|
||||||
# name and other data about the matched throttle
|
# name and other data about the matched throttle
|
||||||
body = [
|
body = [
|
||||||
env['rack.attack.matched'],
|
env['rack.attack.matched'],
|
||||||
|
|
@ -213,13 +213,13 @@ Customize the response of blacklisted and throttled requests using an object tha
|
||||||
# Using 503 because it may make attacker think that they have successfully
|
# Using 503 because it may make attacker think that they have successfully
|
||||||
# DOSed the site. Rack::Attack returns 429 for throttling by default
|
# DOSed the site. Rack::Attack returns 429 for throttling by default
|
||||||
[ 503, {}, [body]]
|
[ 503, {}, [body]]
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
|
For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
|
request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Logging & Instrumentation
|
## Logging & Instrumentation
|
||||||
|
|
@ -229,9 +229,9 @@ Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/
|
||||||
You can subscribe to 'rack.attack' events and log it, graph it, etc:
|
You can subscribe to 'rack.attack' events and log it, graph it, etc:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
|
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
|
||||||
puts req.inspect
|
puts req.inspect
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue