0%

基于Stunnel加密通信方案设计, 突破防火墙限制

前言

我们在企业客户网络中部署了一些On-Premises的服务网关, 服务网关上部署了Squid代理软件, 为客户本地终端访问云服务提供正向代理
然而,对于使用较旧防火墙的客户来说,这些防火墙不支持基于FQDN(完全合格域名)的通配符匹配。这意味着当本地终端尝试访问云端资源时,用户必须逐一配置FQDN, 这种配置过程不仅繁琐,而且容易因为遗漏某些FQDN导致连接被防火墙阻止,造成网络中断,严重影响用户体验。
针对这个问题, 我们设计了一种基于Stunnel的加密通信方案, 绕过客户防火墙限制。 客户仅需配置Stunnel服务器的FQDN, 无需为每个目标服务器的FQDN进行单独设置, 从而简化了防火墙配置流程,提升了用户体验。

Stunnel是什么, 被设计用来解决什么问题

Stunnel是一款开源软件,其主要功能是为应用程序或服务提供TLS/SSL加密支持。 它的设计初衷是为了增强那些本身不支持加密功能的传统应用程序或服务的安全性
通过Stunnel, 可以在严格的防火墙规则下实现安全的加密通信,同时有效绕过可能存在的网络访问限制。

为什么需要Stunnel ?

直接通过墙内的Squid代理出去不行吗?

演示一下, 创建两台Linux虚拟机

  • 客户端 (192.168.52.200)
  • Squid代理 (192.168.52.204)

客户端通过Squid代理直接访问目标服务器www.trendmicro.com

1
curl -x 192.168.52.204:3128 https://www.trendmicro.com

wireshark抓包结果如下:

客户端和Squid代理之间报文

Squid代理到目标服务器之间的报文

从报文中可以看出, 防火墙可以直接识别目标服务器的IP, 这会导致连接被阻止.

HTTP tunnel(HTTP隧道)的概念

HTTP tunnel是HTTP/1.1引入的功能, 常用于两台网络受限的机器之间建立网络连接。 客户端通过发送HTTP CONNECT请求与代理建立TCP隧道, 以访问HTTPS服务
根据上面抓包结果, 可以梳理出HTTP tunnel的工作过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
客户端                                 Squid(代理)                           目标服务器
| | |
| --- TCP connection -->| |
| | |
| --- CONNECT www.trendmicro.com:443 -->| |
| | |
| | --- TCP connection -->|
| | |
| <-- HTTP 200 OK ---| |
| Connection Established | |
| | |
========================== CONNECT tunnel Established ======================= |
| | |
| --- TLS Application Data -->| |
| | |
| | --- TLS Application Data -->|
| | |
| | <-- TLS Stream ---|
| | HTTP 200 OK |
| | |
| <-- TLS Stream ---| |
| HTTP 200 OK | |
  • 客户端和代理服务器建立TCP连接
  • 然后, 客户端向代理发送明文的HTTP CONNECT请求, 告知代理需要连接的目标服务器
  • 代理收到请求后, 和目标服务器建立TCP隧道连接, 并返回200 Connection Established给客户端, 告诉客户端隧道已经成功建立
  • 隧道建立后, 代理仅负责转发数据而不解析或修改内容,确保了HTTPS数据的安全性,即使对于代理而言也无法解密TLS加密的内容

在墙外部署Squid代理行不行呢 ?

这种方案仍然会存在问题, 这里演示一下, 准备两台虚拟机, 一台云服务器

  • 客户端 (192.168.52.200)
  • 墙内代理 (192.168.52.204)
  • 墙外代理(云服务器) (47.103.80.253)

wireshark抓包:

客户端和墙内Squid代理之间的报文

墙内Squid到墙外Squid之间的报文

观察抓包结果可以发现:

  • 尽管正文部分是经过TLS加密的,但HTTP CONNECT请求是明文传输的,这意味着防火墙可以识别其中的目标服务器信息及代理认证信息(如用户名和密码), 此外,TLS握手过程中Client Hello报文中的SNI也会暴露目标服务器信息,因此这种方式仍然会被防火墙识别并阻止。
  • 另外,代理认证信息在公网上传输也是非常不安全的做法

基于Stunnel加密通信方案

说明:

  • 墙内部署Squid + Stunnel client, 墙外部署Stunnel Server + Squid, 客户终端的代理设置为墙内的Squid
  • 通过这种方案, 客户防火墙不需要逐一指定目标服务器的FQDN, 只需允许通往Stunnel服务器(端口443)的流量即可。

