OPENVPN 实践

2020/04/10 Linux 网络

OPENVPN 实践

VPN(全称Virtual Private Network)虚拟专用网络,是依靠ISP和其他的NSP,在公共网络中建立专用的数据通信网络的技术,可以为企业之间或者个人与企业之间提供安全的数据传输隧道服务。在VPN中任意两点之间的连接并没有传统专网所需的端到端的物理链路,而是利用公共网络资源动态组成的,可以理解为通过私有的隧道技术在公共数据网络上模拟出来的和专网有同样功能的点到点的专线技术。

操作均在 Ubuntu 18.04 server 下。

安装

安全前需要先安装 openssl 和 lzo。

$ sudo apt install openssl        # oepnssl
$ sudo apt install libssl-dev
$ sudo apt install liblzo2-2      # lzo
$ sudo apt install liblzo2-dev
$ sudo apt install libpam0g-dev   # pam
$ apt install openvpn
$ apt install easy-rsa

依靠 openvpn 实现 vpn 服务,依靠 easy-rsa 实现加密认证。

当通过 apt 安装完成后,主要的目录有:

- /usr/sbin/openvpn         执行的二进制程序
- /etc/openvpn              openvpn 需要的相关配置文件
- /usr/share/doc/openvpn    openvpn 的帮助文档以及示例配置文件
- /usr/share/easy-rsa       easy-rsa 生成密钥对的位置

生成密钥

配置 vars

首先需要修改 vars 文件。

$ cd /usr/share/easy-rsa
$ vi vars

vars 文件不改也能用,但是缺省的信息后期很难管理,生成的证书也没有和自身相关的部分,所以推荐修改以下参数。

# /usr/share/easy-rsa/vars
export KEY_COUNTRY="CN"
export KEY_PROVINCE="ZJ"
export KEY_CITY="hangzhou"
export KEY_ORG="xxxx"
export KEY_EMAIL="aaa@xxx.com"

根据自己的需求来修改,注意的是,当改完了这些内容后直接进行后续操作时,可能会遇到:

**************************************************************
  No /usr/share/easy-rsa/openssl.cnf file could be found
  Further invocations will fail
**************************************************************

我们也可以看到当前目录下确实没有 openssl.cnf,但是有 openssl-0.9.6.cnf,openssl-0.9.8.cnf,openssl-1.0.0.cnf。一般情况下,可以理解为 openssl-1.0.0.cnf 就是 openssl.cnf 文件,但是我们需要通过环境变量来指定。

# /usr/share/easy-rsa/vars

# 注释一行,新增一行
# export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
export KEY_CONFIG=$EASY_RSA/openssl-1.0.0.cnf

当 vars 配置完成后,通过下面的指令导入环境以及清理环境。

$ source vars
$ ./clean-all

生成 ca 证书

