问题一:SSL单向认证,一级代理和二级代理无法正常进行握手,因此访问时在一级代理会出现502状态码
单向认证
SSL单向认证只要求站点部署了ssl证书就行,任何用户都可以去访问(IP被限制除外等),只是服务端提供了身份认证
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”
access 访问日志
状态码502
问题二:4层透明代理,无法透传XFF头,所以二级代理获取的IP地址是一级代理的IP,无法获取客户端真实IP
解决方案
通常使用的架构
在多级nginx 代理下进行https 配置。通常的架构是内部的nginx 之间采用的是http进行通讯
一级代理配置https,二级以上的nginx 则是配置http。架构图如下:
参考文档:https://github.com/apache/apisix/discussions/4793
如果需要采用问题一的链路架构方案,如何解决此问题?重点!
由于内网环境有直接访问worker节点及后端服务调用的情况,同时基于安全性考虑,会在ingress配置https证书。另外有项目需要获取真实IP的场景,通常情况前端一级代理会有haproxy或nginx负载均衡器,如果一级代理haproxy或nginx使用4层转发,则无法透传http头参数给ingress,所以ingress获取的是一级代理的IP地址。所以需要使用7层转发,从而衍生出 一级代理HTTPS 转发至 二级代理HTTPS的链路。
- 从上面问题一的error日志发现,一级代理nginx与二级代理进行SSL握手失败
- 从一级代理抓包分析,数据包显示一级代理发送client hello信息给二级代理成功,但二级代理并没返回server hello信息给一级代理,且报错:TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Internal Error)
- 然后在二级代理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:443
- 报错信息提示找不到sni?回复client端的hello信息失败,所以抓包上面显示:TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Internal Error)
- 核对了一级代理和二级代理的TLS版本,均支持v1 v1.1 v1.2 v1.3,因此排除此问题导致的
- 回顾问题一的报错信息,结合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请求指定的IP
配置文件参数: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(二级代理需要信任一级代理的地址)
- 请求来源 IP 在 set_real_ip_from 范围内
如果请求来源的 IP 地址在 set_real_ip_from 指定的范围内,Nginx 会信任该请求,并使用 real_ip_header 指定的头部(如 X-Forwarded-For)中的值作为客户端的真实 IP 地址。
请求来源 IP 不在 set_real_ip_from 范围内
- 如果请求来源的 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。
详细分析一下,这样的结果是经过这样的流程而形成的:
- 用户IP0—> 代理Proxy1(IP1),Proxy1记录用户IP0,并将请求转发个Proxy2时,带上一个Http Header X-Forwarded-For: IP0
- Proxy2收到请求后读取到请求有 X-Forwarded-For: IP0,然后proxy2 继续把链接上来的proxy1 ip追加到 X-Forwarded-For 上面,构造出X-Forwarded-For: IP0, IP1,继续转发请求给Proxy 3
- 同理,Proxy3 按照第二部构造出 X-Forwarded-For: IP0, IP1, IP2,转发给真正的服务器,比如NGINX,nginx收到了http请求,里面就是 X-Forwarded-For: IP0, IP1, IP2 这样的结果。所以Proxy 3 的IP3,不会出现在这里。
- 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,并且有返回结果,访问正常
深入抓包验证
客户端有携带server_name,并且server端可以正常返回
最后二级代理日志有流量进入且正常转发至后端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”