解决此问题的最佳方法是使用 PROXY protocol 连接 HAProxy 和 Varnish。
此协议将客户端连接信息存储在 TCP 包中,并沿各个跃点传输此信息。
要求您的代理服务器支持此协议。幸运的是 Varnish 和 HAProxy 可以。
对于 HAProxy,只需将 send-proxy-v2 作为额外属性添加到后端定义。
对于 Varnish,您需要确保打开支持 PROXY 的侦听端口。
HAProxy 示例配置
这是一个支持 send-proxy-v 的过于简化的 HAProxy 示例配置:
defaults
mode http
frontend http-in-proxy
bind *:80
default_backend servers-proxy
backend servers-proxy
server server1-proxy varnish.example.com:8443 send-proxy-v2
为了简单起见,我没有终止 TLS,但我只是通过 PROXY 协议向 Varnish 发送纯 HTTP。
清漆代理示例
下面是一个使用 PROXY 支持打开的额外侦听端口的示例:
varnishd -a :80 -a :8443,PROXY -f /etc/varnish/default.vcl -s malloc,2g
如您所见,常规 HTTP 端口是 80,而 PROXY 端口是 8443。 HAProxy 配置也引用了该端口。
那个 X-Forwarded-For 标头呢?
成功设置 PROXY 协议后,您现在可以受益于准确的 X-Forwarded-For 标头。
Varnish 将使用它从 PROXY 协议获得的值,不再需要链接 X-Forwarded-For 值。
如果连接HAProxy的客户端IP地址为1.2.3.4,则Nginx中的X-Forwarded-For标头为X-Forwarded-For: 1.2.3.4。
额外奖励:TLS 信息。
PROXY 协议不仅存储原始客户端 IP 地址,还存储有关请求协议的信息。
以下是proxy VMOD 的示例,用于确定原始连接是通过 HTTP 还是 HTTPS:
vcl 4.1;
import proxy;
sub vcl_recv {
if (proxy.is_ssl()) {
set req.http.X-Forwarded-Proto = "https";
} else {
set req.http.X-Forwarded-Proto = "http";
}
}
sub vcl_hash {
hash_data(req.http.X-Forwarded-Proto);
}
注意:此示例还将通过将 X-Forwarded-Proto 标头的值添加到缓存对象的哈希值来创建基于协议的缓存变体。
这是另一个使用 PROXY 协议提取更多 TLS 信息的示例:
vcl 4.1;
import proxy;
sub vcl_deliver {
set resp.http.alpn = proxy.alpn();
set resp.http.authority = proxy.authority();
set resp.http.ssl = proxy.is_ssl();
set resp.http.ssl-version = proxy.ssl_version();
set resp.http.ssl-cipher = proxy.ssl_cipher();
set resp.http.client-has-cert-sess = proxy.client_has_cert_sess();
set resp.http.client-has-cert-conn = proxy.client_has_cert_conn();
}
后备计划
我希望我让您相信 PROXY 协议非常强大。但是,如果您不打算使用 PROXY 协议,您仍然可以在 VCL 代码中提取 X-Forwarded-For 标头的第一个值。
您可以在 VCL 中执行此操作:
sub vcl_recv {
set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For,"^([^,]+)(,[^,]+)*","\1");
}
然后,此值将以现成的格式呈现给您的 Nginx 服务器。