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

在上一篇文章《使用squid搭建代理服务器》里,我们知道单单使用墙外的squid代理是不可能实现翻墙的,因为墙内主机到墙外squid之间发送的请求会被GFW监控到。即使是https,报文正文虽然是加密的,但报头是不加密的,依然会被GFW墙掉。

既然如此,如果我们从墙内到墙外squid之间的数据包是加密过的,那不就可以瞒过GFW了?这就需要用到stunnel

Stunnel 是一个自由的跨平台软件,用于提供全局的TLS/SSL服务。针对本身无法进行TLS或SSL通信的客户端及服务器,Stunnel可提供安全的加密连 接。该软件可在许多操作系统下运行,包括Unix-like系统,以及Windows。Stunnel依赖于某个独立的库,如OpenSSL或者 SSLeay,以实现TLS或SSL协议。

——百度百科

ps:除了stunnel外,比较有名的开源隧道软件还包括:由代理软件varnish-cache的母公司提供的hitch(前身是stud),国内VPN厂商曲径提供的qtunnel

一、squid + stunnel 翻墙大法

squid+stunnel翻墙大法的原理如下图:

屏幕快照 2016-04-03 下午12.45.31

用户将tcp包发给stunnel client;stunnel client将包加密,发送给stunnel server;stunnel server解密后发送给squid;squid将包中的http请求进行转发,然后再将请求结果返回给stunnel server;stunnel server加密发给stunnel client;stunnel client解密后交回给用户。这样,由于通过GFW的数据是被stunnel加密过的,用户就能放心的“科学上网”了。

根据stunnel client的部署位置,我们有两套实现方案:

方案1:墙外服务器部署squid + stunnel server;墙内用户主机部署stunnel client,并将浏览器代理设置为本机上的stunnel client。

这个方案的优点就是节约,只需要一台墙外服务器。缺点是墙内每个用户主机都要装stunnel client,如果只是自己一个人用的话还好,用户多的话就麻烦了。而且我个人很讨厌在电脑上装太多后台程序,有洁癖。=。=#

方案2:墙外服务器部署squid + stunnel server;墙内服务器部署stunnel client;墙内用户主机将浏览器代理设置为墙内的stunnel client服务器。

这个方案的优点就是对用户方便,傻瓜操作,只要修改浏览器代理设置即可使用。缺点就是不节约,需要两台服务器,墙外墙内各一台。

好在我就有阿里云跟亚马逊两台云服务器,所以我选择方案2来进行科学上网。

二、TLS/SSL证书

在安装配置stunnel之前,需要先了解下stunnel server与client之间是怎么进行TLS/SSL传输的。由于官方没有好一点的文档,所以可以参考这篇关于https原理的文章,http://blog.csdn.net/clh604/article/details/22179907,我觉的写的很好。这也是stunnel server与client加密传输的基础。

SO,在配置stunnel之前,我们得先准备好一个TLS/SSL证书。我们可以生成证书请求,然后发给权威CA机构,让他们给我们签发证书,权威CA机构签发的证书好处就是一般浏览器都能识别,但就是贵。我们也可以生成自签名的证书,这种证书的好处就是不花钱,但由于不是权威CA机构签发的,浏览器会弹出提示“证书不安全”。不过反正我们现在需要的证书只用在stunnel server与client之间,不用提供给用户浏览器,所以用自签名证书就好了。

2.1 安装openssl

一般linux发行版本都自带openssl,所以这里就不多讲了。

2.2 生成自签名证书

在要做为stunnel server的服务器上使用下面命令生成自签名证书:

openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem

req指令,用来创建和管理证书请求(Certificate Signing Request, CSR)以让第三方权威机构CA来签发我们需要的证书。也可以使用-x509参数来生成自签名证书。

-new参数,表示新建证书(或证书请求)

-x509参数,表示要生成x.509格式的证书而不是证书请求

-days参数,表示生成的证书的有效时间

-nodes参数,不要加密私钥(如果没有定义这个参数,执行openssl req命令时候会要求输入一个密码,来对要生成的私钥文件进行加密,然后后面stunnel或nginx这些服务器程序要使用这个私钥的时候就会被要求输入密码)

-out参数,要生成的证书(certificate)文件名(如果没有定义-x509参数,生成的就是证书请求certificate request)

