mirror of
https://github.com/samsonjs/rack-attack.git
synced 2026-03-25 09:25:49 +00:00
* docs: Improve Getting Started section * docs: Following Getting Started show Usage to the README reader * docs: Move the configuration tip to the Usage section * docs: Move the cache store configuration comment to Usage * docs: Clarify Responses title * docs: allow2ban also uses the cache store * docs: Improve Usage docs for blocking, safelisting and throttling * docs: Don't give the impression that the gem is not being maintained when it actually is * docs: Be a bit more clear about cache store in README * docs: Attempt to be a bit more concise in the README intro * docs: Clarify sentence
This commit is contained in:
parent
644ca8ff55
commit
4c1aa8b312
1 changed files with 169 additions and 85 deletions
254
README.md
254
README.md
|
|
@ -1,10 +1,8 @@
|
|||
# Rack::Attack!!!
|
||||
# Rack::Attack
|
||||
|
||||
*Rack middleware for blocking & throttling abusive requests*
|
||||
|
||||
Rack::Attack is a rack middleware to protect your web app from bad clients.
|
||||
It allows *safelisting*, *blocklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
|
||||
|
||||
Throttle and fail2ban 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)).
|
||||
Protect your Rails and Rack apps from bad clients. Rack::Attack lets you easily decide when to *allow*, *block* and *throttle* based on properties of the request.
|
||||
|
||||
See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
|
||||
|
||||
|
|
@ -12,99 +10,97 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac
|
|||
[](https://travis-ci.org/kickstarter/rack-attack)
|
||||
[](https://codeclimate.com/github/kickstarter/rack-attack)
|
||||
|
||||
## Looking for maintainers
|
||||
|
||||
I'm looking for new maintainers to help me support Rack::Attack. Check out
|
||||
[issue #219 for details](https://github.com/kickstarter/rack-attack/issues/219).
|
||||
|
||||
## Getting started
|
||||
|
||||
Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to your Gemfile with bundler:
|
||||
### 1. Installing
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
# In your Gemfile
|
||||
|
||||
gem 'rack-attack'
|
||||
```
|
||||
Tell your app to use the Rack::Attack middleware.
|
||||
For Rails apps:
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install rack-attack
|
||||
|
||||
### 2. Plugging into the application
|
||||
|
||||
Then tell your ruby web application to use rack-attack as a middleware.
|
||||
|
||||
a) For __rails__ applications:
|
||||
|
||||
```ruby
|
||||
# In config/application.rb
|
||||
|
||||
config.middleware.use Rack::Attack
|
||||
```
|
||||
|
||||
Or for Rackup files:
|
||||
b) For __rack__ applications:
|
||||
|
||||
```ruby
|
||||
# In config.ru
|
||||
|
||||
require "rack/attack"
|
||||
use Rack::Attack
|
||||
```
|
||||
|
||||
Add a `rack_attack.rb` file to `config/initializers/`:
|
||||
```ruby
|
||||
# In config/initializers/rack_attack.rb
|
||||
class Rack::Attack
|
||||
# your custom configuration...
|
||||
end
|
||||
```
|
||||
__IMPORTANT__: By default, rack-attack won't perform any blocking or throttling, until you specifically tell it what to protect against by configuring some rules.
|
||||
|
||||
## Usage
|
||||
|
||||
*Tip:* The example in the wiki is a great way to get started:
|
||||
[Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
|
||||
|
||||
Optionally configure the cache store for throttling or fail2ban filtering:
|
||||
Define rules by calling `Rack::Attack` public methods, in any file that runs when your application is being initialized. For rails applications this means creating a new file named `config/initializers/rack_attack.rb` and writing your rules there.
|
||||
|
||||
### Safelisting
|
||||
|
||||
Safelists have the most precedence, so any request matching a safelist would be allowed despite matching any number of blocklists or throttles.
|
||||
|
||||
#### `safelist_ip(ip_address_string)`
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
||||
# config/initializers/rack_attack.rb (for rails app)
|
||||
|
||||
Rack::Attack.safelist_ip("5.6.7.8")
|
||||
```
|
||||
|
||||
Note that `Rack::Attack.cache` is only used for throttling and fail2ban filtering; not blocklisting & safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
|
||||
#### `safelist_ip(ip_subnet_string)`
|
||||
|
||||
## How it works
|
||||
|
||||
The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default.
|
||||
|
||||
* If the request matches any **safelist**, it is allowed.
|
||||
* Otherwise, if the request matches any **blocklist**, it is blocked.
|
||||
* 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.
|
||||
* Otherwise, all **tracks** are checked, and the request is allowed.
|
||||
|
||||
The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
def call(env)
|
||||
req = Rack::Attack::Request.new(env)
|
||||
# config/initializers/rack_attack.rb (for rails app)
|
||||
|
||||
if safelisted?(req)
|
||||
@app.call(env)
|
||||
elsif blocklisted?(req)
|
||||
self.class.blocklisted_response.call(env)
|
||||
elsif throttled?(req)
|
||||
self.class.throttled_response.call(env)
|
||||
else
|
||||
tracked?(req)
|
||||
@app.call(env)
|
||||
end
|
||||
Rack::Attack.safelist_ip("5.6.7.0/24")
|
||||
```
|
||||
|
||||
#### `safelist(name, &block)`
|
||||
|
||||
Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be blocked, and falsy otherwise.
|
||||
|
||||
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
# config/initializers/rack_attack.rb (for rails apps)
|
||||
|
||||
# Provided that trusted users use an HTTP request header named APIKey
|
||||
Rack::Attack.safelist("mark any authenticated access safe") do |request|
|
||||
# Requests are allowed if the return value is truthy
|
||||
request.env["APIKey"] == "secret-string"
|
||||
end
|
||||
```
|
||||
|
||||
Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you
|
||||
can cleanly monkey patch helper methods onto the
|
||||
[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
|
||||
|
||||
## About Tracks
|
||||
|
||||
`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
|
||||
|
||||
## Usage
|
||||
|
||||
Define safelists, blocklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
|
||||
these go in an initializer in `config/initializers/`.
|
||||
A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples).
|
||||
|
||||
### Safelists
|
||||
|
||||
```ruby
|
||||
# Always allow requests from localhost
|
||||
# (blocklist & throttles are skipped)
|
||||
Rack::Attack.safelist('allow from localhost') do |req|
|
||||
|
|
@ -113,16 +109,44 @@ Rack::Attack.safelist('allow from localhost') do |req|
|
|||
end
|
||||
```
|
||||
|
||||
### Blocklists
|
||||
### Blocking
|
||||
|
||||
#### `blocklist_ip(ip_address_string)`
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
# Block requests from 1.2.3.4
|
||||
Rack::Attack.blocklist('block 1.2.3.4') do |req|
|
||||
# config/initializers/rack_attack.rb (for rails apps)
|
||||
|
||||
Rack::Attack.blocklist_ip("1.2.3.4")
|
||||
```
|
||||
|
||||
#### `blocklist_ip(ip_subnet_string)`
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
# config/initializers/rack_attack.rb (for rails apps)
|
||||
|
||||
Rack::Attack.blocklist_ip("1.2.0.0/16")
|
||||
```
|
||||
|
||||
#### `blocklist(name, &block)`
|
||||
|
||||
Name your custom blocklist and make your ruby-block argument returna a truthy value if you want the request to be blocked, and falsy otherwise.
|
||||
|
||||
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
# config/initializers/rack_attack.rb (for rails apps)
|
||||
|
||||
Rack::Attack.blocklist("block all access to admin") do |request|
|
||||
# Requests are blocked if the return value is truthy
|
||||
'1.2.3.4' == req.ip
|
||||
request.path.start_with?("/admin")
|
||||
end
|
||||
|
||||
# Block logins from a bad user agent
|
||||
Rack::Attack.blocklist('block bad UA logins') do |req|
|
||||
req.path == '/login' && req.post? && req.user_agent == 'BadUA'
|
||||
end
|
||||
|
|
@ -135,6 +159,8 @@ This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Ma
|
|||
See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
|
||||
how the parameters work. For multiple filters, be sure to put each filter in a separate blocklist and use a unique discriminator for each fail2ban filter.
|
||||
|
||||
Fail2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
||||
|
||||
```ruby
|
||||
# Block suspicious requests for '/etc/password' or wordpress specific paths.
|
||||
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
|
||||
|
|
@ -155,8 +181,12 @@ end
|
|||
Note that `Fail2Ban` filters are not automatically scoped to the blocklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`.
|
||||
|
||||
#### Allow2Ban
|
||||
|
||||
`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.
|
||||
|
||||
Allow2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
||||
|
||||
```ruby
|
||||
# Lockout IP addresses that are hammering your login page.
|
||||
# After 20 requests in 1 minute, block all requests from that IP for 1 hour.
|
||||
|
|
@ -171,33 +201,40 @@ Rack::Attack.blocklist('allow2ban login scrapers') do |req|
|
|||
end
|
||||
```
|
||||
|
||||
### Throttling
|
||||
|
||||
### Throttles
|
||||
Throttle state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present).
|
||||
|
||||
#### `throttle(name, options, &block)`
|
||||
|
||||
Name your custom throttle, provide `limit` and `period` as options, and make your ruby-block argument return the __discriminator__. This discriminator is how you tell rack-attack whether you're limiting per IP address, per user email or any other.
|
||||
|
||||
The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request).
|
||||
|
||||
E.g.
|
||||
|
||||
```ruby
|
||||
# Throttle requests to 5 requests per second per ip
|
||||
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
|
||||
# is incremented and compared with the limit. In this case:
|
||||
# "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
|
||||
#
|
||||
# If falsy, the cache key is neither incremented nor checked.
|
||||
# config/initializers/rack_attack.rb (for rails apps)
|
||||
|
||||
req.ip
|
||||
Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request|
|
||||
request.ip
|
||||
end
|
||||
|
||||
# Throttle login attempts for a given email parameter to 6 reqs/minute
|
||||
# Return the email as a discriminator on POST /login requests
|
||||
Rack::Attack.throttle('logins/email', limit: 6, period: 60) do |req|
|
||||
req.params['email'] if req.path == '/login' && req.post?
|
||||
Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req|
|
||||
if req.path == '/login' && req.post?
|
||||
req.params['email']
|
||||
end
|
||||
end
|
||||
|
||||
# You can also set a limit and period using a proc. For instance, after
|
||||
# Rack::Auth::Basic has authenticated the user:
|
||||
limit_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
|
||||
period_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 1.second : 1.minute}
|
||||
Rack::Attack.throttle('req/ip', limit: limit_proc, period: period_proc) do |req|
|
||||
req.ip
|
||||
limit_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 100 : 1 }
|
||||
period_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 1 : 60 }
|
||||
|
||||
Rack::Attack.throttle('request per ip', limit: limit_proc, period: period_proc) do |request|
|
||||
request.ip
|
||||
end
|
||||
```
|
||||
|
||||
|
|
@ -223,7 +260,17 @@ ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, r
|
|||
end
|
||||
```
|
||||
|
||||
## Responses
|
||||
### Cache store configuration
|
||||
|
||||
Throttle, allow2ban and fail2ban state is stored in a configurable cache (which defaults to `Rails.cache` if present), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
|
||||
|
||||
```ruby
|
||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
|
||||
```
|
||||
|
||||
Note that `Rack::Attack.cache` is only used for throttling, allow2ban and fail2ban filtering; not blocklisting and safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
|
||||
|
||||
## Customizing responses
|
||||
|
||||
Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
|
||||
|
||||
|
|
@ -288,6 +335,43 @@ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, r
|
|||
end
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default.
|
||||
|
||||
* If the request matches any **safelist**, it is allowed.
|
||||
* Otherwise, if the request matches any **blocklist**, it is blocked.
|
||||
* 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.
|
||||
* Otherwise, all **tracks** are checked, and the request is allowed.
|
||||
|
||||
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
|
||||
def call(env)
|
||||
req = Rack::Attack::Request.new(env)
|
||||
|
||||
if safelisted?(req)
|
||||
@app.call(env)
|
||||
elsif blocklisted?(req)
|
||||
self.class.blocklisted_response.call(env)
|
||||
elsif throttled?(req)
|
||||
self.class.throttled_response.call(env)
|
||||
else
|
||||
tracked?(req)
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you
|
||||
can cleanly monkey patch helper methods onto the
|
||||
[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb).
|
||||
|
||||
### About Tracks
|
||||
|
||||
`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
|
||||
|
|
|
|||
Loading…
Reference in a new issue