From b0da52a1bd5d254dbde54ef1b1b8b2f7efc50722 Mon Sep 17 00:00:00 2001 From: Leo Arnold Date: Mon, 8 Oct 2018 18:46:47 +0200 Subject: [PATCH] Move Wiki examples into version control. Closes #324 --- README.md | 8 +-- docs/advanced_configuration.md | 93 ++++++++++++++++++++++++++++++++++ docs/example_configuration.md | 83 ++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 docs/advanced_configuration.md create mode 100644 docs/example_configuration.md diff --git a/README.md b/README.md index 8aa92cf..03b714c 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ __IMPORTANT__: By default, rack-attack won't perform any blocking or throttling, ## 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) +*Tip:*If you just want to get going asap, then you can take our [example configuration](docs/example_configuration.md) +and tailor it to your needs, or check out the [advanced configuration](docs/advanced_configuration.md) examples. 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. @@ -378,7 +378,7 @@ The Rack::Attack middleware compares each request against *safelists*, *blocklis * 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): +The algorithm is actually more concise in code: See [Rack::Attack.call](lib/rack/attack.rb): ```ruby def call(env) @@ -399,7 +399,7 @@ 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). +[request object](lib/rack/attack/request.rb). ### About Tracks diff --git a/docs/advanced_configuration.md b/docs/advanced_configuration.md new file mode 100644 index 0000000..b4f877a --- /dev/null +++ b/docs/advanced_configuration.md @@ -0,0 +1,93 @@ +## Advanced Configuration + +If you're feeling ambitious or you have a very particular use-case for Rack::Attack, these advanced configurations may help. + +:beetle::warning: Much of this code is untested. Copy-paste at your own risk! + +### Exponential Backoff + +By layering throttles with linearly increasing limits and exponentially increasing periods, you can mimic an exponential backoff throttle. See [#106](https://github.com/kickstarter/rack-attack/issues/106) for more discussion. + +```ruby +# Allows 20 requests in 8 seconds +# 40 requests in 64 seconds +# ... +# 100 requests in 0.38 days (~250 requests/day) +(1..5).each do |level| + throttle("logins/ip/#{level}", :limit => (20 * level), :period => (8 ** level).seconds) do |req| + if req.path == '/login' && req.post? + req.ip + end + end +end +``` + +### Rack::Attack::Request Helpers + +You can define helpers on requests like `localhost?` or `subdomain` by monkey-patching `Rack::Attack::Request`. See [#73](https://github.com/kickstarter/rack-attack/issues/73) for more discussion. + +```ruby +class Rack::Attack::Request < ::Rack::Request + def localhost? + ip == "127.0.0.1" + end +end + +Rack::Attack.safelist("localhost") { |req| req.localhost? } +``` + +### Blocklisting From ENV Variables + +You can have `Rack::Attack` configure its blocklists from ENV variables to simplify maintenance. See [#110](https://github.com/kickstarter/rack-attack/issues/110) for more discussion. + +```ruby +class Rack::Attack + # Split on a comma with 0 or more spaces after it. + # E.g. ENV['HEROKU_VARIABLE'] = "foo.com, bar.com" + # spammers = ["foo.com", "bar.com"] + spammers = ENV['HEROKU_VARIABLE'].split(/,\s*/) + + # Turn spammers array into a regexp + spammer_regexp = Regexp.union(spammers) # /foo\.com|bar\.com/ + blocklist("block referer spam") do |request| + request.referer =~ spammer_regexp + end +end +``` + +### Reset Specific Throttles + +By doing a bunch of monkey-patching, you can add a helper for resetting specific throttles. The implementation is kind of long, so see [#113](https://github.com/kickstarter/rack-attack/issues/113) for more discussion. + +```ruby +Rack::Attack.reset_throttle "logins/email", "user@example.com" +``` + +### Blocklisting From Rails.cache + +You can configure blocklists to check values stored in `Rails.cache` to allow setting blocklists from inside your application. See [#111](https://github.com/kickstarter/rack-attack/issues/111) for more discussion. + +```ruby +# Block attacks from IPs in cache +# To add an IP: Rails.cache.write("block 1.2.3.4", true, expires_in: 2.days) +# To remove an IP: Rails.cache.delete("block 1.2.3.4") +Rack::Attack.blocklist("block IP") do |req| + Rails.cache.read("block #{req.ip}") +end +``` + +### Throttle Basic Auth Crackers + +An example implementation for blocking hackers who spam basic auth attempts. See [#47](https://github.com/kickstarter/rack-attack/issues/47) for more discussion. + +```ruby +# After 5 requests with incorrect auth in 1 minute, +# block all requests from that IP for 1 hour. +Rack::Attack.blocklist('basic auth crackers') do |req| + Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 5, :findtime => 1.minute, :bantime => 1.hour) do + # Return true if the authorization header not incorrect + auth = Rack::Auth::Basic::Request.new(req.env) + auth.credentials != [my_username, my_password] + end +end +``` \ No newline at end of file diff --git a/docs/example_configuration.md b/docs/example_configuration.md new file mode 100644 index 0000000..706ed99 --- /dev/null +++ b/docs/example_configuration.md @@ -0,0 +1,83 @@ +## Example Configuration + +If you just go ahead and copy this to `/config/initializers/rack_attack.rb`, then you'll be safe from 95% of bad requests. This won't stop sophisticated hackers, but at least you can sleep more soundly knowing that your application isn't going to be accidentally taken down by a misconfigured web scraper in the middle of the night. If this isn't enough for you, check out [Advanced Configuration](advanced_configuration.md) too. + +```ruby +class Rack::Attack + + ### Configure Cache ### + + # If you don't want to use Rails.cache (Rack::Attack's default), then + # configure it here. + # + # Note: The store is only used for throttling (not blacklisting and + # whitelisting). It must implement .increment and .write like + # ActiveSupport::Cache::Store + + # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + ### Throttle Spammy Clients ### + + # If any single client IP is making tons of requests, then they're + # probably malicious or a poorly-configured scraper. Either way, they + # don't deserve to hog all of the app server's CPU. Cut them off! + # + # Note: If you're serving assets through rack, those requests may be + # counted by rack-attack and this throttle may be activated too + # quickly. If so, enable the condition to exclude them from tracking. + + # Throttle all requests by IP (60rpm) + # + # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}" + throttle('req/ip', limit: 300, period: 5.minutes) do |req| + req.ip # unless req.path.start_with?('/assets') + end + + ### Prevent Brute-Force Login Attacks ### + + # The most common brute-force login attack is a brute-force password + # attack where an attacker simply tries a large number of emails and + # passwords to see if any credentials match. + # + # Another common method of attack is to use a swarm of computers with + # different IPs to try brute-forcing a password for a specific account. + + # Throttle POST requests to /login by IP address + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" + throttle('logins/ip', limit: 5, period: 20.seconds) do |req| + if req.path == '/login' && req.post? + req.ip + end + end + + # Throttle POST requests to /login by email param + # + # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.email}" + # + # Note: This creates a problem where a malicious user could intentionally + # throttle logins for another user and force their login requests to be + # denied, but that's not very common and shouldn't happen to you. (Knock + # on wood!) + throttle("logins/email", limit: 5, period: 20.seconds) do |req| + if req.path == '/login' && req.post? + # return the email if present, nil otherwise + req.params['email'].presence + end + end + + ### Custom Throttle Response ### + + # By default, Rack::Attack returns an HTTP 429 for throttled responses, + # which is just fine. + # + # If you want to return 503 so that the attacker might be fooled into + # believing that they've successfully broken your app (or you just want to + # customize the response), then uncomment these lines. + # self.throttled_response = lambda do |env| + # [ 503, # status + # {}, # headers + # ['']] # body + # end +end +``` \ No newline at end of file