-keyout参数,要生成的私钥(private key)文件名(上面使用了跟证书一样的文件名,并不会覆盖该文件,而是追加到一起)

 

三、安装配置stunnel

3.1 安装stunnel

我有两台服务器,墙外的亚马逊服务器用作stunnel server,墙内的阿里云服务器用作stunnel client。在两台服务器都安装好stunnel。

有的版本的stunnel安装后自动添加到系统service中了,可以用service stunnel start/stop/restart。有的版本不会自动添加,启动的时候就直接敲stunnel,停的话有个快捷的kill方式:

kill <code>{{EJS5}}</code>

PS:记得在系统的iptables(如果有的话)以及亚马逊、阿里云控制台的安全组策略中打开stunnel配置中用到的端口。

3.2 配置stunnel server

默认的stunnel配置文件在/etc/stunnel/stunnel.conf。关于更多参数信息,可以参考:https://www.stunnel.org/static/stunnel.html

; 设置工作目录
chroot = /var/run/stunnel/
; 设置stunnel的pid文件路径(在chroot下)
pid = /stunnel.pid
; 设置stunnel工作的用户(组)
setuid = root
setgid = root

; 开启日志等级:emerg (0), alert (1), crit (2), err (3), warning (4), notice (5), info (6), or debug (7)
; 默认为5
debug = 7
; 日志文件路径(我的server的版本有个bug,这个文件也被放在chroot路径下了,client的版本则是独立的=。=#)
output = /stunnel.log

; 证书文件,就是在本文2.2中用openssl生成的自签名证书(server端必须设置这两项)
cert = /etc/stunnel/stunnel.pem
; 私钥文件
key = /etc/stunnel/stunnel.pem

; 设置stunnel服务,可以设置多个服务,监听同的端口,并发给不同的server。
; 自定义服务名squid-proxy
[squid-proxy]
; 服务监听的端口,client要连接这个端口与server通信
accept = 3129
; 服务要连接的端口,连接到squid的3128端口,将数据发给squid
connect = 3128

; **************************************************************************
; * 下面这些配置我都注释掉了,但也需要了解下 *
; **************************************************************************

; 设置是否对传输数据进行压缩,默认不开启。
; 这是跟openssl相关的,如果你的openssl没有zlib,开启这个设置会导致启动失败(failed to initialize compression method)
;compression = zlib

; 设置ssl版本,这个也是跟安装的openssl有关的
;sslVersion = TLSv1

; Authentication stuff needs to be configured to prevent MITM attacks
; It is important to understand that this option was solely designed for access control and not for authorization
; It is not enabled by default!
; 下面这些配置用来定义是否信任对方发过来的证书。就好比浏览器访问https的时候,浏览器默认会信任那些由权威CA机构签发的证书,
; 对于那些自签名证书,浏览器就会弹出对话框提醒用户这个证书可能不安全,是否要信任该证书。
; 这是有效防止中间人攻击的手段
; verify 等级2表示需要验证对方发过来的证书(默认0,不需要验证,都信任)
; 因为这个配置是server端的,我们不需要理会client的证书(client也不会没事发证书过来啦)
;verify = 2
; CAfile 表示受信的证书文件,即如果对方发过来的证书在这个CAfile里,那么就是受信任的证书;否则不信任该证书,断开连接。
;CAfile = /etc/stunnel/stunnel-client.pem

 

3.3 配置stunnel client

因为我会在client的配置中开启了证书验证,就是对于对方发过来的证书文件,client需要去CAfile中进行匹配,匹配到的证书才是受信证书,才允许建立连接。这样的话,我们就需要把server端的证书拷贝过来。我是直接打开server端的stunnel.pem,然后将里面CERITIFICATE拷贝到client端新建的/etc/stunnel/stunnel-server.pem文件中。

屏幕快照 2016-04-03 下午5.51.50

; stunnel工作目录
chroot = /var/run/stunnel/
; stunnel工作的用户组
setuid = root
setgid = root
; stunnel工作时候的pid
pid = /stunnel.pid

; 日志等级
debug = 7
; 日志文件
output = /var/log/stunnel/stunnel.log

; 表示以client模式启动stunnel,默认client = no,即server模式
client = yes

; 定义一个服务
[squid-proxy]
; 监听3128端口,那么用户浏览器的代理设置就是 stunnel-client-ip:3128
accept = 3128
; 要连接到的stunnel server的ip与端口
connect = xx.xx.xx.xx:3129

; 需要验证对方发过来的证书
verify = 2
; 用来进行证书验证的文件(里面有stunnel server的证书)
CAfile = /etc/stunnel/stunnel-server.pem

; 客户端不需要传递自己的证书,所以注释掉
;cert = /etc/stunnel/stunnel.pem
;key = /etc/stunnel/stunnel.key

3.4 双向认证

上面3.2+3.3的配置中,client需验证来自对方的证书是否可信,而server不需要验证对方的证书,这是常规的https的做法,可以有效防止中间人攻击。

但如果我们不希望stunnel server被别人利用,就应该进行双向认证,实现client的access control。即在连接时候,client也需要给server发送自己的证书,server验证对方证书可信才进行连接,这样可以避免server被其他人搭建的client利用(当然这个可能性很小)。

但是!如果进行双向认证的话,肯定要比单向认证更耗时间,降低连接效率。我觉得更好的办法是在stunnel server的防火墙上限制对server端口的访问,只允许来自我们自建的client-ip的连接,对于其他ip则直接拒绝。同理,也可以在client端去掉对server证书的认证,通过防火墙进行限制。注意这只是忽略掉了对证书的认证,server-client之间的连接还是需要用ssl进行加密的,server还是得传递共有证书给client。

曲径的qtunnel敢说自己比stunnel快,据说就是因为他们把server-client之间建立连接时的ssl握手给取消了。

 

四、配置squid进行用户授权

配置好上面的stunnel后,stunnel client接收用户数据并发送给stunnel server,因为我的stunnel server与squid都部署在一台亚马逊云服务器上,所以即使我不修改squid的配置,使用默认的squid.conf,它也能处理stunnel server发过来的请求。

这是因为数据从stunnel server交给squid的时候,它的源ip地址已经变成127.0.0.1了!!!而不是用户的源ip。此时squid根本无法获取真实用户ip。由于squid的默认配置是http_access  allow  localhost的,所以它允许处理stunnel server发给它的所有请求。这样非常不好,因为我们没法对stunnel client做用户验证,这就相当于只要有人连接到我的stunnel client端口就能使用这套翻墙代理了。

那么为了进行用户验证,我们就必须在squid上注释掉这个http_access  allow  localhost,然后开启auth_param,验证程序的配置参考我的上一篇文章《使用squid搭建代理服务器》

 

五、更好的ss翻墙架构

在上文的介绍中,由于stunnel无法做用户验证,只能把这个任务交到墙外的squid手上。不管用户能否通过验证,其验证过程都要进行stunnel加密传输,这样会浪费我们的服务器资源。而且更重要的,在上面的架构中,我们无法根据用户目标地址进行流量转发控制,即对于国内网站,本来无需翻墙的,上面的架构无法实现这个转发控制。

好在我们可以通过squid hierarchy来完善我们的架构,只需墙内服务器在搭建一个squid,该墙内squid负责接收用户请求,进行用户验证以及流量转发控制。

屏幕快照 2016-04-12 下午4.54.31

 

PS: @2016.04.23,好像可以把墙外的stunnel server去掉,squid b通过https_port与stunnel client直连,取代stunnel server的解密工作。这样做的话,不知道能否提高效率。因为虽然看起来过程中的节点少了,但实际上步骤还是一样的,而且stunnel默认就是多进程工作的,可能解密效率会高于squid,但不知道stunnel server与squid b之间的通信是否会耗时,squid也可以通过worker指令开启多进程协作。诶,这个真得测试一下才知道。

 

又PS:@2016.08.31,经回帖的一位网友提醒,我特意去查了下squid hierarchy,发现cache_peer指令也可以使用ssl来进行连接,那么这样的话,我就完全可以抛弃stunnel作为中间层,直接让墙内的squid与蔷薇的squid通过ssl进行通信。哈哈,谢谢这位匿名网友。

20 thoughts on “squid + stunnel >> 跨越长城,科学上网!”

  1. 感谢写的太好了 详细明了 。简单易懂。一直怕中间人攻击 不知道如何配置 。谢谢你的文章。

  2. squid 可以用ssl连接。为何还要用一个stunnel
    感觉很奇怪。网上都这么写,很少有直接用squid单独用的

    https_port 0.0.0.0:4430 cert=/root/stunnel/publickey.pem key=/root/stunnel/privatekey.pem

    客户端直接导入 publickey.pem 以后,直接用HTTPS 代理即可

    1. 谢谢你,我一开始也没发现https_port的用法,也是参考网上其他人的做法做的。后来发现的时候,我的“梯子”的方案都已经搭建好了,就懒的改了。
      经你已提醒,我还特意去查了下squid hierarchy,发现在squid层级之间也可以直接使用ssl通信,这样在我的方案中就可以完全避开stunnel了。

      1. 你好,直接用squid ssl通信配置文件怎么写呢,我服务器根据test用户上面的0.0.0.0:4430 这样配置。squid无法成功运行

  3. Pingback: 使用squid搭建代理服务器-刘鑫的博客

  4. 您好,一条IP用squid代理设置多个端口比如202.18.19.03:1011,202.18.19.03:1012,202.18.19.03:1013.到底怎么弄?大神请教下,谢谢!

  5. 能帮我搭个墙吗,我有阿里云北京和阿里云新加坡服务器,按照你写的搭了,但是用火狐测试访问Google时,北京服务器的stunnel.log什么记录都没有…

  6. 首先感谢博主高质量的分享。有个点没太理解。

    >client也需要给server发送自己的证书,server验证对方证书可信才进行连接。

    按RSA理论来说,证书本身就是可以公开的,它是CA签名或自签名的公钥,这样其他客户端也自然可以冒充,所以Server端不应该依据证书来判断client的指定身份。

    >上面3.2+3.3的配置中,client需验证来自对方的证书是否可信。

    这里Client验证证书可信,核心目的是防止Server被冒充,Client会用冒充者的公钥加密,然后再传给冒充者,这样的加密没有意义,因此Client要验证证书的确是Server的才能有效防止冒充,也就是中间人。如果冒充者,盗用Server的证书,发给Client,Client用证书加密,没关系呀,即使发给了冒充者,冒充者没有Server的私钥,解密不了。也就是说“client需验证来自对方的证书是否可信”,核心是为了加密,信息不被盗看,而不是确认Server的身份。

    为什么需要双向的呢?很多时候并不需要。为什么呢?因为Server把证书发给Client后,双方就协商出一个对称密钥了,用这个对称密钥进行回话,这样Client给Server发送请求,Server给Client发送响应,也都可以加密。那么Server需要确切知道Client的身份吗?有时候的确需要知道,但通常都是依赖于在应用层做一个账号+密码。大家接触最多的双向认证,应该是 git,用git命令向github推送代码的时候(不用账号密码,可以使用RSA),这是需要客户端用私钥签名,让服务端用公钥验签的。

    1. 可能我表述的有点别扭。

      首先,client需要验证server的证书这点没问题吧?就像常规HTTPS一样的套路。
      你指出“核心是为了加密,信息不被盗看,而不是确认server的身份”这句话确实是对的。但是,你没有更好的办法可以用来确认现在与你连接的server是不是真的server本人,还是server公钥的盗用者。既然盗用者没有办法解密你的信息,那么我们可以假设不会有这么无聊的盗用者了吧,那公钥就可以用来唯一标识server了。只要公钥对了,就认为是真的server发过来的。如果真有人盗用公钥假装server,那你也没办法呀。都到这地步了,就相当于电信把你网线剪掉了,你无能为力呀。

      然后,到这里为止,说明说你已经搭建了一个stunnel的server。可以供所有人连接进来,对吧?
      但是这可是你自己私人的服务器呀,你不会希望你的服务器给所有人连接吧?
      所以,让服务器去验证客户端的公钥,就是希望不会有别人盗用你的身份与你的服务器进行连接。
      那么又回到上面那个问题,真的有人盗用你的公钥与server连接了,那怎么办? 没办法呀,你的服务器还是会给他发数据,不过反正他也无法解密,就当做你多浪费些流量给他囖。

      就这样。 不知道你能不能理解我想说的了。

    1. sorry,具体怎么用我也没试过。你可以参考下http://www.squid-cache.org/Doc/config/cache_peer/的TLS OPTIONS这一块。

  7. 存在一个问题,GFW可以检测长时间TCP连接的IP,这样一来 CS架构就不太能够逃过检测了

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top