接下来,通过实际搭建环境演示一下

  • 墙内的主机(192.168.52.204), 部署 Squid Client + Stunnel Client
  • 墙外的云服务器(47.103.80.253), 部署 Stunnel Server + Squid Server

配置墙外的云服务器(部署 Stunnel Server + Squid Server)

  1. 安装软件

    1
    yum install -y squid stunnel
  2. 配置Squid

  • Squid启用basic认证

    1
    2
    3
    4
    5
    yum install -y httpd-tools
    htpasswd -c /etc/squid/squid_user test
    New password: qY8kd0Cf
    Re-type new password: qY8kd0Cf
    Adding password for user test
  • 设置密码,并验证密码文件是否正确生成

    1
    2
    3
    4
    5
    6
    cat /etc/squid/squid_user
    test:$XXXXXXXXXXXXXXXXXXX

    /usr/lib64/squid/basic_ncsa_auth /etc/squid/squid_user
    test qY8kd0Cf
    OK
  • 修改Squid配置文件(/etc/squid/squid.conf),添加以下认证相关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # Insert your own rules here to allow access from your clients

    # http_access allow localhost 加注释,表示localhost也需要认证

    auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/squid_user
    auth_param basic children 5
    auth_param basic realm Proxy Authentication Required
    auth_param basic credentialsttl 2 hours
    auth_param basic casesensitive on

    acl authUsers proxy_auth REQUIRED
    http_access allow authUsers

    http_access deny all
  • 重启Squid服务并测试

    1
    2
    3
    4
    5
    6
    systemctl restart squid

    curl -x localhost:3128 https://www.baidu.com
    curl: (56) Received HTTP code 407 from proxy after CONNECT
    # curl -x test:qY8kd0Cf@localhost:3128 https://www.baidu.com
    200 OK
  1. 配置Stunnel
  • 生成一个自签名证书

    1
    openssl req -new -x509 -days 365 -nodes -out /etc/stunnel/stunnel.pem -keyout /etc/stunnel/stunnel.pem
  • 编辑Stunnel配置文件/etc/stunnel/stunnel.conf

    1
    2
    3
    4
    5
    6
    cert = /etc/stunnel/stunnel.pem
    client = no

    [squid]
    accept = 443
    connect = 127.0.0.1:3128
  • 启动Stunnel

    1
    systemctl restart stunnel

配置墙内的机器(部署 Squid + Stunnel Client)

  1. 配置Stunnel
  • 编辑Stunnel配置文件 /etc/stunnel/stunnel.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    debug=5
    client = yes
    verify=0
    output=/var/log/stunnel.log
    pid = /var/run/stunnel.pid
    [upstream]
    accept = 8081

    connect=47.103.80.253:443
    sslVersion=TLSv1.2
  • 启动Stunnel, 使用curl测试

    1
    2
    3
    4
    systemctl restart stunnel

    curl -x 'test:qY8kd0Cf@localhost:8081' https://www.trendmicro.com -i
    200 OK
  1. 配置Squid
  • 修改Squid.conf, 所有流量都转发到本地Stunnel, 并指定云端Proxy的用户名和密码

    1
    2
    3
    4
    5
    6
    http_access allow localhost

    never_direct allow all
    cache_peer 127.0.0.1 parent 8081 0 no-query default login=test:qY8kd0Cf

    http_access deny all
  • 启动Squid, 并使用curl测试

    1
    2
    3
    systemctl start squid

    curl -x localhost:3128 https://www.trendmicro.com -i

测试墙内到墙外的数据是否加密

为了验证数据是否被加密,在客户端通过墙内代理访问墙外云服务器,并使用Wireshark抓包分析; 在客户端执行以下命令

1
curl -x 'test:qY8kd0Cf@192.168.52.204:8081' https://www.trendmicro.com -i

客户端到代理的报文:

墙内代理到墙外云服务器的报文:

可以看到这一段的请求数据已经被加密, 防火墙只能看到Stunnel服务器的FQDN, 无法识别真正的目标服务器

为啥不直接用HTTPS代理 ?

客户虚拟设备是临时部署的, HTTPS代理需要管理和维护SSL/TLS证书, 增加了部署的复杂性
在客户内网中使用HTTP代理可以保证安全,所有客户都走HTTPS代理有性能损失, Stunnel加密方案只针对防火墙配置有问题的部分客户开放

方案的缺点是什么

Stunnel在处理大量TLS加密流量时会遇到性能瓶颈, 参考stunnel performance data

我们的解决方法是水平扩展, 让客户多部署几台Stunnel, 以处理更多的终端

参考

squid + stunnel >> 跨越长城,科学上网!