I came across the varnish throttle module the other day – which seems quite useful – and certainly gives better control over abusive requests than using fail2ban (in that, only specific URLs/request types can be targeted and blocked with the throttle module, while fail2ban tends to trigger the blocking of any traffic from a client which can be more painful).
Fail2ban —
- – tends to block all traffic from a host, at least on a specific port.
- – works across multiple protocols – so one fail2ban instance can protect everything on a server (it just requires a log file and regexp to look for to match abusive hosts)
- – but once a limit is hit the end user can often just not connect – they’re not shown any useful error message
Anyway, with the Throttle module, you can combine blocking based on some or all of :
- Client IP address
- Request method (GET/POST)
- Some sort of http header (backend, or client provided)
- Request URL (e.g wp-login.php)
- For one or many virtual hosts/websites/domains
- Characteristics of a URL (e.g. if it has an API key in it)
based on how you construct the ‘throttle key’ (see is_allowed(key, duration) calls below) – so for example:
- by omitting a client_ip from the throttle key, you could protect against a distributed attack,
- or by omitting a Http Host header, you could protect againstĀ for attacks against multiple sites on one server (if you’re hosting many wordpress blogs, for example).
Presumably, you could provide clients with a way of unblocking themselves if it’s in error.
Anyway, in my trial configuration (see segments below) I’ve specified two time periods – so combining blocking a short spike in traffic, or excessive slower longer term abuse — so e.g. 10 matches in 15 seconds or 500 matches in an hour. In my case, I’m protecting individual websites/domains, and from excessive HTTP POST requests to WordPress’s xmlrpc.php or wp-login page(s).
(Let’s hope no other websites have ‘xmlrpc.php’ files!)
Installation
Installation isn’t difficult – just follow the instructions, perhaps a bit like the below….
apt-src build varnish export VARNISHSRC=$(pwd)/varnish-3.0.2 git clone https://github.com/nand2/libvmod-throttle.git cd libvmod-throttle bash autogen.sh mkdir build export VMODDIR=$(pwd)/build ./configure make make install cp build/libvmod_throttle.so /usr/lib/x86_64-linux-gnu/varnish/vmods/
varnish – default.vcl
Random varnish.vcl snippet below (for varnish 3.0.x).
import throttle; import std; .... sub vcl_pass { .... std.log("PASS : " + req.http.host + " : " + req.url ); // some arbitrary condition/check/url to match against... if(req.url ~ "(xmlrpc|wp-login).php" && req.request == "POST") { // log some info about our buckets std.log("XXX Rate limit bucket: 10req/m : " + throttle.remaining_calls(req.http.Host + "wp1", "10req/m")); std.log("XXX Rate limit bucket: 50req/h : " + throttle.remaining_calls(req.http.Host + "wp2", "50req/h")); // the check ....for 10 req/minute if(throttle.is_allowed(req.http.Host + "wp1", "10req/m") > 0s) { std.log("XXX Rate limit 10req/m throttling : " + req.http.Host + " too many requests for " + req.url + " / " + req.http.X-Forwarded-For ); error 429 "Calm down"; } // the check for 50 req/hour. if(throttle.is_allowed(req.http.Host + "wp2", "50req/h") > 0s) { std.log("XXX Rate limit 50req/h throttling : " + req.http.Host + " too many requests for " + req.url + " / " + req.http.X-Forwarded-For ); error 429 "Calm down"; } } return (pass); } .... sub vcl_error { set obj.http.content-type = "text/html; charset=utf-8"; std.log("XXX Error page - URL:" + req.url + " Host " + req.http.host + " Status " + obj.status); if ( obj.status == 429 ) { synthetic std.fileread("/etc/varnish/error-429.html"); } else { synthetic std.fileread("/etc/varnish/error-503.html"); } return ( deliver ) ; }