找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1138

积分

0

好友

146

主题
发表于 20 小时前 | 查看: 0| 回复: 0

随着网络环境对安全性要求的不断提升,支持安全通信的四层代理已成为必备能力。Nginx 的 Stream 模块不仅能处理裸 TCP 连接,还能通过 SSL 模块处理 TLS/SSL 加密流,或通过 SSL 预读模块在不中断加密的情况下获取关键信息,从而实现灵活的流量路由。本文将深入解析这两个核心模块的指令与实践。

SSL 阶段

在 TCP 四层代理场景中,直接透传加密流量或对流量进行加解密转换是常见需求。Nginx Stream 模块能够处理来自下游客户端的 TLS/SSL 加密连接,并根据需要将其转换为裸 TCP 流转发至上游服务器。

使用 TLS/SSL 主要有以下三种典型应用场景,下图清晰地展示了它们的数据流差异:

Nginx Stream四层代理TLS/SSL三种应用场景图

  • 场景1:透传 SSL 流。Nginx 不解析 SSL,直接将客户端发来的加密 TCP 流原样转发给上游服务器。
  • 场景2:剥离 SSL 层(重点)。Nginx 解密客户端发来的 TLS 协议,将其转换为明文 TCP 协议后再发送至上游服务。
  • 场景3:后端加密。客户端与 Nginx 之间使用明文 TCP,但 Nginx 向上游服务发起连接时启用 TLS/SSL 加密。

ssl 模块详解

ngx_stream_ssl_module 模块使 Stream 反向代理能够处理下游客户端的 TLS/SSL 协议。该模块默认未被编译进 Nginx,需要通过 --with-stream_ssl_module 参数显式启用。

Stream SSL 模块的指令与 HTTP 模块中的 SSL 指令高度相似,以下列出部分常用指令及其配置示例。

stream {
  map $ssl_alpn_protocol $proxy {
    h2                  127.0.0.1:8001;
    http/1.1            127.0.0.1:8002;
  }

  # 全局配置,例如私钥密码文件
  ssl_password_file /etc/keys/global.pass;

  server {
    listen      12346 ssl;
    proxy_pass  $proxy;

    # 支持的 ALPN 协议列表
    ssl_alpn    h2 http/1.1;

    # 支持多证书(RSA 和 ECDSA)
    ssl_certificate     example.com.rsa.crt;
    ssl_certificate_key example.com.rsa.key;

    ssl_certificate     example.com.ecdsa.crt;
    ssl_certificate_key example.com.ecdsa.key;

    # 私钥密码文件
    ssl_password_file global.pass;

    # 支持的协议版本
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

    # 支持的加密套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;

    # SSL 握手超时时间(Stream 模块特有)
    ssl_handshake_timeout 10s;
  }
}

以下是 Stream SSL 与 HTTP SSL 常用指令的快速对照表,方便你理解:

Stream SSL与HTTP SSL常用指令对照表

核心指令速览

  • ssl_alpn:声明服务器支持的 ALPN 协议列表。
    Syntax: ssl_alpn protocol ...;
    Default: —
    Context: stream, server
  • ssl_certificatessl_certificate_key:指定服务器证书和私钥路径。
    Syntax: ssl_certificate file;
    Syntax: ssl_certificate_key file;
    Default: —
    Context: stream, server
  • ssl_ciphers:指定支持的加密套件列表。
    Syntax: ssl_ciphers ciphers;
    Default: ssl_ciphers HIGH:!aNULL:!MD5;
    Context: stream, server
  • ssl_protocols:指定支持的 TLS/SSL 协议版本。
    Syntax: ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3];
    Default: ssl_protocols TLSv1.2 TLSv1.3;
    Context: stream, server
  • ssl_handshake_timeout:设置 SSL 握手超时时间,默认 60 秒。
    Syntax: ssl_handshake_timeout time;
    Default: ssl_handshake_timeout 60s;
    Context: stream, server

此外,Stream SSL 模块还提供了丰富的内建变量,用于获取连接的安全信息,主要分为以下几类:

