應用獲取客戶端真實IP
背景信息
因為產品需要,要在應用端獲取到真實的客戶端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了。