Nginx和APISIX多级代理如何获取客户端真实IP地址—全网最详细

Nginx和APISIX多级代理如何获取客户端真实IP地址—全网最详细

Deng YongJie's blog 9 2024-09-28

问题一:SSL单向认证,一级代理和二级代理无法正常进行握手,因此访问时在一级代理会出现502状态码

image-20240912114852812

单向认证

SSL单向认证只要求站点部署了ssl证书就行,任何用户都可以去访问(IP被限制除外等),只是服务端提供了身份认证

3437040-88fdfaf70a49c1fb

debug error访问日志

[error] 611848#611848: *25 SSL_do_handshake() failed (SSL: error:0A000438:SSL routines::tlsv1 alert internal error:SSL alert number 80) while SSL handshaking to upstream, client: 192.168.10.46, server: jette-test.xxxx, request: “GET / HTTP/1.1”, upstream: “https://10.7.0.82:443/”, host: “jette-test.xxxx”

image-20240912120101197

access 访问日志

状态码502

image-20240912141416483
image-20240912162445678

问题二:4层透明代理,无法透传XFF头,所以二级代理获取的IP地址是一级代理的IP,无法获取客户端真实IP

image-20240912114938722

解决方案

通常使用的架构

在多级nginx 代理下进行https 配置。通常的架构是内部的nginx 之间采用的是http进行通讯
一级代理配置https,二级以上的nginx 则是配置http。架构图如下:

image-20240912111657393
image-20240912120301791

参考文档https://github.com/apache/apisix/discussions/4793

如果需要采用问题一的链路架构方案,如何解决此问题?重点!

由于内网环境有直接访问worker节点及后端服务调用的情况,同时基于安全性考虑,会在ingress配置https证书。另外有项目需要获取真实IP的场景,通常情况前端一级代理会有haproxy或nginx负载均衡器,如果一级代理haproxy或nginx使用4层转发,则无法透传http头参数给ingress,所以ingress获取的是一级代理的IP地址。所以需要使用7层转发,从而衍生出 一级代理HTTPS 转发至 二级代理HTTPS的链路。

  1. 从上面问题一的error日志发现,一级代理nginx与二级代理进行SSL握手失败
  2. 从一级代理抓包分析,数据包显示一级代理发送client hello信息给二级代理成功,但二级代理并没返回server hello信息给一级代理,且报错:TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Internal Error)image-20240912151622341
  3. 然后在二级代理apisix的日志发现报错:[error] 182#182: 3139952 [lua] init.lua:192: http_ssl_client_hello_phase(): failed to find SNI: please check if the client requests via IP or uses an outdated protocol. If you need to report an issue, provide a packet capture file of the TLS handshake., context: ssl_client_hello_by_lua, client: 10.7.0.223, server: 0.0.0.0:443image-20240912145049198
  4. 报错信息提示找不到sni?回复client端的hello信息失败,所以抓包上面显示:TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Internal Error)
  5. 核对了一级代理和二级代理的TLS版本,均支持v1 v1.1 v1.2 v1.3,因此排除此问题导致的
  6. 回顾问题一的报错信息,结合SNI(Server Name Indication)资料和nginx的参数,发现nginx是默认关闭了此功能。所以原因是上游二级代理无法接收到正确的server_name,导致二级代理证书返回的不对,二级代理apisix就会返回400状态码拒绝请求,也就出现了抓包的提示Level: Fatal, Description: Internal Error以及二级代理的报错找不到对应的SNI:failed to find SNI:please check if the client requests via IP or uses an outdated protocol. If you need to report an issue, provide a packet capture file of the TLS handshake。SNI的作用就是指定服务器的主机名,也就是携带域名server_name请求指定的IPimage-20240912154956591image-20240912155054628

配置文件参数:https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_server_name

一级代理nginx配置文件

        #添加下面两行参数
	    proxy_ssl_server_name on;
	    proxy_ssl_name $host;

参数详解

