活到老学到老  

记录遇到问题的点点滴滴。

Haproxy环境获取用户IP

6年前发布  · 2091 次阅读
  Haproxy  真实IP 
haproxy工作在前端用户和后端的Server之间,作为"中间人",haproxy会建立两个连接,一是用户端与haproxy建立一个连接,另一个是haproxy与后端的server建立一个连接。
 
所有proxy类服务的程序都会有一个相同的问题,就是处于proxy后端的server上不能够看到用户源IP地址,而只能看到haproxy的IP地址。
 
要解决这个问题,可以使用tproxy的方法,可以参考我之前的blog文档
http://blog.sina.com.cn/s/blog_704836f401011e17.html
 
但是这个方法有个比较明显的缺点就是比较难以经过防火墙,另外后端的server的默认网关必须是haproxy。
 
haproxy作者Willy Tarreau开发了一个"Proxy protocol"用来向后端Server传递用户源IP地址。
目前Proxy protocol有v1和v2两个版本,v1偏重人类可读,v2是二进制格式,易于程序处理。
 
proxy protocol有两种角色:sender和receiver 。sender在与receiver之间每个新连接建立成功以后,都会先发送一个带有PROXY header信息的的包。如果receiver没有正确配置,不能识别这个包则会丢弃,连接不能成功建立。
 
Proxy protocol v1的格式如下:
PROXY关键字+空格+协议栈+源IP+目的IP+源端口+目的端口+\r\n
PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n
这样,下一跳主机通过接受到的这些信息就可以正确处理用户的连接,就像用户是直接连接到自己一样。
通过以上分析,我们看到可以利用这个proxy protocol传递用户的源IP地址。具体的部署结构如下图所示:
    1. client经过防火墙,发送请求到处于DMZ区中的reverse-proxy
    2. Reverse-Proxy校验请求,经过防火墙,转发请求到后端LAN上的load-balancer上
    3. Load-balancer选择一个后端服务器,将请求转发到选定的Server上
    4. 后端Server处理用户请求,然后将结果返回给load-balancer
    5. Load-balancer转发处理结果给reverse-proxy
    6. reverse-proxy转发处理结果给client
 
因为haproxy对于后端只要求路由可达即可,所以load-balancer可以与reverse-proxy处于不同地理位置。这样就大大扩展了灵活性。
 
具体的配置非常简单,简单说一下:
 
[ reverse-proxy ]上,使用"send-proxy"关键字
server srv1 192.168.10.1 check send-proxy
 
[Load-balancer]上,在bind部分使用"accept-proxy"关键字
bind 192.168.1.1:80 accept-proxy
 
在bind后边使用了accept-proxy以后,haproxy会当作proxy protocol中的源IP真实存在一样,可以将这个源IP用与ACL,内容过滤,日志,透明代理等等。
 
后端的服务器上配置默认网关为load-balancer
[Load-balancer]上,配置使用source 0.0.0.0 usesrc clientip
backend bk_app
[...]
  source 0.0.0.0 usesrc clientip
  server srv1 192.168.11.1 check
 
 
 如上图所示,经过了3层haproxy的接力传递,最后在后端的web服务器上可以接收到最初的用户src ip地址。
目前已经有多个软件支持proxy protocol,例如: nginx,percona server
 
[haproxy和nginx配合的示例]:
 
1.在haproxy上使用send-proxy参数
server srv1 192.168.10.1 check send-proxy
 
2.nginx主配置文件/etc/nginx/nginx.conf中定义新的log格式
 
log_format elb_log '$proxy_protocol_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent "$http_referer" ' '"$http_user_agent"';
 
 $proxy_protocol_addr这个变量就是取过来的用户真实IP,这个变量可以用在很多地方。
 
