Varnish自动将负载均衡器IP添加到X-Forwarded-For标头

时间:2014-08-28 22:04:20

标签: varnish varnish-vcl

我的请求流程如下;

HAProxy --> Varnish (4.0.1) --> Apache web backends

当新请求进入HAProxy时,客户端的IP地址将被添加到X-Forwarded-For标头(这很好!)。但是,看起来Varnish也在添加HAProxy IP。当请求到达我的vcl_recv例程时,X-Forwarded-For标题为:

X-Forwarded-For: end-user-ip, haproxy-ip

您可以在varnishlog输出中看到:

*   << Request  >> 8
-   Begin          req 7 rxreq
-   Timestamp      Start: 1409262358.542659 0.000000 0.000000
-   Timestamp      Req: 1409262358.542659 0.000000 0.000000
-   ReqStart       192.168.1.103 48193
-   ReqMethod      PURGE
-   ReqURL         /some/path
-   ReqProtocol    HTTP/1.1
-   ReqHeader      Authorization: Basic xxx
-   ReqHeader      User-Agent: curl/7.30.0
-   ReqHeader      Host: example.com
-   ReqHeader      Accept: */*
-   ReqHeader      X-Forwarded-For: 1.2.3.4
-   ReqHeader      Connection: close
-   ReqUnset       X-Forwarded-For: 1.2.3.4
-   ReqHeader      X-Forwarded-For: 1.2.3.4, 192.168.1.101
-   VCL_call       RECV
-   ReqUnset       X-Forwarded-For: 1.2.3.4, 192.168.1.101
-   VCL_acl        NO_MATCH purge_acl
-   Debug          "VCL_error(403, Not allowed.)"
-   VCL_return     synth

我需要准确的客户端IP地址的原因是我可以根据PURGE / BAN的ACL规则进行检查。由于X-Forwarded-For标头中的最后一个IP是HAProxy的IP,因此所有IP的ACL检查都会失败。这是我的配置的相关部分:

acl purge_acl {
    "1.2.3.4";
}

sub vcl_recv {

    set req.backend_hint = load_balancer.backend();

    if (req.method == "PURGE") {
        if (!std.ip(req.http.X-forwarded-for, "0.0.0.0") ~ purge_acl) {
            return(synth(403, "Not allowed."));
        }
        ban("obj.http.x-url ~ " + req.url);
        return(synth(200, "Ban added"));
    }

}

我是否可以完全依赖HAProxy的X-Forwarded-For标题,而不使用Varnish篡改它?

旁注,似乎Varnish正在做这个(虽然这不是在mv VCL配置中)

if (req.restarts == 0) {
    if (req.http.X-Forwarded-For) {
        set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
    } else {
        set req.http.X-Forwarded-For = client.ip;
    }
}

3 个答案:

答案 0 :(得分:9)

Varnish将其默认逻辑附加到您定义的任何函数,例如vcl_recv,而不是纯粹覆盖它。默认的vcl_recv逻辑包含:

if (req.http.x-forwarded-for) {
    set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {
    set req.http.X-Forwarded-For = client.ip;
}
你注意到了

对我来说似乎很奇怪的是,vcl_recv中的Varnish默认逻辑似乎在vcl_recv逻辑之前执行。如果您定义自己的X-Forwarded-For,您会在vcl_deliver内看到什么值?

您可以做的一件事就是在必要时解析出第一个IP地址:

set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "^([^,]+),?.*$", "\1");

答案 1 :(得分:9)

我今天也遇到了这个问题。

清漆4.0中的default.vcl已在builtin.vcl中重命名,但不包含您提到的set req.http.X-Forwarded-For部分 - link。不过,根据协议规范 - Wikipedia link,他明确地将逗号分隔的列表附加到中间代理IP地址。

一种解决方案是使用X-Real-IP标头,在HAProxy中使用真实客户端ip覆盖此标头,并将此标头用于vcl ACL。

(错误地)提及in the varnish forum的另一个解决方案是获取最左边IP地址的regsub(req.http.X-Forwarded-For, "[, ].*$", "")。但是,此方法 NOT SECURE ,因为此标头很容易被欺骗。

我的建议是从标题中提取已知部分,清漆IP,如下所示:

if (!std.ip(regsub(req.http.X-Forwarded-For, ", 192\.168\.1\.101$", ""), "0.0.0.0") ~ purge_acl) {
    return(synth(403, "Not allowed."));
}

唯一的问题是如果连接中有超过2个跃点,例如。您还使用代理连接到互联网。 nginx提供了一个很好的解决方案,因为您可以定义可信任的跃点,并且它们会被递归忽略,直到真正的客户端ip。

set_real_ip_from 192.168.1.101;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

您可以在此serverfault thread answer

中查看有关此内容的更多详情

您可能还想检查在VCL_call RECV中您在ACL匹配之前执行ReqUnset X-Forwarded-For的原因。

答案 2 :(得分:2)

Vanish源代码已移至GitHub,以供参考,从版本4.0开始,X-Forwarded-For逻辑已移出builtin.vcl(以前称为default.vcl)和源代码逻辑可以在bin/varnishd/cache/cache_req_fsm.c中找到。