From 41c449f838139c12471b200c4b84f04ada03728a Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Wed, 23 Oct 2013 00:09:43 -0500 Subject: [PATCH] properly indent code sections Using three tick marks and double indenting is redundant. Doing both produces a readme with an odd visual flow. This change does not modify content, it only changes lefthand whitespace so the Readme on Github will be more coherent. --- README.md | 214 +++++++++++++++++++++++++++--------------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index d09403d..060b4eb 100644 --- a/README.md +++ b/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: ```ruby - # In your Gemfile - gem 'rack-attack' +# In your Gemfile +gem 'rack-attack' ``` Tell your app to use the Rack::Attack middleware. For Rails 3+ apps: ```ruby - # In config/application.rb - config.middleware.use Rack::Attack +# In config/application.rb +config.middleware.use Rack::Attack ``` Or for Rackup files: ```ruby - # In config.ru - use Rack::Attack +# In config.ru +use Rack::Attack ``` Optionally configure the cache store for throttling: ```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). @@ -56,20 +56,20 @@ 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): ```ruby - def call(env) - req = Rack::Request.new(env) +def call(env) + req = Rack::Request.new(env) - if whitelisted?(req) - @app.call(env) - elsif blacklisted?(req) - blacklisted_response[env] - elsif throttled?(req) - throttled_response[env] - else - tracked?(req) - @app.call(env) - end - end + if whitelisted?(req) + @app.call(env) + elsif blacklisted?(req) + blacklisted_response[env] + elsif throttled?(req) + throttled_response[env] + else + tracked?(req) + @app.call(env) + end +end ``` ## About Tracks @@ -85,27 +85,27 @@ A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) objec ### Whitelists ```ruby - # Always allow requests from localhost - # (blacklist & throttles are skipped) - Rack::Attack.whitelist('allow from localhost') do |req| - # Requests are allowed if the return value is truthy - '127.0.0.1' == req.ip - end +# Always allow requests from localhost +# (blacklist & throttles are skipped) +Rack::Attack.whitelist('allow from localhost') do |req| + # Requests are allowed if the return value is truthy + '127.0.0.1' == req.ip +end ``` ### Blacklists ```ruby - # Block requests from 1.2.3.4 - Rack::Attack.blacklist('block 1.2.3.4') do |req| - # Request are blocked if the return value is truthy - '1.2.3.4' == req.ip - end +# Block requests from 1.2.3.4 +Rack::Attack.blacklist('block 1.2.3.4') do |req| + # Request are blocked if the return value is truthy + '1.2.3.4' == req.ip +end - # Block logins from a bad user agent - Rack::Attack.blacklist('block bad UA logins') do |req| - req.path == '/login' && req.post? && req.user_agent == 'BadUA' - end +# Block logins from a bad user agent +Rack::Attack.blacklist('block bad UA logins') do |req| + req.path == '/login' && req.post? && req.user_agent == 'BadUA' +end ``` #### Fail2Ban @@ -116,79 +116,79 @@ See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0 how the parameters work. ```ruby - # Block requests containing '/etc/password' in the params. - # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes. - Rack::Attack.blacklist('fail2ban pentesters') do |req| - # `filter` returns truthy value if request fails, or if it's from a previously banned IP - # so the request is blocked - 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. - CGI.unescape(req.query_string) =~ %r{/etc/passwd} - end - end +# Block requests containing '/etc/password' in the params. +# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes. +Rack::Attack.blacklist('fail2ban pentesters') do |req| + # `filter` returns truthy value if request fails, or if it's from a previously banned IP + # so the request is blocked + 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. + CGI.unescape(req.query_string) =~ %r{/etc/passwd} + end +end ``` #### 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. ```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. - Rack::Attack.blacklist('allow2ban login scrapers') do |req| - # `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 - # they hit the limit. At that point, filter will return true and block. - Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do - # The count for the IP is incremented if the return value is truthy. - req.path = '/login' and req.method == 'post' - end - end +# Lockout IP addresses that are hammering your login page. +# After 20 requests in 1 minute, block all requests from that IP for 1 hour. +Rack::Attack.blacklist('allow2ban login scrapers') do |req| + # `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 + # they hit the limit. At that point, filter will return true and block. + Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do + # The count for the IP is incremented if the return value is truthy. + req.path = '/login' and req.method == 'post' + end +end ``` ### Throttles ```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. +# 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. - req.ip - end + req.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.seconds) do |req| - req.params['email'] if req.path == '/login' && req.post? - 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.seconds) do |req| + req.params['email'] if req.path == '/login' && req.post? +end - # You can also set a limit using a proc instead of a number. For - # instance, after Rack::Auth::Basic has authenticated the user: - 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| - req.ip - end +# You can also set a limit using a proc instead of a number. For +# instance, after Rack::Auth::Basic has authenticated the user: +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| + req.ip +end ``` ### Tracks ```ruby - # Track requests from a special user agent - Rack::Attack.track("special_agent") do |req| - req.user_agent == "SpecialAgent" - end +# Track requests from a special user agent +Rack::Attack.track("special_agent") do |req| + req.user_agent == "SpecialAgent" +end - # Track it using ActiveSupport::Notification - 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 - Rails.logger.info "special_agent: #{req.path}" - STATSD.increment("special_agent") - end - end +# Track it using ActiveSupport::Notification +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 + Rails.logger.info "special_agent: #{req.path}" + STATSD.increment("special_agent") + end +end ``` ## Responses @@ -196,30 +196,30 @@ 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). ```ruby - Rack::Attack.blacklisted_response = lambda do |env| - # Using 503 because it may make attacker think that they have successfully - # DOSed the site. Rack::Attack returns 401 for blacklists by default - [ 503, {}, ['Blocked']] - end +Rack::Attack.blacklisted_response = lambda do |env| + # Using 503 because it may make attacker think that they have successfully + # DOSed the site. Rack::Attack returns 401 for blacklists by default + [ 503, {}, ['Blocked']] +end - Rack::Attack.throttled_response = lambda do |env| - # name and other data about the matched throttle - body = [ - env['rack.attack.matched'], - env['rack.attack.match_type'], - env['rack.attack.match_data'] - ].inspect +Rack::Attack.throttled_response = lambda do |env| + # name and other data about the matched throttle + body = [ + env['rack.attack.matched'], + env['rack.attack.match_type'], + env['rack.attack.match_data'] + ].inspect - # Using 503 because it may make attacker think that they have successfully - # DOSed the site. Rack::Attack returns 429 for throttling by default - [ 503, {}, [body]] - end + # Using 503 because it may make attacker think that they have successfully + # DOSed the site. Rack::Attack returns 429 for throttling by default + [ 503, {}, [body]] +end ``` For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data: ```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 @@ -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: ```ruby - ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req| - puts req.inspect - end +ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req| + puts req.inspect +end ``` ## Testing