root@localhost:/usr/share/easy-rsa# ./build-ca
Can't load /root/.rnd into RNG
140106201145792:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Generating a RSA private key
..............................................+++++
......................+++++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [SanFrancisco]:
Organization Name (eg, company) [Fort-Funston]:
Organizational Unit Name (eg, section) [MyOrganizationalUnit]:
Common Name (eg, your name or your server's hostname) [Fort-Funston CA]:
Name [EasyRSA]:
Email Address [me@myhost.mydomain]:
root@localhost:/usr/share/easy-rsa# ll keys/
total 20
drwx------ 2 root root 4096 Apr  7 02:35 ./
drwxr-xr-x 3 root root 4096 Apr  7 02:33 ../
-rw-r--r-- 1 root root 1850 Apr  7 02:35 ca.crt
-rw------- 1 root root 1704 Apr  7 02:35 ca.key
-rw-r--r-- 1 root root    0 Apr  7 02:33 index.txt
-rw-r--r-- 1 root root    3 Apr  7 02:33 serial

在这个过程中,会有几次交互,让你确认或输入相关信息,如果已经在 vars 里配置好了,那么这里就直接回车好了,否则需要根据每一项单独输入。

生成服务器端证书和密钥 key 文件

root@xxx:/usr/share/easy-rsa# ./build-key-server server
Can't load /root/.rnd into RNG
140287103832512:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Generating a RSA private key
.....................................................................................................................................................................................................................+++++
...............+++++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [SanFrancisco]:
Organization Name (eg, company) [Fort-Funston]:
Organizational Unit Name (eg, section) [MyOrganizationalUnit]:
Common Name (eg, your name or your server's hostname) [server]:
Name [EasyRSA]:
Email Address [me@myhost.mydomain]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /usr/share/easy-rsa/openssl-1.0.0.cnf
Can't load /root/.rnd into RNG
140162557669824:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Can't open /usr/share/easy-rsa/keys/index.txt.attr for reading, No such file or directory
140162557669824:error:02001002:system library:fopen:No such file or directory:../crypto/bio/bss_file.c:72:fopen('/usr/share/easy-rsa/keys/index.txt.attr','r')
140162557669824:error:2006D080:BIO routines:BIO_new_file:no such file:../crypto/bio/bss_file.c:79:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :PRINTABLE:'CA'
localityName          :PRINTABLE:'SanFrancisco'
organizationName      :PRINTABLE:'Fort-Funston'
organizationalUnitName:PRINTABLE:'MyOrganizationalUnit'
commonName            :PRINTABLE:'server'
name                  :PRINTABLE:'EasyRSA'
emailAddress          :IA5STRING:'me@myhost.mydomain'
Certificate is to be certified until Apr  5 06:44:37 2030 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
root@xxx:/usr/share/easy-rsa# ll keys/
total 56
drwx------ 2 root root 4096 Apr  7 02:44 ./
drwxr-xr-x 3 root root 4096 Apr  7 02:39 ../
-rw-r--r-- 1 root root 5784 Apr  7 02:44 01.pem
-rw-r--r-- 1 root root 1850 Apr  7 02:42 ca.crt
-rw------- 1 root root 1704 Apr  7 02:42 ca.key
-rw-r--r-- 1 root root  149 Apr  7 02:44 index.txt
-rw-r--r-- 1 root root   21 Apr  7 02:44 index.txt.attr
-rw-r--r-- 1 root root    0 Apr  7 02:39 index.txt.old
-rw-r--r-- 1 root root    3 Apr  7 02:44 serial
-rw-r--r-- 1 root root    3 Apr  7 02:39 serial.old
-rw-r--r-- 1 root root 5784 Apr  7 02:44 server.crt
-rw-r--r-- 1 root root 1098 Apr  7 02:44 server.csr
-rw------- 1 root root 1704 Apr  7 02:44 server.key

中间的很多信息也是可以回车掠过,注意在最后需要手动确认两次。当执行完成后,会在 keys 目录下出现,server.crt,server.csr,server.key 和 01.pem 等文件。

生成客户端证书和 key 文件

可以通过 easy-rsa/build-key 生成单个用户专属的证书和 key,每个证书在同一时刻只能供一个客户端连接。下面会生成一个不需要密码的账户 test。

root@localhost:/usr/share/easy-rsa# ./build-key test
Can't load /root/.rnd into RNG
139841275879872:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Generating a RSA private key
...+++++
.......................+++++
writing new private key to 'test.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]:
State or Province Name (full name) [CA]:
Locality Name (eg, city) [SanFrancisco]:
Organization Name (eg, company) [Fort-Funston]:
Organizational Unit Name (eg, section) [MyOrganizationalUnit]:
Common Name (eg, your name or your server's hostname) [test]:
Name [EasyRSA]:
Email Address [me@myhost.mydomain]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /usr/share/easy-rsa/openssl-1.0.0.cnf
Can't load /root/.rnd into RNG
139913049067968:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :PRINTABLE:'CA'
localityName          :PRINTABLE:'SanFrancisco'
organizationName      :PRINTABLE:'Fort-Funston'
organizationalUnitName:PRINTABLE:'MyOrganizationalUnit'
commonName            :PRINTABLE:'test'
name                  :PRINTABLE:'EasyRSA'
emailAddress          :IA5STRING:'me@myhost.mydomain'
Certificate is to be certified until Apr  5 06:52:46 2030 GMT (3650 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
root@localhost:/usr/share/easy-rsa# ll keys/
total 88
drwx------ 2 root root 4096 Apr  7 02:52 ./
drwxr-xr-x 3 root root 4096 Apr  7 02:39 ../
-rw-r--r-- 1 root root 5784 Apr  7 02:44 01.pem
-rw-r--r-- 1 root root 5654 Apr  7 02:52 02.pem
-rw-r--r-- 1 root root 1850 Apr  7 02:42 ca.crt
-rw------- 1 root root 1704 Apr  7 02:42 ca.key
-rw-r--r-- 1 root root  296 Apr  7 02:52 index.txt
-rw-r--r-- 1 root root   21 Apr  7 02:52 index.txt.attr
-rw-r--r-- 1 root root   21 Apr  7 02:44 index.txt.attr.old
-rw-r--r-- 1 root root  149 Apr  7 02:44 index.txt.old
-rw-r--r-- 1 root root    3 Apr  7 02:52 serial
-rw-r--r-- 1 root root    3 Apr  7 02:44 serial.old
-rw-r--r-- 1 root root 5784 Apr  7 02:44 server.crt
-rw-r--r-- 1 root root 1098 Apr  7 02:44 server.csr
-rw------- 1 root root 1704 Apr  7 02:44 server.key
-rw-r--r-- 1 root root 5654 Apr  7 02:52 test.crt
-rw-r--r-- 1 root root 1098 Apr  7 02:52 test.csr
-rw------- 1 root root 1704 Apr  7 02:52 test.key

在这中间的提示输入项,可以根据需求填写。和 server key 类似,最后手动确认。会生成 02.pem,test.crt,test.csr,test.key 等文件。

生成 vpn 密钥协议交换文件

root@localhost:/usr/share/easy-rsa# ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
......++++++.......
root@localhost:/usr/share/easy-rsa# ll keys/
total 92
drwx------ 2 root root 4096 Apr  7 02:56 ./
drwxr-xr-x 3 root root 4096 Apr  7 02:39 ../
-rw-r--r-- 1 root root 5784 Apr  7 02:44 01.pem
-rw-r--r-- 1 root root 5654 Apr  7 02:52 02.pem
-rw-r--r-- 1 root root 1850 Apr  7 02:42 ca.crt
-rw------- 1 root root 1704 Apr  7 02:42 ca.key
-rw-r--r-- 1 root root  424 Apr  7 02:58 dh2048.pem
-rw-r--r-- 1 root root  296 Apr  7 02:52 index.txt
-rw-r--r-- 1 root root   21 Apr  7 02:52 index.txt.attr
-rw-r--r-- 1 root root   21 Apr  7 02:44 index.txt.attr.old
-rw-r--r-- 1 root root  149 Apr  7 02:44 index.txt.old
-rw-r--r-- 1 root root    3 Apr  7 02:52 serial
-rw-r--r-- 1 root root    3 Apr  7 02:44 serial.old
-rw-r--r-- 1 root root 5784 Apr  7 02:44 server.crt
-rw-r--r-- 1 root root 1098 Apr  7 02:44 server.csr
-rw------- 1 root root 1704 Apr  7 02:44 server.key
-rw-r--r-- 1 root root 5654 Apr  7 02:52 test.crt
-rw-r--r-- 1 root root 1098 Apr  7 02:52 test.csr
-rw------- 1 root root 1704 Apr  7 02:52 test.key

会等待一段时间,生成 keys/dh2048.pem 文件。

生成 HMAC firewall

$ openvpn --genkey --secret keys/ta.key

至此,密钥和证书生成工作已完成。

证书及密钥说明表

Filename Needed By Purpose Secret
ca.crt server+all client Root CA ertificate No
ca.key key signing machine only Root CA key YES
dh{n}.pem server only Diffie Hellman parameters NO
server.crt server only Server Certificate NO
server.key server only Server Key YES
clientl.crt clientl only Clientl Certificate NO
clientl.key clientl only Clientl Key YES

配置 server 端

我们进入到 /etc/openvpn 目录,开始操作。先拷贝一些文件到该目录下。

$ cp -r /usr/share/easy-rsa/keys /etc/openvpn
$ cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf /etc/openvpn 

然后修改 /etc/openvpn/server.conf 文件。

# /etc/openvpn/server.conf 修改后的结果
local aaa.bbb.ccc.ddd
port 1194
proto tcp
dev tun
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/server.crt
dh /etc/openvpn/keys/dh2048.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 192.168.1.0 255.255.255.0"
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 114.114.114.114"
client-to-client
duplicate-cn
keepalive 20 120
cipher AES-256-CBC
comp-lzo
user xxx
group xxx
persist-key
persist-tun
status openvpn-status.log
log         openvpn.log
verb 3
配置参数 参数说明
local 10.0.0.28(外网卡地址) 哪一个本地地址要被 openvpn 进行监听
port 1194 监听的端口,默认是 1194
proto tcp 指定监听的协议,当并发访问多时,推荐tcp
dev tun vpn server 的模式采用路由模式,可选 tap 或 tun
ca /etc/openvpn/keys/ca.crt ca 证书
cert /etc/openvpn/keys/server.crt  
key /etc/openvpn/keys/server.key this file should be kept secret
dh /etc/openvpn/keys/dh2048.pem vpn 密钥协议交换文件
server 10.8.0.0 这个是 vpn server 动态分配给 vpn client 的地址池,一般不需要更改。这个段不要和任何网络地址段冲突或者重复。
ifconfig-pool-persist ipp.txt  
push “route 172.16.1.0 255.255.255.0” 这个是 vpn server 所在的内网网段,如果有多个可以写多个 push,注意,此命令实际作用是在 vpn client 本地生成 vpn sever 所在的内网网段路由,确保能够和 vpn server 所在的内网网段通信
client-to-client 允许拨号的多个 vpn client 互相通信
duplicate-cn 允许多个客户端使用同一个帐号连接
keepalive 10 20 每 10 秒 ping 一次,若是 120 秒未收到包,即认定客户端断线
comp-lzo 开启压缩功能
persist-key 当 vpn 超时后,当重新启动 vpn 后,保持上一次使用的私钥,而不重新读取私钥
persist-tun 通过 keepalive 检测 vpn 超时后,当重新启动 vpn 后,保持 tun 或者 tap 设备自动连接状态
status openvpn-status.log openvpn日志状态信息
log /var/log/openvpn.log 日志文件
verb 3 指定日志文件冗余

server.cnf 文件配置完毕后,进行启动服务等操作。

# 关闭防火墙
root@xxx:/etc/openvpn# systemctl stop ufw.service
root@xxx:/etc/openvpn# systemctl status ufw.service
● ufw.service - Uncomplicated firewall
   Loaded: loaded (/lib/systemd/system/ufw.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Fri 2020-04-03 18:02:19 CST; 3 days ago
     Docs: man:ufw(8)
  Process: 14664 ExecStop=/lib/ufw/ufw-init stop (code=exited, status=0/SUCCESS)
 Main PID: 351 (code=exited, status=0/SUCCESS)

Apr 03 18:02:19 xxx systemd[1]: Stopping Uncomplicated firewall...
Apr 03 18:02:19 xxx ufw-init[14664]: Skip stopping firewall: ufw (not enabled)
Apr 03 18:02:19 xxx systemd[1]: Stopped Uncomplicated firewall.
Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

# 开启内核转发
root@xxx:/etc/openvpn# sed -i 's#net.ipv4.ip_forward = 0#net.ipv4.ip_forward = 1#' /etc/sysctl.conf
root@xxx:/etc/openvpn# sysctl -p

# 启动服务
root@xxx:/etc/openvpn# /usr/sbin/openvpn --config /etc/openvpn/server.conf &

# 查看虚拟网卡
root@xxx:/etc/openvpn# ip a|grep tun
11: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 100
    link/none
    inet 10.8.0.1 peer 10.8.0.2/32 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::ce49:7fb3:9eed:9e40/64 scope link stable-privacy
       valid_lft forever preferred_lft forever

# 盯着日志,准备数据接入
root@xxx:/etc/openvpn# tail -f /var/log/openvpn.log

配置 client 端

客户端需要安装软件。根据 2x HOW TO 提到的,不同的平台有不同的 GUI 程序,即便是 Linux 也可以通过编译源码的方式来安装。这里以 macOS 为基准,主要解释如何配置。

下载 OpenVPN Connect,启动。

从安装好 openvpn 服务的机器上拉取 client.cfg 和必要的key。

$ scp ca.crt test.crt test.key user@yourip:~/openvpn/keys
$ vi ~/openvpn/client.ovpn
client      # 代表客户端
dev tun     # 和 server 端一致
proto tcp   # 和 server 端一致
remote xxx.xxx.xxx.xxx 1194   # server.conf 里的 local 
resolv-retry infinite
nobind      # 不绑定
persist-key # 保持 key
persist-tun # 保持 tun
ca ~/keys/ca.crt
cert ~/keys/test.crt
key ~/keys/test.key
ns-cert-type server
comp-lzo
verb 3

双击这个 client.ovpn 文件或者在软件 OpenVPN Connect 中选择导入,点击连接即可。此时可以观察多了一块虚拟网卡,默认分配 ip 为 10.8.0.6。

通过 ping 做测试。

$ ping -I tun0 10.8.0.1
PING 10.8.0.1 (10.8.0.1) from 10.8.0.6 tun0: 56(84) bytes of data.
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=1.00 ms
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=1.37 ms
^C
--- 10.8.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.009/1.193/1.377/0.184 ms

可见 VPN 确实连接上了,但是尝试通过 tun0 口访问 baidu.com 是失败的。一开始以为是防火墙设置的问题,再以为是路由表配置的问题,三以为不转发的问题。查了很多资料,不得其解,又是一个未解之谜,等一个询问专业人士。

参考

关于连接 VPN 但无法连接互联网的问题:

Search

    Table of Contents