set_real_ip_from(二级代理需要信任一级代理的地址)

  1. 请求来源 IP 在 set_real_ip_from 范围内
    如果请求来源的 IP 地址在 set_real_ip_from 指定的范围内,Nginx 会信任该请求,并使用 real_ip_header 指定的头部(如 X-Forwarded-For)中的值作为客户端的真实 IP 地址。

请求来源 IP 不在 set_real_ip_from 范围内

  1. 如果请求来源的 IP 地址不在 set_real_ip_from 指定的范围内,Nginx 不会信任这个请求中的 X-Forwarded-For 头部中的 IP 地址。

行为:

  • Nginx 直接使用请求来源的 IP 地址(即 $remote_addr)作为客户端的 IP 地址
  • 这意味着 Nginx 会将负载均衡器或代理服务器的 IP 地址视为客户端的 IP,而不会考虑 X-Forwarded-For 头中的值。

场景:

  • set_real_ip_from范围内: 如果你有一级代理nginx负载均衡器,所有请求都会先经过它再到达二级代理Nginx。nginx负载均衡器会在请求头中加入 X-Forwarded-For 以记录客户端的真实 IP。如果你将nginx负载均衡器的 IP 地址配置在 set_real_ip_from 中,二级代理Nginx 会读取并信任 X-Forwarded-For 中的客户端真实 IP。
  • 不在set_real_ip_from范围内: 如果请求不是通过你信任的一级代理nginx负载均衡器发来的(可能是直接访问二级代理 Nginx,或者来自不可信的代理服务器),二级代理Nginx 会认为这个请求中的 X-Forwarded-For 不可信,于是使用实际请求来源 IP(一级代理nginx负载均衡器或代理的 IP 地址)作为客户端 IP。

X-Real-IP

是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是,X-Real-Ip 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。

X-Forwarded-For
X-Forwarded-For 是一个扩展头。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 remote_address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

详细分析一下,这样的结果是经过这样的流程而形成的:

  1. 用户IP0—> 代理Proxy1(IP1),Proxy1记录用户IP0,并将请求转发个Proxy2时,带上一个Http Header X-Forwarded-For: IP0
  2. Proxy2收到请求后读取到请求有 X-Forwarded-For: IP0,然后proxy2 继续把链接上来的proxy1 ip追加到 X-Forwarded-For 上面,构造出X-Forwarded-For: IP0, IP1,继续转发请求给Proxy 3
  3. 同理,Proxy3 按照第二部构造出 X-Forwarded-For: IP0, IP1, IP2,转发给真正的服务器,比如NGINX,nginx收到了http请求,里面就是 X-Forwarded-For: IP0, IP1, IP2 这样的结果。所以Proxy 3 的IP3,不会出现在这里。
  4. nginx 获取proxy3的IP 能通过remote_address就是真正建立TCP链接的IP,这个不能伪造,是直接产生链接的IP。$remote_address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。

二级代理apisix修改配置

vim templates/configmap.yaml
        real_ip_header: "X-Real-IP"
        #real_ip_header: "X-Forwarded-For"    # http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header
        real_ip_recursive: "on"
        real_ip_from:                  # http://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from
        #注意这里的IP地址需要填写上级代理的地址,比如上级的代理是haproxy或nginx
          - 10.7.0.7
          - 10.7.0.0/22
          - 127.0.0.1
          - 'unix:'

验证结果

一级代理日志:携带了server_name访问二级代理的443,并且有返回结果,访问正常

image-20240912162747045
image-20240912162920144
image-1727677034618

深入抓包验证

客户端有携带server_name,并且server端可以正常返回

image-20240912163131680
image-20240912163319751

最后二级代理日志有流量进入且正常转发至后端gateway服务,并且返回数据包,能够获取客户端真实IP地址,因为一级代理与二级代理成功携带server_name握手通信,证书链返回正确

192.168.10.46 192.168.10.46, 192.168.10.46 - [12/Sep/2024:16:32:11 +0800] jette-test.xxx “GET / HTTP/1.0” 404 88 0.003 “” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36” 10.42.44.159:8750 404 0.003 “http://jette-test.xxx

image-1727677096993