PageRenderTime 73ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/README.md

https://github.com/rickygu/rack-attack
Markdown | 287 lines | 217 code | 70 blank | 0 comment | 0 complexity | b3b0d4566e102bd61f496c1050d2a327 MD5 | raw file
  1. # Rack::Attack!!!
  2. *Rack middleware for blocking & throttling abusive requests*
  3. Rack::Attack is a rack middleware to protect your web app from bad clients.
  4. It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
  5. 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)).
  6. See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack.
  7. [![Gem Version](https://badge.fury.io/rb/rack-attack.png)](http://badge.fury.io/rb/rack-attack)
  8. [![Build Status](https://travis-ci.org/kickstarter/rack-attack.png?branch=master)](https://travis-ci.org/kickstarter/rack-attack)
  9. [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.png)](https://codeclimate.com/github/kickstarter/rack-attack)
  10. ## Getting started
  11. Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
  12. ```ruby
  13. # In your Gemfile
  14. gem 'rack-attack'
  15. ```
  16. Tell your app to use the Rack::Attack middleware.
  17. For Rails 3+ apps:
  18. ```ruby
  19. # In config/application.rb
  20. config.middleware.use Rack::Attack
  21. ```
  22. Or for Rackup files:
  23. ```ruby
  24. # In config.ru
  25. use Rack::Attack
  26. ```
  27. Add a `rack-attack.rb` file to `config/initalizers/`:
  28. ```ruby
  29. # In config/initializers/rack-attack.rb
  30. module Rack::Attack
  31. # your custom configuration...
  32. end
  33. ```
  34. *Tip:* The example in the wiki is a great way to get started:
  35. [Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration)
  36. Optionally configure the cache store for throttling:
  37. ```ruby
  38. Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
  39. ```
  40. 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).
  41. ## How it works
  42. The Rack::Attack middleware compares each request against *whitelists*, *blacklists*, *throttles*, and *tracks* that you define. There are none by default.
  43. * If the request matches any **whitelist**, it is allowed.
  44. * Otherwise, if the request matches any **blacklist**, it is blocked.
  45. * 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.
  46. * Otherwise, all **tracks** are checked, and the request is allowed.
  47. The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
  48. ```ruby
  49. def call(env)
  50. req = Rack::Request.new(env)
  51. if whitelisted?(req)
  52. @app.call(env)
  53. elsif blacklisted?(req)
  54. blacklisted_response[env]
  55. elsif throttled?(req)
  56. throttled_response[env]
  57. else
  58. tracked?(req)
  59. @app.call(env)
  60. end
  61. end
  62. ```
  63. ## About Tracks
  64. `Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes.
  65. ## Usage
  66. Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
  67. these go in an initializer in `config/initializers/`.
  68. A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) object is passed to the block (named 'req' in the examples).
  69. ### Whitelists
  70. ```ruby
  71. # Always allow requests from localhost
  72. # (blacklist & throttles are skipped)
  73. Rack::Attack.whitelist('allow from localhost') do |req|
  74. # Requests are allowed if the return value is truthy
  75. '127.0.0.1' == req.ip
  76. end
  77. ```
  78. ### Blacklists
  79. ```ruby
  80. # Block requests from 1.2.3.4
  81. Rack::Attack.blacklist('block 1.2.3.4') do |req|
  82. # Request are blocked if the return value is truthy
  83. '1.2.3.4' == req.ip
  84. end
  85. # Block logins from a bad user agent
  86. Rack::Attack.blacklist('block bad UA logins') do |req|
  87. req.path == '/login' && req.post? && req.user_agent == 'BadUA'
  88. end
  89. ```
  90. #### Fail2Ban
  91. `Fail2Ban.filter` can be used within a blacklist to block all requests from misbehaving clients.
  92. This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
  93. See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
  94. how the parameters work.
  95. ```ruby
  96. # Block requests containing '/etc/password' in the params.
  97. # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
  98. Rack::Attack.blacklist('fail2ban pentesters') do |req|
  99. # `filter` returns truthy value if request fails, or if it's from a previously banned IP
  100. # so the request is blocked
  101. Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
  102. # The count for the IP is incremented if the return value is truthy.
  103. CGI.unescape(req.query_string) =~ %r{/etc/passwd}
  104. end
  105. end
  106. ```
  107. #### Allow2Ban
  108. `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
  109. clients until such time as they reach maxretry at which they are cut off as per normal.
  110. ```ruby
  111. # Lockout IP addresses that are hammering your login page.
  112. # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
  113. Rack::Attack.blacklist('allow2ban login scrapers') do |req|
  114. # `filter` returns false value if request is to your login page (but still
  115. # increments the count) so request below the limit are not blocked until
  116. # they hit the limit. At that point, filter will return true and block.
  117. Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
  118. # The count for the IP is incremented if the return value is truthy.
  119. req.path == '/login' and req.post?
  120. end
  121. end
  122. ```
  123. ### Throttles
  124. ```ruby
  125. # Throttle requests to 5 requests per second per ip
  126. Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
  127. # If the return value is truthy, the cache key for the return value
  128. # is incremented and compared with the limit. In this case:
  129. # "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
  130. #
  131. # If falsy, the cache key is neither incremented nor checked.
  132. req.ip
  133. end
  134. # Throttle login attempts for a given email parameter to 6 reqs/minute
  135. # Return the email as a discriminator on POST /login requests
  136. Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
  137. req.params['email'] if req.path == '/login' && req.post?
  138. end
  139. # You can also set a limit using a proc instead of a number. For
  140. # instance, after Rack::Auth::Basic has authenticated the user:
  141. limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
  142. Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
  143. req.ip
  144. end
  145. ```
  146. ### Tracks
  147. ```ruby
  148. # Track requests from a special user agent
  149. Rack::Attack.track("special_agent") do |req|
  150. req.user_agent == "SpecialAgent"
  151. end
  152. # Track it using ActiveSupport::Notification
  153. ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
  154. if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
  155. Rails.logger.info "special_agent: #{req.path}"
  156. STATSD.increment("special_agent")
  157. end
  158. end
  159. ```
  160. ## Responses
  161. 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).
  162. ```ruby
  163. Rack::Attack.blacklisted_response = lambda do |env|
  164. # Using 503 because it may make attacker think that they have successfully
  165. # DOSed the site. Rack::Attack returns 403 for blacklists by default
  166. [ 503, {}, ['Blocked']]
  167. end
  168. Rack::Attack.throttled_response = lambda do |env|
  169. # name and other data about the matched throttle
  170. body = [
  171. env['rack.attack.matched'],
  172. env['rack.attack.match_type'],
  173. env['rack.attack.match_data']
  174. ].inspect
  175. # Using 503 because it may make attacker think that they have successfully
  176. # DOSed the site. Rack::Attack returns 429 for throttling by default
  177. [ 503, {}, [body]]
  178. end
  179. ```
  180. For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
  181. ```ruby
  182. request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
  183. ```
  184. ## Logging & Instrumentation
  185. Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API if available.
  186. You can subscribe to 'rack.attack' events and log it, graph it, etc:
  187. ```ruby
  188. ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
  189. puts req.inspect
  190. end
  191. ```
  192. ## Testing
  193. A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will
  194. need to enable the cache in your development environment. See [Caching with Rails](http://guides.rubyonrails.org/caching_with_rails.html)
  195. for more on how to do this.
  196. ## Performance
  197. The overhead of running Rack::Attack is typically negligible (a few milliseconds per request),
  198. but it depends on how many checks you've configured, and how long they take.
  199. Throttles usually require a network roundtrip to your cache server(s),
  200. so try to keep the number of throttle checks per request low.
  201. If a request is blacklisted or throttled, the response is a very simple Rack response.
  202. A single typical ruby web server thread can block several hundred requests per second.
  203. 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).
  204. ## Motivation
  205. Abusive clients range from malicious login crackers to naively-written scrapers.
  206. They hinder the security, performance, & availability of web applications.
  207. It is impractical if not impossible to block abusive clients completely.
  208. Rack::Attack aims to let developers quickly mitigate abusive requests and rely
  209. less on short-term, one-off hacks to block a particular attack.
  210. ## Mailing list
  211. New releases of Rack::Attack are announced on
  212. <rack.attack.announce@librelist.com>. To subscribe, just send an email to
  213. <rack.attack.announce@librelist.com>. See the
  214. [archives](http://librelist.com/browser/rack.attack.announce/).
  215. ## License
  216. Copyright Kickstarter, Inc.
  217. Released under an [MIT License](http://opensource.org/licenses/MIT).