SSL VPN --- (C-S)OpenVPN搭建过程
学习完SSL,我们来看一下如何构建一个基于SSL的OpenVPN。OpenVPN一般提供客户端/服务器模式,也就是客户端连接到服务器端,可以访问服务器端,但服务器端无法主动访问客户端,这是通过隧道(tunnel)来实现的。大致流程是客户端对服务器发起SSL连接,连接建立后,所有流量通过SSL传输,OpenVPN还会加上特定的报头表示这是OpenVPN报文。
有关OpenVPN的下载网站在https://www.techspot.com/downloads/5182-openvpn.html
我这里使用的是OpenVPN2.4.7,下载地址:https://files02.tchspt.com/storage2/temp/openvpn-2.4.7.tar.gz
我使用一台Linux服务器作为服务端,客户端准备了一台Linux和一台Windows。
先决条件
在Linux服务端和Linux客户端先安装一些软件,并配置服务端支持转发功能。
yum install lzo lzo-devel pam pam-devel vim wget -y
yum install openssl-devel openssl gcc gcc-c++ cmake -y
查看tun模块是否支持
modinfo tun # 查看模块信息
开启路由转发功能
vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
sysctl -p
服务端安装过程
在服务端安装OpenVPN。
wget https://files02.tchspt.com/storage2/temp/openvpn-2.4.7.tar.gz
tar -zxvf openvpn-2.4.7.tar.gz
cd openvpn-2.4.7
./configure
make && make install
有了软件,根据SSL握手的流程,我们还需要证书。如果不了解可以先看一下之前介绍SSL的文章,如何获取一个证书?如果只是为了测试,不值得去买一个,所以我们可以自己生成证书,具体步骤请看如何生成自签名证书。
现在我们有证书了,具体要有些什么证书呢?OpenVPN如果要选证书验证的话,还必须要使用双向认证,所以这就需要把很多证书,证书链,私钥分别放到客户端和服务器。以下面这个证书链为例:
Root.key Root.crt
|
Second.key Second.crt
|
Server.key Server.crt
Root证书签发了Second证书,Second证书签发了Server证书,我们的Server证书是服务器使用的,为了方便起见,双向认证的客户端也使用Server证书。所以,两边需要放的材料如下:
- 服务端:证书链(Second.crt+Root.crt) 证书(Server.crt) 私钥(Server.key) Diffie-Hellman参数(dh.pem)
- 客户端:证书链(Second.crt+Root.crt) 证书(Server.crt) 私钥(Server.key)
这里两边放的内容是一样的,为什么呢,因为不管是签发流程还是证书都是一样的。应该是一种设定,使用OpenVPN的话客户端和服务端证书的签发者必须是一样的,因为没有专门的配置选项来区分客户端和服务端的CA。😐 这个dh.pem也是一个强制选项,应该是需要DH算法参与进密钥交换,但是也应该是两边都有啊,光服务端设置这么一个是有什么说法,于是抓包看一下,抓包看了之后server hello之后的报文都没有被wireshark解析出内容,这。。。
之前的博客里只有生成证书的步骤,在这里把生成dh.pem的步骤加上:
openssl dhparam -out dh.pem 1024
最后的1024是DH参数集大小,1024可以马上生成完毕,如果是2048的话就需要几分钟了。
准备好这些材料后开始编写配置文件server.conf:
local 10.0.0.45
port 8850
proto udp
dev tun
ca ./secondCA.crt
cert ./server.crt
key ./server.key
dh ./dh.pem
#push "redirect-gateway def1 bypass-dhcp"
push "route 10.0.0.0 255.255.0.0"
client-to-client
log /var/log/openvpn/openvpn.log
server 10.11.0.0 255.255.255.0
push "dhcp-option DNS 114.114.114.114"
#compress lzo
duplicate-cn
keepalive 10 120
comp-lzo
persist-key
persist-tun
log-append /var/log/openvpn/server.log
status /var/log/openvpn/status.log
verb 3
explicit-exit-notify 1
具体的含义可以看一下https://www.bbsmax.com/A/B0zqenGGzv/
explicit-exit-notify只用在UDP协议,作用是在服务端重启让客户端自动重连。
comp-lzo两端配置必须一致。
配置完成,可以启动服务端了:
/usr/local/sbin/openvpn --config server.conf
启动后,可以换一个终端查看日志:
tail -f /var/log/openvpn/openvpn.log
日志输出如下说明服务端正常启动:
Sun Apr 19 03:11:22 2020 TUN/TAP device tun0 opened
Sun Apr 19 03:11:22 2020 TUN/TAP TX queue length set to 100
Sun Apr 19 03:11:22 2020 /sbin/ifconfig tun0 10.11.0.6 pointopoint 10.11.0.5 mtu 1500
Sun Apr 19 03:11:22 2020 /sbin/route add -net 10.0.0.0 netmask 255.255.0.0 gw 10.11.0.5
Sun Apr 19 03:11:22 2020 /sbin/route add -net 10.11.0.0 netmask 255.255.255.0 gw 10.11.0.5
Sun Apr 19 03:11:22 2020 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Sun Apr 19 03:11:22 2020 Initialization Sequence Completed
客户端安装过程
在客户端安装OpenVPN。
wget https://files02.tchspt.com/storage2/temp/openvpn-2.4.7.tar.gz
tar -zxvf openvpn-2.4.7.tar.gz
cd openvpn-2.4.7
./configure
make && make install
证书什么的就不赘述了,直接上配置文件client.ovpn:
client
dev tun
proto udp
remote 1.1.1.1 8850
#user nobody
#group nobody
nobind
ca ./secondCA.crt
cert server.crt
key server.key
#remote-cert-tls server
#comp-lzo
verb 3
#route nopull
具体含义请参考https://www.bbsmax.com/A/pRdBBRwPdn/
route nopull的意思是不接受服务端发来的路由,所以可以自己配。。
有些老哥可能会使用ns-cert-type server这个参数,目前ns-cert-type应该被淘汰了,应当使用remote-cert-tls。那么使用不使用这个参数是取决于证书,如果你的证书里面有extend Key usage这个extension,那可以用,如果没有,那就不要用。
然后就可以运行客户端了,我这里以Linux作为客户端。
/usr/local/sbin/openvpn --config client.ovpn
连接的过程中会有各种报错,例如我遇到了这个错误
Sat Apr 18 21:53:44 2020 TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)
Sat Apr 18 21:53:44 2020 TLS Error: TLS handshake failed
这个错误在客户端和服务器都会出现,从字面上来说是本地判定TLS握手失败,但不代表对端也这么判定。例如,服务端校验客户端证书失败,服务端日志里会有校验失败的错误,但是客户端这里不会有什么提示,只有一个这个60s错误。所以遇到问题最好看一下双方的日志。如果是网络问题,延迟比较大,丢包严重,也会导致这个错误出现。
或者这个
Bad LZO decompression header
这是两边的压缩选项不匹配导致的。
到这一步,VPN是连上了,下面来说通信的事情。
通信过程
客户端连接上VPN的时候,会新增一个网卡,例如tun0.
tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1500
inet 10.11.0.6 netmask 255.255.255.255 destination 10.11.0.5
inet6 fe80::c9b3:da7c:184a:45b3 prefixlen 64 scopeid 0x20<link>
unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 100 (UNSPEC)
RX packets 3 bytes 252 (252.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6 bytes 396 (396.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
这个网卡地址是tunnel的地址,是服务器给客户端分配的。如果查看操作系统路由表例如route -n
[root@ip-192-168-0-186 ec2-user]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 eth0
10.0.0.0 10.11.0.5 255.255.0.0 UG 0 0 0 tun0
10.11.0.0 10.11.0.5 255.255.255.0 UG 0 0 0 tun0
10.11.0.5 0.0.0.0 255.255.255.255 UH 0 0 0 tun0
169.254.169.254 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
也可以看到有一部分相应的路由。按照路由交换的基本法,我客户端想去访问10.0.0.156,怎么办呢,查一下路由表,发现应当走tun0这个网卡。那我tun0这个网卡连接的是一个点对点网络,这个时候也就不需要考虑网关什么其他的了,直接发就完事了。所以数据包从tun0发出去,源地址肯定是tun0了,目的地址是10.0.0.156.下一步,OpenVPN会对流经这个网卡的数据包进行处理,该封装封装,该解封解封。服务端会收到一个数据包,源地址是10.11.0.6,目的地址是10.0.0.156。这个时候如何处理呢,这就需要看服务端的了。
之前我们说过让服务端开启了转发功能ip_forward。然而这还不够,因为这个转发只是表面意义的转发,就是把这个数据包原样发出去,这样会有一个问题,我服务端知道10.11.0.6是谁,但10.0.0.156可不知道10.11.0.6是谁,这包能到,但是无法回来。那么这种情况下我们需要给服务端加一个NAT功能,让客户端访问其他网络的数据包的源地址变为服务端的IP地址。可以执行以下命令:
iptables -t nat -A POSTROUTING -s 10.11.0.0/255.255.0.0 -o eth0 -j MASQUERADE
不得不说,iptables功能确实强大,这个回头再说。这样一来,客户端就可以顺利地和其他网络通信了。