mirror of
https://github.com/samsonjs/rack-attack.git
synced 2026-04-26 14:57:47 +00:00
Merge pull request #181 from renee-travisci/language_change_suggestion
Rename blacklist/whitelist to blocklist/safelist.
This commit is contained in:
commit
4f462c33dc
13 changed files with 121 additions and 75 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,3 +3,5 @@ bin
|
||||||
.bundle
|
.bundle
|
||||||
*.gem
|
*.gem
|
||||||
*.gemfile.lock
|
*.gemfile.lock
|
||||||
|
.ruby-version
|
||||||
|
.ruby-gemset
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
## v4.2.0 - 26 Oct 2014
|
## v4.2.0 - 26 Oct 2014
|
||||||
- Throttle's `period` argument now takes a proc as well as a number (thanks @gsamokovarov)
|
- Throttle's `period` argument now takes a proc as well as a number (thanks @gsamokovarov)
|
||||||
- Invoke the `#call` method on `blacklist_response` and `throttle_response` instead of `#[]`, as per the Rack spec. (thanks @gsamokovarov)
|
- Invoke the `#call` method on `blocklist_response` and `throttle_response` instead of `#[]`, as per the Rack spec. (thanks @gsamokovarov)
|
||||||
|
|
||||||
## v4.1.1 - 11 Sept 2014
|
## v4.1.1 - 11 Sept 2014
|
||||||
- Fix a race condition in throttles that could allow more requests than intended.
|
- Fix a race condition in throttles that could allow more requests than intended.
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
## v4.1.0 - 22 May 2014
|
## v4.1.0 - 22 May 2014
|
||||||
- Tracks take an optional limit and period to only notify once a threshold
|
- Tracks take an optional limit and period to only notify once a threshold
|
||||||
is reached (similar to throttles). Thanks @chiliburger!
|
is reached (similar to throttles). Thanks @chiliburger!
|
||||||
- Default throttled & blacklist responses have Content-Type: text/plain
|
- Default throttled & blocklist responses have Content-Type: text/plain
|
||||||
- Rack::Attack.clear! resets tracks
|
- Rack::Attack.clear! resets tracks
|
||||||
|
|
||||||
## v4.0.1 - 14 May 2014
|
## v4.0.1 - 14 May 2014
|
||||||
|
|
@ -49,7 +49,7 @@
|
||||||
* Test more dalli versions.
|
* Test more dalli versions.
|
||||||
|
|
||||||
## v3.0.0 - 15 March 2014
|
## v3.0.0 - 15 March 2014
|
||||||
* Change default blacklisted response to 403 Forbidden (thanks @carpodaster).
|
* Change default blocklisted response to 403 Forbidden (thanks @carpodaster).
|
||||||
* Fail gracefully when Redis store is not available; rescue exeption and don't
|
* Fail gracefully when Redis store is not available; rescue exeption and don't
|
||||||
throttle request. (thanks @wkimeria)
|
throttle request. (thanks @wkimeria)
|
||||||
* TravisCI runs integration tests.
|
* TravisCI runs integration tests.
|
||||||
|
|
@ -62,7 +62,7 @@
|
||||||
## v2.2.1 - 13 August 2013
|
## v2.2.1 - 13 August 2013
|
||||||
* Add license to gemspec
|
* Add license to gemspec
|
||||||
* Support ruby version 1.9.2
|
* Support ruby version 1.9.2
|
||||||
* Change default blacklisted response code from 503 to 401; throttled response
|
* Change default blocklisted response code from 503 to 401; throttled response
|
||||||
from 503 to 429.
|
from 503 to 429.
|
||||||
|
|
||||||
## v2.2.0 - 20 June 2013
|
## v2.2.0 - 20 June 2013
|
||||||
|
|
|
||||||
48
README.md
48
README.md
|
|
@ -2,7 +2,7 @@
|
||||||
*Rack middleware for blocking & throttling abusive requests*
|
*Rack middleware for blocking & throttling abusive requests*
|
||||||
|
|
||||||
Rack::Attack is a rack middleware to protect your web app from bad clients.
|
Rack::Attack is a rack middleware to protect your web app from bad clients.
|
||||||
It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
|
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)).
|
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)).
|
||||||
|
|
||||||
|
|
@ -53,14 +53,14 @@ Optionally configure the cache store for throttling or fail2ban filtering:
|
||||||
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 and fail2ban filtering; 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 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).
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
The Rack::Attack middleware compares each request against *whitelists*, *blacklists*, *throttles*, and *tracks* that you define. There are none by default.
|
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 **whitelist**, it is allowed.
|
* If the request matches any **safelist**, it is allowed.
|
||||||
* Otherwise, if the request matches any **blacklist**, it is blocked.
|
* 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, 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.
|
* Otherwise, all **tracks** are checked, and the request is allowed.
|
||||||
|
|
||||||
|
|
@ -70,10 +70,10 @@ The algorithm is actually more concise in code: See [Rack::Attack.call](https://
|
||||||
def call(env)
|
def call(env)
|
||||||
req = Rack::Attack::Request.new(env)
|
req = Rack::Attack::Request.new(env)
|
||||||
|
|
||||||
if whitelisted?(req)
|
if safelisted?(req)
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
elsif blacklisted?(req)
|
elsif blocklisted?(req)
|
||||||
self.class.blacklisted_response.call(env)
|
self.class.blocklisted_response.call(env)
|
||||||
elsif throttled?(req)
|
elsif throttled?(req)
|
||||||
self.class.throttled_response.call(env)
|
self.class.throttled_response.call(env)
|
||||||
else
|
else
|
||||||
|
|
@ -93,47 +93,47 @@ can cleanly monkey patch helper methods onto the
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
|
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/`.
|
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).
|
A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples).
|
||||||
|
|
||||||
### Whitelists
|
### safelists
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Always allow requests from localhost
|
# Always allow requests from localhost
|
||||||
# (blacklist & throttles are skipped)
|
# (blocklist & throttles are skipped)
|
||||||
Rack::Attack.whitelist('allow from localhost') do |req|
|
Rack::Attack.safelist('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 || '::1' == req.ip
|
'127.0.0.1' == req.ip || '::1' == req.ip
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Blacklists
|
### blocklists
|
||||||
|
|
||||||
```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.blocklist('block 1.2.3.4') do |req|
|
||||||
# Requests are blocked if the return value is truthy
|
# Requests 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.blocklist('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
|
||||||
|
|
||||||
`Fail2Ban.filter` can be used within a blacklist to block all requests from misbehaving clients.
|
`Fail2Ban.filter` can be used within a blocklist to block all requests from misbehaving clients.
|
||||||
This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
|
This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
|
||||||
See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
|
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 blacklist and use a unique discriminator for each fail2ban filter.
|
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.
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# Block suspicious requests for '/etc/password' or wordpress specific paths.
|
# 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.
|
# 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.blocklist('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("pentesters-#{req.ip}", :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
|
Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
|
||||||
|
|
@ -147,7 +147,7 @@ Rack::Attack.blacklist('fail2ban pentesters') do |req|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that `Fail2Ban` filters are not automatically scoped to the blacklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`.
|
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
|
||||||
`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
|
||||||
|
|
@ -155,7 +155,7 @@ clients until such time as they reach maxretry at which they are cut off as per
|
||||||
```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.blocklist('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.
|
||||||
|
|
@ -220,12 +220,12 @@ end
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|
||||||
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 blocklisted 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.blocklisted_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 403 for blacklists by default
|
# DOSed the site. Rack::Attack returns 403 for blocklists by default
|
||||||
[ 503, {}, ['Blocked']]
|
[ 503, {}, ['Blocked']]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -274,7 +274,7 @@ but it depends on how many checks you've configured, and how long they take.
|
||||||
Throttles usually require a network roundtrip to your cache server(s),
|
Throttles usually require a network roundtrip to your cache server(s),
|
||||||
so try to keep the number of throttle checks per request low.
|
so try to keep the number of throttle checks per request low.
|
||||||
|
|
||||||
If a request is blacklisted or throttled, the response is a very simple Rack response.
|
If a request is blocklisted or throttled, the response is a very simple Rack response.
|
||||||
A single typical ruby web server thread can block several hundred requests per second.
|
A single typical ruby web server thread can block several hundred requests per second.
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@ Rack::Attack.throttle "logins/email", :limit => 2, :period => 60 do |req|
|
||||||
req.post? && req.path == "/login" && req.params['email']
|
req.post? && req.path == "/login" && req.params['email']
|
||||||
end
|
end
|
||||||
|
|
||||||
# Blacklist bad IPs from accessing admin pages
|
# blocklist bad IPs from accessing admin pages
|
||||||
Rack::Attack.blacklist "bad_ips from logging in" do |req|
|
Rack::Attack.blocklist "bad_ips from logging in" do |req|
|
||||||
req.path =~ /^\/admin/ && bad_ips.include?(req.ip)
|
req.path =~ /^\/admin/ && bad_ips.include?(req.ip)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Whitelist a User-Agent
|
# safelist a User-Agent
|
||||||
Rack::Attack.whitelist 'internal user agent' do |req|
|
Rack::Attack.safelist 'internal user agent' do |req|
|
||||||
req.user_agent == 'InternalUserAgent'
|
req.user_agent == 'InternalUserAgent'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ class Rack::Attack
|
||||||
autoload :PathNormalizer, 'rack/attack/path_normalizer'
|
autoload :PathNormalizer, 'rack/attack/path_normalizer'
|
||||||
autoload :Check, 'rack/attack/check'
|
autoload :Check, 'rack/attack/check'
|
||||||
autoload :Throttle, 'rack/attack/throttle'
|
autoload :Throttle, 'rack/attack/throttle'
|
||||||
autoload :Whitelist, 'rack/attack/whitelist'
|
autoload :Safelist, 'rack/attack/safelist'
|
||||||
autoload :Blacklist, 'rack/attack/blacklist'
|
autoload :Blocklist, 'rack/attack/blocklist'
|
||||||
autoload :Track, 'rack/attack/track'
|
autoload :Track, 'rack/attack/track'
|
||||||
autoload :StoreProxy, 'rack/attack/store_proxy'
|
autoload :StoreProxy, 'rack/attack/store_proxy'
|
||||||
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
|
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
|
||||||
|
|
@ -19,14 +19,24 @@ class Rack::Attack
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
|
|
||||||
attr_accessor :notifier, :blacklisted_response, :throttled_response
|
attr_accessor :notifier, :blocklisted_response, :throttled_response
|
||||||
|
|
||||||
|
def safelist(name, &block)
|
||||||
|
self.safelists[name] = Safelist.new(name, block)
|
||||||
|
end
|
||||||
|
|
||||||
def whitelist(name, &block)
|
def whitelist(name, &block)
|
||||||
self.whitelists[name] = Whitelist.new(name, block)
|
warn "[DEPRECATION] 'whitelist' is deprecated. Please use 'safelist' instead."
|
||||||
|
safelist(name, &block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocklist(name, &block)
|
||||||
|
self.blocklists[name] = Blocklist.new(name, block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blacklist(name, &block)
|
def blacklist(name, &block)
|
||||||
self.blacklists[name] = Blacklist.new(name, block)
|
warn "[DEPRECATION] 'blacklist' is deprecated. Please use 'blocklist' instead."
|
||||||
|
blocklist(name, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def throttle(name, options, &block)
|
def throttle(name, options, &block)
|
||||||
|
|
@ -37,23 +47,43 @@ class Rack::Attack
|
||||||
self.tracks[name] = Track.new(name, options, block)
|
self.tracks[name] = Track.new(name, options, block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def whitelists; @whitelists ||= {}; end
|
def safelists; @safelists ||= {}; end
|
||||||
def blacklists; @blacklists ||= {}; end
|
def blocklists; @blocklists ||= {}; end
|
||||||
def throttles; @throttles ||= {}; end
|
def throttles; @throttles ||= {}; end
|
||||||
def tracks; @tracks ||= {}; end
|
def tracks; @tracks ||= {}; end
|
||||||
|
|
||||||
def whitelisted?(req)
|
def whitelists
|
||||||
whitelists.any? do |name, whitelist|
|
warn "[DEPRECATION] 'whitelists' is deprecated. Please use 'safelists' instead."
|
||||||
whitelist[req]
|
safelists
|
||||||
|
end
|
||||||
|
|
||||||
|
def blacklists
|
||||||
|
warn "[DEPRECATION] 'blacklists' is deprecated. Please use 'blocklists' instead."
|
||||||
|
blocklists
|
||||||
|
end
|
||||||
|
|
||||||
|
def safelisted?(req)
|
||||||
|
safelists.any? do |name, safelist|
|
||||||
|
safelist[req]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def blacklisted?(req)
|
def whitelisted?
|
||||||
blacklists.any? do |name, blacklist|
|
warn "[DEPRECATION] 'whitelisted?' is deprecated. Please use 'safelisted?' instead."
|
||||||
blacklist[req]
|
safelisted?
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocklisted?(req)
|
||||||
|
blocklists.any? do |name, blocklist|
|
||||||
|
blocklist[req]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blacklisted?
|
||||||
|
warn "[DEPRECATION] 'blacklisted?' is deprecated. Please use 'blocklisted?' instead."
|
||||||
|
blocklisted?
|
||||||
|
end
|
||||||
|
|
||||||
def throttled?(req)
|
def throttled?(req)
|
||||||
throttles.any? do |name, throttle|
|
throttles.any? do |name, throttle|
|
||||||
throttle[req]
|
throttle[req]
|
||||||
|
|
@ -75,14 +105,14 @@ class Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear!
|
def clear!
|
||||||
@whitelists, @blacklists, @throttles, @tracks = {}, {}, {}, {}
|
@safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {}
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set defaults
|
# Set defaults
|
||||||
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
||||||
@blacklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
|
@blocklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
|
||||||
@throttled_response = lambda {|env|
|
@throttled_response = lambda {|env|
|
||||||
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
retry_after = (env['rack.attack.match_data'] || {})[:period]
|
||||||
[429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
|
[429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
|
||||||
|
|
@ -96,10 +126,10 @@ class Rack::Attack
|
||||||
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
|
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
|
||||||
req = Rack::Attack::Request.new(env)
|
req = Rack::Attack::Request.new(env)
|
||||||
|
|
||||||
if whitelisted?(req)
|
if safelisted?(req)
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
elsif blacklisted?(req)
|
elsif blocklisted?(req)
|
||||||
self.class.blacklisted_response.call(env)
|
self.class.blocklisted_response.call(env)
|
||||||
elsif throttled?(req)
|
elsif throttled?(req)
|
||||||
self.class.throttled_response.call(env)
|
self.class.throttled_response.call(env)
|
||||||
else
|
else
|
||||||
|
|
@ -109,8 +139,8 @@ class Rack::Attack
|
||||||
end
|
end
|
||||||
|
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
def_delegators self, :whitelisted?,
|
def_delegators self, :safelisted?,
|
||||||
:blacklisted?,
|
:blocklisted?,
|
||||||
:throttled?,
|
:throttled?,
|
||||||
:tracked?
|
:tracked?
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
module Rack
|
module Rack
|
||||||
class Attack
|
class Attack
|
||||||
class Whitelist < Check
|
class Blocklist < Check
|
||||||
def initialize(name, block)
|
def initialize(name, block)
|
||||||
super
|
super
|
||||||
@type = :whitelist
|
@type = :blocklist
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
@ -8,7 +8,7 @@ module Rack
|
||||||
maxretry = options[:maxretry] or raise ArgumentError, "Must pass maxretry option"
|
maxretry = options[:maxretry] or raise ArgumentError, "Must pass maxretry option"
|
||||||
|
|
||||||
if banned?(discriminator)
|
if banned?(discriminator)
|
||||||
# Return true for blacklist
|
# Return true for blocklist
|
||||||
true
|
true
|
||||||
elsif yield
|
elsif yield
|
||||||
fail!(discriminator, bantime, findtime, maxretry)
|
fail!(discriminator, bantime, findtime, maxretry)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# Rack::Attack.whitelist("localhost") {|req| req.localhost? }
|
# Rack::Attack.safelist("localhost") {|req| req.localhost? }
|
||||||
#
|
#
|
||||||
module Rack
|
module Rack
|
||||||
class Attack
|
class Attack
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
module Rack
|
module Rack
|
||||||
class Attack
|
class Attack
|
||||||
class Blacklist < Check
|
class Safelist < Check
|
||||||
def initialize(name, block)
|
def initialize(name, block)
|
||||||
super
|
super
|
||||||
@type = :blacklist
|
@type = :safelist
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ describe 'Rack::Attack.Allow2Ban' do
|
||||||
@bantime = 60
|
@bantime = 60
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||||
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
|
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
|
||||||
Rack::Attack.blacklist('pentest') do |req|
|
Rack::Attack.blocklist('pentest') do |req|
|
||||||
Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
|
Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ describe 'Rack::Attack.Fail2Ban' do
|
||||||
@bantime = 60
|
@bantime = 60
|
||||||
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
|
||||||
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
|
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
|
||||||
Rack::Attack.blacklist('pentest') do |req|
|
Rack::Attack.blocklist('pentest') do |req|
|
||||||
Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
|
Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ describe 'Rack::Attack' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Rack::Attack.whitelist('valid IP') do |req|
|
Rack::Attack.safelist('valid IP') do |req|
|
||||||
req.remote_ip == "127.0.0.1"
|
req.remote_ip == "127.0.0.1"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ describe 'Rack::Attack' do
|
||||||
|
|
||||||
describe 'normalizing paths' do
|
describe 'normalizing paths' do
|
||||||
before do
|
before do
|
||||||
Rack::Attack.blacklist("banned_path") {|req| req.path == '/foo' }
|
Rack::Attack.blocklist("banned_path") {|req| req.path == '/foo' }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'blocks requests with trailing slash' do
|
it 'blocks requests with trailing slash' do
|
||||||
|
|
@ -14,47 +14,62 @@ describe 'Rack::Attack' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'blacklist' do
|
describe 'blocklist' do
|
||||||
before do
|
before do
|
||||||
@bad_ip = '1.2.3.4'
|
@bad_ip = '1.2.3.4'
|
||||||
Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
Rack::Attack.blocklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
|
||||||
end
|
end
|
||||||
|
|
||||||
it('has a blacklist') {
|
it('has a blocklist') {
|
||||||
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
|
Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true
|
||||||
|
}
|
||||||
|
|
||||||
|
it('has a blacklist with a deprication warning') {
|
||||||
|
stdout, stderror = capture_io do
|
||||||
|
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
|
||||||
|
end
|
||||||
|
assert_match "[DEPRECATION] 'blacklists' is deprecated. Please use 'blocklists' instead.", stderror
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "a bad request" do
|
describe "a bad request" do
|
||||||
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
|
||||||
it "should return a blacklist response" do
|
it "should return a blocklist response" do
|
||||||
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip
|
||||||
last_response.status.must_equal 403
|
last_response.status.must_equal 403
|
||||||
last_response.body.must_equal "Forbidden\n"
|
last_response.body.must_equal "Forbidden\n"
|
||||||
end
|
end
|
||||||
it "should tag the env" do
|
it "should tag the env" do
|
||||||
last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
|
last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
|
||||||
last_request.env['rack.attack.match_type'].must_equal :blacklist
|
last_request.env['rack.attack.match_type'].must_equal :blocklist
|
||||||
end
|
end
|
||||||
|
|
||||||
allow_ok_requests
|
allow_ok_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "and whitelist" do
|
describe "and safelist" do
|
||||||
before do
|
before do
|
||||||
@good_ua = 'GoodUA'
|
@good_ua = 'GoodUA'
|
||||||
Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua }
|
Rack::Attack.safelist("good ua") {|req| req.user_agent == @good_ua }
|
||||||
end
|
end
|
||||||
|
|
||||||
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
|
it('has a safelist'){ Rack::Attack.safelists.key?("good ua") }
|
||||||
describe "with a request match both whitelist & blacklist" do
|
|
||||||
|
it('has a whitelist with a deprication warning') {
|
||||||
|
stdout, stderror = capture_io do
|
||||||
|
Rack::Attack.whitelists.key?("good ua")
|
||||||
|
end
|
||||||
|
assert_match "[DEPRECATION] 'whitelists' is deprecated. Please use 'safelists' instead.", stderror
|
||||||
|
}
|
||||||
|
|
||||||
|
describe "with a request match both safelist & blocklist" do
|
||||||
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
|
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
|
||||||
it "should allow whitelists before blacklists" do
|
it "should allow safelists before blocklists" do
|
||||||
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
|
||||||
last_response.status.must_equal 200
|
last_response.status.must_equal 200
|
||||||
end
|
end
|
||||||
it "should tag the env" do
|
it "should tag the env" do
|
||||||
last_request.env['rack.attack.matched'].must_equal 'good ua'
|
last_request.env['rack.attack.matched'].must_equal 'good ua'
|
||||||
last_request.env['rack.attack.match_type'].must_equal :whitelist
|
last_request.env['rack.attack.match_type'].must_equal :safelist
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue