Version Tests Code Climate GitHub Sponsors

chop-chop

Rack::Dedos

Somewhat more radical filters designed to decimate malicious requests during a denial-of-service (DoS) attack by chopping their connection well before your Rack app wastes any significant resources on them – ouch!

The filters have been proven to work against certain DoS attacks, however, they might also block IPs behind proxies or VPNs. Make sure you have understood how the filters are triggered and consider this middleware a last resort only to be enabled during an attack.

Thank you for supporting free and open-source software by sponsoring on GitHub or on Donorbox. Any gesture is appreciated, from a single Euro for a ☕️ cup of coffee to 🍹 early retirement.

Install

Security

This gem is cryptographically signed in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now:

gem cert --add <(curl -Ls https://raw.github.com/svoop/rack-dedos/main/certs/svoop.pem)

Bundler

Add the following to the Gemfile or gems.rb of your Bundler powered Ruby project:

gem 'rack-dedos'

And then install the bundle:

bundle install --trust-policy MediumSecurity

Configuration

Given the drastic nature of the filters, you should use this middleware for production environments only and/or if an environment variable like UNDER_ATTACK is set to true.

Rails

# config/application.rb
class Application < Rails::Application
  if Rails.env.production? && ActiveModel::Type::Boolean.new.cast(ENV['UNDER_ATTACK'])
    config.middleware.use Rack::Dedos
  end
end

Rackup

#!/usr/bin/env rackup
require 'rack/dedos'

if %w(true t on 1).include? ENV['UNDER_ATTACK']
  use Rack::Dedos
end

run lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, world!\n"] }

Response

If a request is classified as malicious by at least one filter, the middleware responds with:

403 Forbidden (Temporarily Blocked by Rules)

This is the most appropriate response, however, feel free to trick the requester by tweaking this:

use Rack::Dedos,
  status: 503,
  text: "Temporary Server Error"

Filters

By default, all filters described below are applied. You can exclude certain filters:

use Rack::Dedos,
  except: [:user_agent]

To only apply one specific filter, use the corresponding class as shown below.

User Agent Filter

use Rack::Dedos::Filters::UserAgent,
  cache_url: 'redis://redis.example.com:6379/12',   # db 12 on default port
  cache_key_prefix: 'dedos',   # key prefix for shared caches (default: nil)
  cache_period: 1800   # seconds (default: 900)

Requests are blocked for cache_period seconds in case another request has been made within cache_period seconds from by same IP address but with a different user agent.

The following cache backends are supported:

  • redis://... – ⚠️ The redis gem has to be installed.
  • hash – Only for testing, don't use this in production.

Country Filter

use Rack::Dedos::Filters::Country,
  maxmind_db_file: '/var/db/maxmind/GeoLite2-Country.mmdb',
  allowed_countries: %i(AT CH DE),
  denied_countries: %i(RU)

Either allow or deny requests by probable country of origin. If both are set, the denied_countries option is ignored.

⚠️ The maxmind-db gem has to be installed.

The MaxMind GeoLite2 database is free, however, you have to create an account on maxmind.com and then download the country database.

For automatic updates, create a geoipupdate.conf file and then use the geoipupdate tool to fetch the latest country database:

version=4.10.0
arch=linux_amd64
conf=/etc/geoipupdate.conf
dir=/var/db/maxmind/

mkdir -p "${dir}"
wget --quiet -O /tmp/geoipupdate.tgz https://github.com/maxmind/geoipupdate/releases/download/v${version}/geoipupdate_${version}_${arch}.tar.gz
tar -xz -C /tmp -f /tmp/geoipupdate.tgz
/tmp/geoipupdate_${version}_${arch}/geoipupdate -f "${conf}" -d "${dir}"

Real Client IP

A word on how the real client IP is determined. Both Rack 2 and Rack 3 (up to 3.0.7 at the time of writing) may populate the request ip incorrectly. Here's what a minimalistic Rack app deloyed to Render (behind Cloudflare) reports:

request.ip = 172.71.135.17
request.forwarded_for = ["81.XXX.XXX.XXX", "172.71.135.17", "10.201.229.136"]

Obviously, the reported IP 172.71.135.17 is not the real client IP, the correct one is the (redacted) 81.XXX.XXX.XXX.

Due to this flaw, Rack::Dedos determines the real client IP as follows in order of priority:

  1. Cf-Connecting-Ip header
  2. First entry of the X-Forwarded-For header
  3. ip reported by Rack

Development

For all required test fixtures to be present, you have to check out the repo with all of its submodules:

git clone git@github.com:svoop/rack-dedos.git
cd rack-dedos
git submodule update --init

To install the development dependencies and then run the test suite:

bundle install
bundle exec rake    # run tests once
bundle exec guard   # run tests whenever files are modified

You're welcome to submit issues and contribute code by forking the project and submitting pull requests.

License

The gem is available as open source under the terms of the MIT License.