记一次Nginx代理访问Cloudflare域名出现502错误,顺便初识SNI


处理方式

  • 在 nginx 配置中增加如下配置即可

    proxy_ssl_server_name on;

复现现象

通过 nginx 代理 https://abc.nestealin.com/https://abc.nestealin.com/index.html 时出现 502,配置如下:

server {
    listen  80;
    server_name abc.nestealin.com ;
    rewrite ^(.*)$  https://$host$1 permanent;
}

server {
    listen    443 ssl http2;

    server_name  abc.nestealin.com ;
    charset utf-8;
    access_log  logs/$http_host.access.main.log  main;
    error_log  logs/abc.nestealin.com.error.log error;


    ssl_certificate      /data/keys/server.cer;
    ssl_certificate_key  /data/keys/server.key;
    ssl_session_timeout  5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 ;

    set $origin_root /data/web_static/nes_hexo_blog/;

    location = / {
        proxy_pass https://abc.nestealin.com/index.html;
    }

    location / {
        root $origin_root ;
        if (!-e $request_filename) {
            rewrite ^/(.*)$  / redirect;
        }
    }
}

报错如下:

直接访问 https://abc.nestealin.com/index.html 一切正常

初步判断问题出在正向代理的 proxy_pass 中。

排查过程

日志分析

  • 从访问日志来看:

    108.162.215.99:45784 443 - [23/Aug/2021:20:12:55 +0800] "GET / HTTP/2.0" 502 552 588 "https://abc.nestealin.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" 112.94.5.214 104.21.47.158:443, 172.67.148.224:443, [2606:4700:3031::6815:2f9e]:443, [2606:4700:3033::ac43:94e0]:443 502, 502, 502, 502 0.004, 0.025, 0.000, 0.000 0.030 abc.nestealin.com "- - -" "112.94.5.214"

    可见 nginx 接收请求后,相继尝试请求 104.21.47.158:443, 172.67.148.224:443, [2606:4700:3031::6815:2f9e]:443, [2606:4700:3033::ac43:94e0]:443 四个节点均被返回 502 状态码。

  • 从错误日志来看:

    2021/08/23 20:12:55 [error] 10501#0: *14158 SSL_do_handshake() failed (SSL: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure) while SSL handshaking to upstream, client: 108.162.215.99, server: abc.nestealin.com, request: "GET / HTTP/2.0", upstream: "https://104.21.47.158:443/index.html", host: "abc.nestealin.com", referrer: "https://abc.nestealin.com/"

    可见在正向代理请求 104.21.47.158:443 时在 SSL 握手阶段就已经断开。

抓包分析

目前只知道HTTPS握手不通过,接下来我们抓包对比下。

SSL 握手过程
  • 方便后续从抓包内容分析握手被拦截的阶段。
请求异常
  • 客户端在发出 Client Hello 后就被返回 Handshake Failure ,得不到 Server Hello 响应就已经结束连接
正常请求
  • 加上 proxy_ssl_server_name on; 配置恢复访问后,继续抓包分析。

    location = / {
      proxy_ssl_server_name on;
      proxy_pass https://abc.nestealin.com/index.html;
    }
  • 再次请求时,对比发现在 Client Hello 阶段新增了一个扩展类型 Extension: sever_name ,内容是一个 Server Name Indication extension 即 SNI

    但此时已经可以通过第一阶段,得到 Server Hello 响应

  • 此时,可以大致知道在连接过程中因为请求头没有携带域名,导致请求节点无法匹配证书进行握手交互。

请求验证

如何才能得到Server Hello

通过 openssl s_client 对节点进行请求测试

  • 不携带域名直接请求 Cloudflare 节点的443端口,发现无法匹配证书

    /usr/local/openssl111/bin/openssl s_client -connect 104.21.47.158:443 -tls1_2
    CONNECTED(00000003)
    4639555072:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl/record/rec_layer_s3.c:1528:SSL alert number 40
    ---
    no peer certificate available
    ---
    No client certificate CA names sent
    ---
  • 不携带域名直接请求 Baidu 节点的443端口,发现可以正常匹配证书

    /usr/local/openssl111/bin/openssl s_client -connect 14.215.177.38:443 -tls1_2
    CONNECTED(00000003)
     depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
     verify return:1
     depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
     verify return:1
     depth=0 C = CN, ST = beijing, L = beijing, OU = service operation department, O = "Beijing Baidu Netcom Science Technology Co., Ltd", CN = baidu.com
     verify return:1
     ---
  • 此时基本可以确定节点只以 SNI 方式匹配证书,所以这次携带域名直接请求 Cloudflare 节点的443端口

    /usr/local/openssl111/bin/openssl s_client -connect 104.21.47.158:443 -tls1_2 -servername abc.nestealin.com
    CONNECTED(00000003)
    depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
    verify return:1
    depth=1 C = US, O = Let's Encrypt, CN = R3
    verify return:1
    depth=0 CN = *.nestealin.com
    verify return:1

由上可知,Cloudflare节点需要带上请求主机,就可以进行证书校验。

为什么代理其它节点不会提示502,例如 Baidu、Aliyun

同样从上面试验不难发现,直接请求 Baidu 节点IP 的 443 端口是可以得到默认证书的响应,而 Cloudflare 则匹配不到或不传递域名,则不响应任何证书,这点就取决于各家 SNI 做法( SNI原理参考链接 )。

从而也就说明为何 Nginx 代理 Cloudflare 时会因握手失败导致 502 了。


结论

Cloudflare 节点因为只以 SNI 方式支持证书校验,进行 TLS 握手,导致通过 nginx 做正向代理时,需要加上 proxy_ssl_server_name on; 配置来支持代理请求时可以将请求主机名塞入 SNI 字段,才能进行 TLS 握手。

  • 官方配置说明

    • Syntax: proxy_ssl_server_name on | off;
    • Default: proxy_ssl_server_name off;
    • Context: http, server, location
      • This directive appeared in version 1.7.0.

    Enables or disables passing of the server name through TLS Server Name Indication extension (SNI, RFC 6066) when establishing a connection with the proxied HTTPS server.


相关链接


文章作者: NesTeaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesTeaLin !
  目录