安全套件与协议信息

  • $ssl_cipher:本次连接使用的加密套件。
  • $ssl_protocol:使用的 TLS 协议版本(如 TLSv1.2)。
  • $ssl_alpn_protocol:协商的 ALPN 协议(如 h2)。
  • $ssl_session_reused:会话是否复用(“r”表示复用,“.”表示新建)。

客户端证书信息

  • $ssl_client_s_dn:客户端证书主题信息。
  • $ssl_client_i_dn:客户端证书颁发者信息。
  • $ssl_client_verify:客户端证书验证结果。
  • $ssl_client_v_remain:客户端证书剩余有效天数。

实践:SSL 剥离与 TCP 转发

本示例展示 Nginx 作为四层反向代理,将客户端的 SSL 连接解密后,以明文 TCP 形式转发至上游 HTTP 服务器。请注意,Stream 模块仅处理 TCP 和 SSL 层,不解析上层的 HTTP 协议。

步骤1:编译启用 Stream SSL 模块

./configure --prefix=/usr/local/nginx --with-stream --with-stream_ssl_module
make -j $(nproc) && make install

步骤2:配置 Nginx

  • Stream 代理服务器配置 (192.168.10.2):

    worker_processes auto;
    events {
      worker_connections  1024;
    }
    
    stream {
      log_format basic '$remote_addr:$remote_port "$realip_remote_addr:$realip_remote_port" [$time_local] '
                      '$protocol $status $bytes_sent $bytes_received '
                      '$session_time $ssl_protocol $ssl_session_reused';
      access_log /var/log/nginx/stream_access.log basic;
    
      server {
        listen 8443 ssl; # 监听 SSL 端口
        set_real_ip_from 10.20.172.0/24;
    
        # SSL 证书配置
        ssl_certificate /usr/local/nginx/certs/server.crt;
        ssl_certificate_key /usr/local/nginx/certs/server.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5:!SHA1;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
    
        # 转发至上游裸 TCP 服务
        proxy_pass 10.20.172.213:8011;
      }
    }
  • 上游 HTTP 服务器配置 (192.168.10.3):

    server {
      listen 8011; # 监听明文 TCP 端口
      server_name _;
      root /usr/local/nginx/html;
      index index.html;
    
      location /api {
        return 200 '$time_iso8601 $request_id\n';
      }
    }

步骤3:验证测试
启动服务后,通过浏览器访问 https://server.weiyigeek.top:8443/index.html。同时在上游服务器抓包,可以观察到 TCP 三次握手后直接是明文的 HTTP 请求,证实 SSL 已被 Nginx 剥离。

tcpdump抓包验证SSL剥离结果

步骤4:查看日志
检查 Stream 访问日志,$ssl_session_reused 变量可以显示会话复用情况(. 为新建,r 为复用)。

tail -f /var/log/nginx/stream_access.log
# 示例输出:... TLSv1.3 .   # 新建连接
# 示例输出:... TLSv1.3 r   # 复用连接

查看上游服务器的访问日志,确认请求已成功转发。

上游服务器HTTP访问日志

至此,我们完成了 SSL 解密并转发裸 TCP 流的常见四层代理模式。

PREREAD 阶段

在某些场景下,需要在保持 SSL 加密流完整(不解密)的前提下,获取客户端信息以进行路由决策。例如,根据客户端 TLS 握手信息中的 SNI 域名,将加密流量转发至不同的上游服务器。这正是 ngx_stream_ssl_preread_module 模块的用武之地。

基于SSL预读模块的域名路由场景

ssl_preread 模块详解

stream_ssl_preread_module 模块默认未编译,需通过 --with-stream_ssl_preread_module 启用。它能够在 SSL 握手初期预读 Client Hello 消息,提取其中的 TLS 版本、SNI 域名和 ALPN 协议等信息,而不进行解密,随后将完整的加密流转发至上游。

核心指令

  • ssl_preread:启用预读功能。
    Syntax: ssl_preread on | off;
    Default: ssl_preread off;
    Context: stream, server
  • preread_buffer_size:设置预读缓冲区大小,默认 16k。
  • preread_timeout:等待 Client Hello 完成的超时时间,默认 30s。

