背景信息
因为产品需要,要在应用端获取到真实的客户端IP,访问链路如下:
由于应用前面经过了多次代理,所以默认情况下是获取不到真实IP的。
基本概念
在实现之前,先简单了解一下常用来获取IP的几个header。
- remote_addr
- X-Forwarded-For
- X-Real-IP
(1)remote_addr
remote_addr
代表客户端IP,但是它的值不是由客户端提供的,而是服务端根据客户端IP指定的。当你访问某个应用时,当中间没有经过任何代理,那么应用获取到的remote_addr
就是你的主机IP。如果中间经过了代理转发,正常情况下,应用获取到的remote_addr
就是代理的IP,除非在代理服务器上手动将remote_addr
的地址设置成你的主机IP。
(2)X-Forwarded-For
X-Forwarded-For
是HTTP
扩展头,简称XFF
。
XFF
的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP,其格式为:X-Forwarded-For: client,proxy1,proxy2
。
PS:
X-Forwarded-For
的格式可以被伪造。
如果一个应用的前面有三个代理,分别是 Proxy1
、Proxy2
、Proxy3
,它们的IP地址分别是IP1
、IP2
、IP3
,用户的真实IP为IP0
,那么按照XFF
标准,应用收到的XFF
信息应该如下:X-Forwarded-For: IP0,IP1,IP2
。这里没有IP3
,是因为Proxy3
是转发Proxy2
的代理,在转发过程中会将Proxy2
的IP地址追加到XFF
中,而自己的IP地址会被放入remote_addr
中。
PS:通过一个代理才会把上一个节点的IP加到
XFF
中。
(3)X-Real-IP
X-Real-IP
是一个自定义的头部字段,通常被HTTP代理用来表示与它产生TCP连接的设备IP,和XFF
不一样的是,它不是列表,无法将记录追加到X-Real-IP
的结尾,而是直接替换。
理想情况下,我们需要达到以下效果:
也就是应用获取到的X-Real-IP
就是客户端的真实IP,这就要求除了第一层代理之外,后面的代理不需要再去设置X-Real-IP
,只需要做转发即可,这样应用就能拿到真实的客户端访问IP了。
具体实现
由于现实情况,我们在Haproxy
上会做很多的规则配置,所以第一层SLB
上是单纯的TCP代理,因此在SLB
上不需要做太多额外配置,客户端IP会直接透传过去。
当请求到达Haproxy
之后,需要将客户端IP加到XFF
中,并且设置X-Real-IP
为客户端IP,具体配置如下:
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
log 127.0.0.1 local3
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 5m
timeout server 5m
timeout http-keep-alive 10s
timeout check 10s
unique-id-format %{+X}o\ %ci%cp%fi%fp%Ts%rt%pid
frontend https_link_ha
bind *:443 ssl crt /usr/local/etc/haproxy/cert/crt/ ca-file /usr/local/etc/haproxy/cert/ca/ca.pem verify optional
#log 127.0.0.1 local3
mode http
log-format "%ID %ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r"
option accept-invalid-http-request
http-request set-header x-request-id %[unique-id]
http-request set-header x-request-time %[date()]
http-request set-header X-Real-IP %[src]
default_backend pre
backend pre
server 1 10.74.136.13:8080 check inter 1500 rise 3 fall 3 weight 3
其中主要是两个配置:
option forwardfor except 127.0.0.0/8
在由Haproxy
发往后端的请求中加上XFF
首部,其值是前个客户端的IP。http-request set-header X-Real-IP %[src]
在X-Real-IP
中设置客户端IP。
现在请求就到达Ingress
了。
Ingress
也是一层代理,前面的客户端IP已经放到XFF
中,但是默认情况下,Ingress
没有开启XFF
。
在Ingress
上要使用XFF
,需要使用到以下三个参数:
use-forwarded-headers
:是否开启XFF
头传递,默认是false
。forwarded-for-header
:XFF
的真实header名,默认是X-Forwarded-For
。compute-full-forwarded-for
:列出客户端访问所经过的代理IP,默认情况下,XFF
是从remote_addr
中获取的值。
所以,我们只需要在Nginx Ingress
的ConfigMap
里增加以下两个配置即可:
use-forwarded-headers: 'true'
compute-full-forwarded-for: 'true'
配置完成后,Nginx Ingress
会自动重载服务,不需要单独重启。
然后在应用的日志里就能获取到客户端的真实IP了。
当然,并不是所有的场景都能通过XFF
获取到用户的真实IP,比如当SLB前面还有CDN的情况下,获取的可能就是CDN的来源IP了。
评论区