API 返回 502 Bad Gateway,但网站正常的原因与解决方法

1 问题描述

  • 项目技术栈:

Nginx -> K8S (service – Node Port) -> Springboot

  • 问题描述:有一个提供给第三方的接口,用于接收他们发送的数据。第三方反馈他们在请求该接口时,始终收到 502 网关错误。但我们使用 Postman 测试接口时,一切正常。尝试在浏览器中直接访问该接口,收到 请求方法不被允许 的错误(因为接口期望的是 POST 请求)。

2 排查思路

502网关错误,优先排查Nginx配置问题,再排查网络策略问题,最后排查应用问题。

2.1 先排查Nginx

2.1.1 应用Nginx的配置

server {

    listen 80 ;

    listen 443 ssl http2 ;

    server_name **.com.com;

    index index.php index.html index.htm default.php default.htm default.html;

    proxy_set_header Host $host;

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_set_header X-Forwarded-Host $server_name;

    proxy_set_header X-Real-IP $remote_addr;

    proxy_http_version 1.1;

    proxy_set_header Upgrade $http_upgrade;

    proxy_set_header Connection $http_connection;

    access_log /www/sites/**.com.com/log/access.log main;

    error_log /www/sites/**.com.com/log/error.log;

    location ^~ /.well-known/acme-challenge {

    allow all;

    root /usr/share/nginx/html;

    }

    include /www/sites/***.com/proxy/*.conf;

    if ($scheme = http) {

    return 301 https://$host$request_uri;

    }

    ssl_certificate /www/sites/***.com/ssl/fullchain.pem;

    ssl_certificate_key /www/sites/***.com/ssl/privkey.pem;

    ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1 SSLv3 SSLv2;

    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    ssl_prefer_server_ciphers on;

    ssl_session_cache shared:SSL:10m;

    ssl_session_timeout 10m;

    error_page 497 https://$host$request_uri;

    proxy_set_header X-Forwarded-Proto https;

}
  • 配置中有日志的地方,查看日志:
    • access_log /www/sites/**.com.com/log/access.log main;
    • error_log /www/sites/**.com.com/log/error.log;

没有报错日志! 没有请求日志!

  • 检查 Nginx 的配置文件,确保服务器块的配置正确。
  • 查看 Nginx 的访问日志和错误日志,发现 没有相关的请求日志,也 没有错误日志
  • 结论:请求可能 未到达 Nginx,需要进一步排查网络层面的问题。

2.2 排查服务器网络策略

检查防火墙和安全组设置:

  • 确认服务器的 80 和 443 端口已开放,无网络策略拦截。
  • 结论:网络策略未阻挡请求,问题可能不在网络层。

2.3 试用非公司网络访问

使用非公司网络访问

  • 使用手机热点访问,访问正常!
  • 使用其他省份网络访问(外省windows 浏览器访问,GET请求显示类型错误,nginx有日志),说明访问正常!
  • 结论:问题可能与特定网络环境有关,但由于第三方仍然无法访问,需要继续排查服务器配置

2.4 重新检查Nginx配置

排查Nginx其他配置,查看配置中的的日志文件:

user root;

worker_processes auto;

error_log /var/log/nginx/error.log notice;

error_log /dev/stdout notice;

pid /var/run/nginx.pid;

events {

    worker_connections 1024;

}

http {

    include mime.types;

    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';

    server_tokens off;

    access_log /var/log/nginx/access.log main;

    access_log /dev/stdout main;

    sendfile on;

    server_names_hash_bucket_size 512;

    client_header_buffer_size 32k;

# 其他配置

}

发现错误日志:

024/10/01 03:48:58 [error] 105#105: *11927 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443

2024/10/01 03:48:58 [error] 105#105: *11928 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443

2024/10/01 03:48:59 [error] 105#105: *11930 cannot load certificate "data:": PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking, client: 111.11.111.111, server: 0.0.0.0:443

根据日志找到:“data:”相关的配置:

map "" $empty {

default "";

}

server {

    listen 80;

    listen 443 ssl http2;

    server_name _;

    ssl_ciphers aNULL;

    ssl_certificate data:$empty;

    ssl_certificate_key data:$empty;

    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;

    index 404.html;

    root /usr/share/nginx/html;

}

配置说明:

  • 这个配置是网站的默认配置,当访问的域名不在配置中时,会使用这个配置,这个配置是一个默认的配置,没有配置证书。

找到了问题原因

  • 表面错误:证书配置错误,导致SSL握手失败,导致502错误
  • 根本原因:客户在请求时没有携带 SNI,导致Nginx找不到对应的证书,导致SSL握手失败,导致502错误!

3 SNI介绍

3.1 概念介绍

1. SNI 的作用

  • SNI(Server Name Indication,服务器名称指示) 是 TLS 协议的一个扩展。它的主要作用是在建立 TLS/SSL 握手 时,客户端在 TLS 握手的早期阶段(具体来说,是在 Client Hello 消息中)告诉服务器它想访问的主机名(域名)。

2.为什么需要 SNI

  • 在同一个 IP 地址和端口上托管多个 HTTPS 网站时,服务器需要知道客户端想访问哪个域名,以便提供对应的 SSL 证书。因为在 TLS 握手完成之前,HTTP 请求还没有开始,所以服务器无法通过 HTTP 头部(如 Host 头)来判断客户端想访问的域名。

3. SNI 的工作流程

  • 客户端发起 TLS 连接,在 Client Hello 消息中包含 SNI 扩展,指明目标域名。
  • 服务器收到 Client Hello,读取其中的 SNI 信息,根据域名选择相应的 SSL 证书,完成 TLS 握手。
  • TLS 握手完成后,开始进行加密的 HTTP 通信。

4. 如果客户端不支持 SNI 或不提供 SNI 信息

  • 服务器无法知道客户端想访问哪个域名,只能使用默认的 SSL 证书(通常是在 Nginx 配置中第一个被加载的服务器块,或者显式指定的默认服务器)。
  • 如果默认服务器的 SSL 证书与客户端请求的域名不匹配,会导致证书错误,连接失败。

3.2 请求流程

  • 域名解析与请求过程
    • 域名解析:客户端首先通过 DNS 将域名解析为 IP 地址。
    • 建立连接:客户端使用解析得到的 IP 地址发起 TCP 连接。
    • TLS 握手:如果是 HTTPS 请求,客户端会在 TCP 连接建立后开始 TLS 握手。在这个阶段,客户端需要提供 SNI 信息(即请求的域名)。
    • HTTP 请求:TLS 握手成功后,客户端才会发送加密的 HTTP 请求,包括 HTTP 头部信息(如 Host 头)。
  • SNI 信息的位置
    • SNI 信息是在 TLS 握手的 Client Hello 消息中,而不是在 HTTP 请求头中。因为在 TLS 握手完成之前,HTTP 请求还没有发送。
    • HTTP 请求头中的 Host 头:用于在服务器上区分不同的虚拟主机,但这发生在 TLS 握手之后。

  • 服务器如何处理没有 SNI 的请求
    • 服务器无法根据 SNI 信息选择证书,只能使用默认的 SSL 证书。
    • 如果默 认证书与请求的域名不匹配,客户端会收到证书错误,或者连接被拒绝。

3.3 为什么浏览器访问正常,Postman访问正常,但第三方代码请求接口不正常

  • 浏览器访问正常:浏览器会携带 SNI 信息,所以 Nginx 可以根据请求的域名选择正确的 SSL 证书。
  • Postman 访问正常:Postman 也会携带 SNI 信息。
  • 第三方代码请求不正常:第三方代码可能没有携带 SNI 信息,导致 Nginx 无法选择正确的 SSL 证书,从而导致 SSL 握手失败。

4 解决方案

4.1 方法一:配置默认服务器的有效 SSL 证书

操作步骤:

  • 修改 Nginx 默认服务器块,提供有效的 SSL 证书:
server {
    listen 443 ssl http2 default_server;
    server_name _;

    ssl_certificate /www/sites/example.com/ssl/fullchain.pem;
    ssl_certificate_key /www/sites/example.com/ssl/privkey.pem;

    # 其他配置...
}
  • 重启 Nginx 服务,使配置生效。
  • 效果:客户端未提供 SNI 信息时,Nginx 使用默认的有效证书,完成 SSL 握手。

4.1 方法二:让客户端发送 SNI 信息

操作步骤:

  • 与第三方沟通,确认他们的客户端是否支持 SNI。
  • 如果不支持,建议他们升级客户端或修改代码,确保在 TLS 握手中发送 SNI 信息。

发表评论