关键变量

  • $ssl_preread_server_name:从 SNI 扩展中提取的请求域名。
  • $ssl_preread_protocol:客户端支持的最高 TLS 版本。
  • $ssl_preread_alpn_protocols:客户端建议的 ALPN 协议列表。

重要提示ssl_prereadssl 指令(用于解密)不能同时使用于同一个 server 块。因为 ssl_preread 需要原始的加密数据流,而 ssl 指令会将其解密。

实践:基于 SNI 的加密流量路由

本示例演示如何使用 ssl_preread 模块,根据客户端请求的域名(SNI),将加密的 HTTPS 流量透明转发至不同的上游服务器。

步骤1:编译启用模块

./configure --prefix=/usr/local/nginx --with-stream --with-stream_ssl_preread_module
make -j $(nproc) && make install

步骤2:配置基于 SNI 的路由

worker_processes auto;
events {
  worker_connections  1024;
}

stream {
  log_format ssl_basic '$remote_addr ... ssl_preread=[$ssl_preread_server_name, $ssl_preread_protocol, $ssl_preread_alpn_protocols]';
  access_log /var/log/nginx/stream_access.log ssl_basic;

  # 根据预读的域名映射上游服务器
  map $ssl_preread_server_name $proxy_server {
    test.weiyigeek.top    www.weiyigeek.top:443;  # 路由到首页
    blog.weiyigeek.top    blog.weiyigeek.top:443; # 路由到博客
    default               10.20.172.213:8443;     # 默认后端
  }

  server {
    listen 8443; # 注意这里没有`ssl`参数
    set_real_ip_from 10.20.172.0/24;
    resolver 8.8.8.8 valid=30s;

    ssl_preread on; # 启用预读
    proxy_pass $proxy_server; # 动态路由
  }
}

步骤3:测试验证
配置 DNS 或 hosts 文件,将测试域名指向 Nginx 服务器 IP。

  1. 访问 https://test.weiyigeek.top:8443,流量应被转发至作者首页。
    访问test域名被路由至首页
  2. 访问 https://blog.weiyigeek.top:8443,流量应被转发至作者博客。
    访问blog域名被路由至博客
  3. 直接访问 IP https://10.20.172.214:8443(无 SNI),流量将转发至默认后端。

步骤4:查看预读日志
日志清晰记录了预读模块提取的信息,证明路由决策的依据。

tail -f /var/log/nginx/stream_access.log
# 输出示例:ssl_preread=[blog.weiyigeek.top, TLSv1.3, h2,http/1.1]
# 输出示例:ssl_preread=[test.weiyigeek.top, TLSv1.3, h2,http/1.1]
# 输出示例:ssl_preread=[, TLSv1.3, h2,http/1.1] # 无SNI情况

SSL预读模块日志输出

总结

通过本文的详解与实践,我们掌握了 Nginx Stream 模块中两个处理 SSL 的关键能力:

  1. ngx_stream_ssl_module:用于在四层代理中终止 TLS/SSL 连接,将加密流量转换为明文 TCP 转发至上游,适用于需要解密审计或上游服务不支持 HTTPS 的场景。
  2. ngx_stream_ssl_preread_module:用于在不中断加密的前提下,智能读取 TLS 握手信息(如 SNI、ALPN),并基于这些信息实现灵活的加密流量路由。这在需要透明转发 HTTPS 或其它基于 TLS 的协议(如 SSH over TLS)时非常有用,是构建现代、智能的网络四层网关的核心组件。

理解这两个模块的差异与适用场景,能帮助你在实际的基础架构工作中,更精准地设计流量治理方案。无论是用于安全隔离、协议转换还是智能路由,Nginx Stream 都提供了强大而灵活的工具集。

技术探索永无止境

希望这篇深入浅出的指南能为你带来启发。如果你在实践过程中有任何心得或疑问,欢迎在云栈社区与其他开发者交流探讨。




上一篇:C++17 std::shared_mutex 详解:读写锁原理、加解锁机制与 GCC 源码分析
下一篇:解析SUSE Linux Enterprise 16 SP0:企业级Linux的长期支持与云原生实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-9 21:41 , Processed in 0.425335 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表