Check Varnish ACL via X-Forwarded-For when behind one or more(!) reverse proxies

regexreverse-proxyvarnishx-forwarded-for

I have Varnish running behind a reverse proxy (running on localhost, for SSL offloading). The proxy sets the X-Forwarded-For header or adds itself to it if the header already exists.

When I do ACL checks of course I want to check against the original client's IP, not the IP of my proxy, so I can't use the client.ip field. With the std vmod I can do the following:

vcl 4.0;
import std;
sub vcl_recv {
    if (std.ip(regsub(req.http.X-Forwarded-For, ", 127.0.0.1$", ""), "0.0.0.0") ~ my_acl) {
        ...do stuff...
    }
}

In other words, I trim the proxy's IP (127.0.0.1) from the header before running it through std.ip and comparing it to my ACL. This works fine, except…

This fails when the X-Forwarded-For header is already set before it reaches my proxy. In that case the XFF header contains three or more IP addresses. Trimming the last one still leaves more than one and std.ip chokes on this, delaying the request by several seconds and of course failing to check the ACL.

I need to make sure the XFF header contains only one IP (IPv4 or IPv6) after I've trimmed the proxy's off. This should be the client's IP.

For example:

X-Forwarded-For: 10.10.1.1, 10.10.2.2, 2001:a031:100a:dead:beef:1234:1234:1234, 127.0.0.1

should become

X-Forwarded-For: 2001:a031:100a:dead:beef:1234:1234:1234, 127.0.0.1

Since I can't trust any XFF headers that come in from outside, I'd like to discard anything but the client-ip that my proxy saw. My proxy doesn't support modifying the XFF header so I'll need to do it in Varnish.

The first point in Varnish's flow when I can interact with headers is in vcl_recv() and at that point Varnish has already added the client.ip to the end of the list.

I was hoping to use a regular expression to capture the last two items (IPv4 or IPv6) into a numbered capture group ($1) and simply replace the header with the capture group. Like this:

vcl 4.0;
import std;
sub vcl_recv {
    set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "([a-f0-9:.]+, [a-f0-9:.]+)$", "\1");
}

The third argument (the string that replaces the characters matched by the regex) doesn't work. The resulting header is exactly the same as before, even though the regex captures only the last two IP adresses.

How can I throw away anything but the last two IP addresses from the XFF header?

Best Answer

Your regex seems fine. The only thing I see is that you request to replace the group by itself.

It should work if you add ^(.*) at the beginning of your regex and replace the second argument by \2

set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "^(.*)([a-f0-9:.]+, [a-f0-9:.]+)$", "\2");

}

That being said I don't think it's a good idea to change the XFF header. It should be more standard to add a header rather than changing deleting information that has been transmitted by intermediate proxies.