3.在nginx站点配置中加入以下粗体部分:
server {
    listen 80 proxy_protocol;   # 加入proxy protocol指令
    root /usr/share/nginx/www;
    index index.html index.htm;
 
    # Make site accessible from http://localhost/
    server_name localhost;
    set_real_ip_from 172.31.0.0/20;  # 使用realip module, 172.31.0.0/20是haproxy内网地址段
    real_ip_header proxy_protocol;  # 增加proxy protocol指令
 
    access_log /var/log/nginx/elb-access.log elb_log;  # 使用之前定义的elb_log格式
 
    location / {
         try_files $uri $uri/ /index.html;
     }
}
 
4.nginx的access log中会记录如下:
 
96.251.49.3 - - [20/Mar/2014:03:50:47+0000] "GET / HTTP/1.1"  200 396 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1897.2 Safari/537.36"
 
其中96.251.49.3就是用户的真实IP.
 
set_real_ip_from 和 real_ip_header对于使用proxy protocol不是必须的,但建议这样使用。
 
5.对于在HTTP协议中则相对简单,只需要在haproxy中配置两个option参数。
 
backend http
    mode http
    option http-server-close
    option forwardfor
 
forwardfor这个选项配置的header是X-Forwarded-For, X-Forwarded-for经过多级代理以后会有多个IP,格式是:X-Forwarded-For: client, proxy1, proxy2。我们通常只需要第一个,不过这个可是可以伪造的。
 
另外在实际使用中,有时候需要自己定义这个header的名字,在haproxy中操作也很简单,在配置文件中加入如下自定义header即可。
 
http-request set-header X-Client-IP %[src]
 
主要的CDN厂家,也会自己插入一个用户源IP的主机头,例如 :
网宿: Cdn-Src-Ip --> HTTP_CDN_SRC_IP
Akamai: Ture-Client-IP --> HTTP_TURE_CLIENT_IP
Cloudflare: CF-Connecting-IP --> HTTP_CF_CONNECTING_IP
 
根据 RFC 3875:
Meta-variables with names beginning with "HTTP_" contain values read from the client request header fields, if the protocol used is HTTP. The HTTP header field name is converted to upper case, has all occurrences of "-" replaced with "_" and has "HTTP_" prepended to give the meta-variable name.
 
http header中的变量都会转换成大写,"-"会被替换成下划线,并且在前边加入"HTTP_"前缀。
 
============================
[haproxy和percona server配合的示例]:
 
percona server自5.6.25-73.0这个版本以后,添加了proxy protocol支持,具体配置方法如下:
 
1. haproxy中配置send-proxy参数
2. percona server在 /etc/my.cnf中:
proxy_protocol_networks = haproxy_ip
bind_address=192.168.56.2 (具体的网卡接口名称,不能0.0.0.0)
为什么需要这样配置,详见参考文档7
=============================
[NetScaler和F5 BIGIP处理PROXY protocol的示例]:
 
NetScaler和F5内部没有内置支持proxy protocol,因此与proxy protocol配合时会有问题,可以通过如下方式去除proxy protocol相关信息。如果愿意,可以写更复杂的规则来处理。
参考文档:
1.http://blog.haproxy.com/2012/06/05/preserve-source-ip-address-despite-reverse-proxies/
2.http://blog.haproxy.com/2013/09/16/howto-transparent-proxying-and-binding-with-haproxy-and-aloha-load-balancer/
3.https://chrislea.com/2014/03/20/using-proxy-protocol-nginx/
4.http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html
5.https://philio.me/showing-the-correct-client-ip-in-logs-and-scripts-when-using-nginx-behind-a-reverse-proxy/
6.http://serverfault.com/questions/722151/haproxy-how-to-append-client-ip-in-x-client-ip-and-x-forwarded-for-headers
7.https://www.percona.com/blog/2015/10/15/proxy-protocol-percona-xtradb-cluster-quick-guide/
8.https://www.citrix.com/content/dam/citrix/en_us/citrix-developer/documents/Netscaler/irules/proxy-protocol-receiver.pdf
9.http://stackoverflow.com/questions/20325467/http-header-in-apache
10.http://www.cdnplanet.com/blog/which-cdns-support-edns-client-subnet/
11.http://www.cnblogs.com/zhengyun_ustc/archive/2012/09/19/getremoteaddr.html