nginx 配置 ssl_protocols 遇到的坑

二维码
| May 18, 2020 | 原创

最近在配置 nginx https 服务时,遇到一个问题,发觉配置的 ssl_protocols 的指令获取的 tls 协商加密的版本是错误的,这种问题发生在同一个ip服务器上同时配置了多个 https server 情况下,类似于 SNI 问题。

现场

由于项目的 nginx 除了项目的默认的 https 服务配置外,还部署了 lvs 心跳检测的 https 服务,并且没有指定显示的指定 default_server

# 心跳 https 服务
server {
     listen                      443 ssl;
     server_name                 lvscheck.com;

     ssl_protocols               TLSv1, TLSv1.1;
     ssl_prefer_server_ciphers   on;
  
     ssl_certificate 		 lvscheck.com.crt;
     ssl_certificate_key 	 lvscheck.com.key;

     # ... 
 }


# 业务项目配置
server {
     listen                      443 ssl;
     server_name                 s.com;

     ssl_protocols               TLSv1, TLSv1.1, TLSv1.2;
     ssl_prefer_server_ciphers   on;
  
     ssl_certificate 		 s.com.crt;
     ssl_certificate_key 	 s.com.key;

     # ... 
 }

业务项目 server 和 心跳检测 server 公用 443 端口,目前主流浏览器已经支持 SNI,所以也能获取正确的 https 证书。

但请注意如上两份配置的 ssl_protocols 指令,两个服务的该指令并不一样,由于心跳检测的配置是默认软件预装,所以新的 TLSv1.2TLSv1.3 默认并没有及时更新,而业务服务 server 配置是由项目同事开发所配,所以新增的了 TLSv1.2 版本。

于是我们就这样愉快的上了线,当我们使用浏览器测试时,浏览器警告,当前使用的 ssl 版本是 tlsv1.0 或 tlsv1.1 ,不安全,建议升级,如图:

问题

事情并没有按照我们预想的方向进行,明明我们在业务的 server 中配置了 ssl_protocols 指令,并且也支持了新增的 TLSv1.2 版本,但浏览器却使用的是 TLSv1 这个版本,怀疑是使用了 lvs 心跳检测 server 中的配置。

那么为什么会出现这样的原因呢?经过一番调查,我们终于找到了结论:ssl_protocols 指令配置,只有 default_server 配置有用

“ssl_protocols” won’t do anything good in non-default server{} blocks, even if SNI is used. https://forum.nginx.org/read.php?2,254016,254673#msg-254673

由于上述的两份 https 配置,我们并没有指定 default_server 指令,那么隐性的,第一份心跳检测的 server 配置作为了默认的 default_server 配置字段。

所以,nginxhttps 握手协商的时候,nginx 把心跳检测的 ssl_protocols 设置的版本错误的发送给了浏览器。

如何解决?

知道原因后,可以使用两种方式来解决,要么把业务 server 设置为 default_server,要么把心跳检测的 serverssl_protocols 版本和业务配置的字段保持一致即可。