现象:
某日某服务通过代理访问某公网地址:xxx-test.hhfx.cn,出现502 的情况,但直接访问该网站可以正常完成请求,查看502报错时间段的日志,出现了SSL_do_handshake() failed (SSL: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv1 alert handshake failure的报错。
分析
目前内网服务调用公网接口,使用的是Nginx正向代理的方式进行的,且默认使用4层代理,仅做TCP层面的端口转发。该业务开始使用了4层转发失败了,这个原因后述。在4层代理配置失败后,尝试使用7层代理进行转发,然后报了上述错误,从错误描述来看,是Nginx代理和该域名在SSL的 握手阶段,就失败了。回顾一下SSL的加密过程: 回顾报错,发现在客户端(Nginx)发起第一个Client Hello后就被服务端返回SSL_do_handshake了,但是不通过Nginx,仅通过游览器访问又是可以的,所以,我认为在以上这两种情况下,客户端发出的Client Hello报文的数据包是不一样的,通过抓包可以进行对比。 在抓包对比后发现,正常调用比通过Nginx转发,在Client Hello 的报文里,多了一个扩展类型:Server Name Indication extension 这个Server Name Indication extension 即是常说的SNI了,那么,至此,可以可以初步断定,是Nginx 正向代理转发的时候,在SSL握手发起Client Hello的时候,请求里少了SNI的扩展头,导致了握手失败,那么,这样也解释了为什么这个域名无法使用4层代理进行转发,因为4层转发是没有SSL握手这个动作的。那么要修复这个问题,只要想办法在Nginx的请求里加上这个头,就可以了。
解决办法
由于知道了原因,那么查询Nginx文档即可,通过文档: https://nginx.org/en/docs/http/ngx_http_proxy_module.html 查询到,通过加载以下配置即可让Nginx支持SNI的头部: 最后修改成以下配置:
server {
listen 4589;
location / {
proxy_ssl_server_name on;
proxy_pass https://xxx-test.hhfx.c:443;
client_body_buffer_size 3m;
client_max_body_size 10m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_ignore_client_abort on;
}
}
重载完Nginx,进行测试,成功调用。
问题发散:
之前也有类似的公网转发需求,为什么仅这个网站会有SNI的问题呢?首先回忆一下SNI的作用,比如一个Web Server上面有很多个网站,每个网站的域名均不一致,有abc.com的,有xyz.com 的,那么他们加载的https证书肯定也是不一致的,那么就会需要基于请求的域名加载不同的证书,但由于SSL握手认证这一步是在HTTP请求之前的,必然不能使用HTTP请求里的HOST的Header 作为参考依据,所以,在SSL的握手的请求里,加入了Server Name Indication extension 这个扩容类型,用于让服务端识别客户端发起请求的域名是什么。而之前很多网站没有添加SNI的配置,依然可以转发成功,就和网站服务端本身SNI的做法有关了,比如有些网站只承载一个域名,有些网站都是基于同一个通配符证书进行验证的,这些情况均不需要SNI的验证。
参考文档
- proxy_ssl_server_name 官方文档
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_server_name - ssl/tls握手过程
https://www.cnblogs.com/barrywxx/p/8570715.html - SNI原理
https://copyfuture.com/blogs-details/20210121001219991j
个人公众号, 分享一些日常开发,运维工作中的日常以及一些学习感悟,欢迎大家互相学习,交流
|