迁移SLB过程中的一次踩坑记录
流量入口迁移到云的负载均衡。第二天收到合作方的反馈,提示连接被重置,我们将流量重新切回老入口。简单排查后发现,使用java的合作方出现 connection reset,而其他(如python)的调用方并没有此类报错。安卓客户端也是部分机型报错。
1.初步排查
跟调用方要了一个demo,在我本地机器上跑了一下,发现并没有被重置。抓包对比发现:jdk1.7默认使用TLSv1.0请求https服务,被reset;jdk1.8默认使用TLSv1.2请求https服务,不会被reset。Java HttpClient可以通过SSLContext指定TSL版本避免这个问题, 但是推动调用方去改,显然不妥。
jdk1.8使用TSLv1.2握手,访问新的入口
jdk1.7使用TSLv1.0握手,访问旧的入口 jdk1.7使用TSLv1.0握手,访问新的入口
现在可以确定是新入口会重置TSLv1.0的握手请求。使用openssl s_client -connect 172.16.1.1:443 -tls1 -servername www.xxx.com -showcerts -state -msg < /dev/null
, 绕过负载均衡直接连nginx, 这时提示不支持tlsv1 。到此可以排除不是引入负载均衡导致的问题,想想也是,lvs是tcp层连接,不会影响https。
2.原因分析
再次查看配置中ssl_protocols设置,明明设置了tlsv1 ,但没有生效?查看所有vhost配置,发现还有一处设置了ssl_protocols ,其中只指定了TLSv1.1和TLSv1.2,增加TLSv1后,重新reload,问题消失。
到这,可以确定是由于配置拆分导致的问题。default server的ssl_protocols用来进行ssl握手,配置文件的加载顺序影响Nginx的default server选择,进而影响ssl_protocols的选择。虽然Nginx的SNI支持一个IP配置多个https证书,但这是tls握手完成后再解析extension的域名实现的,握手时tls版本不匹配就没机会解析extension了。具体到这次案例中,没拆分配置的nginx.conf包含solar和solar-pre两个server:solar在前,ssl_protocols为TLSv1.2 TLSv1.1 TLSv1; solar-pre在后ssl_protocols TLSv1.2 TLSv1.1。拆分后solar-pre.conf加载顺序在solar.conf之前,因此导致了这个问题。
3.Nginx include加载顺序
关于nginx include的加载顺序,这篇博文分析比较详细, 加载时调用的函数是strcoll, 这个函数根据环境变量LC_COLLATE来比较字符串。LC_COLLATE默认为»POSIX»或»C»,这时strcoll() 和 strcmp()一样根据ASCII比较字符串大小。可以用 echo $LC_COLLATE
查看变量值,然后LC_COLLATE=C ls -x
或 LC_COLLATE=zh_CN ls -x
来测试下排序 。
也就是说,默认情况下(POSIX||C) nginx的配置按照字母序加载(asccii码越小,越优先加载)
4.SNI与ssl_protocols
nginx 开启SNI后到底能不能支持每个server单独指定ssl_protocols?stackoverflow上的讨论给出了否定答案, 本次经历也说明1.13版本不支持这样配置, 但是可以通过ssl_ciphers限制来曲线实现。
5.如何避免
既然ssl_protocols不支持单独server指定tls版本,那就把它放在http段,每个server只需要指定自己的ssl_certificate/ssl_certificate_key/ssl_ciphers等个性化配置。
6.参考资料
[1]. http://fangpeishi.com/nginx_loads_confs_order.html
[2]. https://www.tutorialspoint.com/c_standard_library/c_function_strcoll.htm
[3]. https://stackoverflow.com/questions/27213607/different-tls-protocols-per-server-in-nginx