why unix | RBL service | netrs | please | ripcalc | linescroll | pwtool
varnish related

varnish related

varnish vcl

There's a neat feature of varnish that helps weed out expensive (backend) requests.

Within a vcl you can hook into the cache 'miss' target:

sub vcl_miss {
    if (req.http.CIDR_RS_CC ~ "^(CC|CODE)$" ) {
        return(synth(491, "Access denied"));
    }
}

Replace CC with a lits of | separated country codes.

What this does is effectively abort the request if the cache content doesn't exist already, you'd use this potentially in a case where a bot in another country is hitting the backend a little too greedily.

Want to check the conf?

varnish logs

What if you want to see error responses from the backend?

varnishncsa -q 'RespStatus >= 500 or BerespStatus >= 500'

What if you want to let the logs scroll by similar to other web logs?

varnishncsa -F '%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-ent}i" "%{Varnish:hitmiss}x"'

I like to add a hit/miss statement at the end so we can see how effective the cache is. This can be graphed.

adjusting the http host header

Some requets might have to go a different server with a different host header. This can't be done in the vcl_recv, but has to be done in vcl_backend_fetch:

sub vcl_recv {
    if (req.url ~ "(list|of|urls)" ) {
        set req.backend_hint = different_server_pool.backend();
    }
}

sub vcl_backend_fetch {
    if (bereq.url ~ "(list|of|urls)" ) {
        set bereq.http.host = "different-server-host.com";
    }
}

dropping cached content

Content can be dropped when it matches an expression, such as all content matching .:

varnishadm 'ban req.url ~ .'

Or if you want to clear a host:

varnishadm 'ban req.http.host (www.)example.com'

test a varnish conf before reloading

Like with other daemons, if the conf is invalid, the daemon will shutdown.

/usr/sbin/varnishd -C -f /etc/varnish/default.vcl

If there are faults it will complain with "VCL compilation failed", so lets grep for that.

/usr/sbin/varnishd -C -f /etc/varnish/default.vcl 2>&1 \
| grep -c 'VCL compilation failed' | grep ^0 \
&& systemctl reload varnish

post filtering with varnish

Well, back against the wall, filtering out URLs is dead simple, but what about data that is sent in POST when the backend can't stop it?

apt-get install varnish varnish-modules

import bodyaccess;
import std;

sub vcl_recv {
    if (req.method == "POST") {
        std.cache_req_body(8KB);
        if (bodyaccess.len_req_body() > 0 && bodyaccess.rematch_req_body("(base64_decode|eval\()") != 0) {
            return(synth(403));
        }
    }
}

Remember, this is a regex comparison, if it matches it will evaluate to 1, no match is 0 and errors are -1.

Then reload it like above. In the above, a request now that has '$(curl)' within it will be responded to with a 403 page. It isn't friendly but might help you out.

varnish throttle

If you need to rate limit a characteristic in varnish you can use the throttle vmod. The is_denied method takes a key, which you can use to store request access timestamps. In this case we're using the client-ip, but you could use the user agent itself. If there are more than 5 requests from the IP in 5s, block for 30s:

import vsthrottle;

sub vcl_recv {
    if (req.http.user-agent ~ "curl" ) {
        if (vsthrottle.is_denied(client.identity, 5, 5s, 30s)) {
            return (synth(499, "too many requests"));
        }
    }
}

varnish utm strip

set req.url = regsuball(req.url, "(utm_source|utm_medium|utm_campaign|utm_term|utm_id|fbclid)=[^&]+", "");
set req.url = regsuball(req.url, "&&+", "&");

wp login post spam

You'll need the redis module, a redis server

apt-get install varnish-redis valkey-server

This include will put a 15-minute block on repeated wp-login POSTs, save and include early in default.vcl:

sub vcl_init {
    new db = redis.db(location = "127.0.0.1:6379");
}

sub vcl_recv {
    # Intercept login attempts to WordPress admin
    if (req.method == "POST" && req.url ~ "wp-login\.php|^/wp-admin/") {
        # Check current failure count for this IP
        db.command("GET");
        db.push("wp_fail:" + req.http.X-Real-IP);
        db.execute();

        # If the failure key exists and is greater than or equal to 3, block it
        if (!db.reply_is_error() && std.integer(db.get_string_reply(), 0) >= 3) {
            return (synth(429, "Too Many Login Attempts"));
        }
    }
}

sub vcl_backend_response {
    # Detect failed POST requests to the login page
    if (bereq.method == "POST" && bereq.url ~ "wp-login\.php") {

        # WordPress login failures typically omit the auth cookie (wordpress_logged_in_)
        if (!beresp.http.Set-Cookie ~ "wordpress_logged_in_") {

            # Atomically increment the counter for this IP
            db.command("INCR");
            db.push("wp_fail:" + bereq.http.X-Real-IP);
            db.execute();

            # Set a 60-second expiration on the key if it was just created
            if (!db.reply_is_error() && db.get_integer_reply() == 1) {
                db.command("EXPIRE");
                db.push("wp_fail:" + bereq.http.X-Real-IP);
                db.push("900");
                db.execute();
            }
        }
    }
}