CentOS7部署OpenVPN实现内网互通

说明

  • 此法用于多个客户端通过OpenVPN服务器实现内网访问
  • OpenVPN服务器操作系统CentOS-7.7
  • OpenVPN版本2.4.8
  • easy-rsa版本3.0.6
  • 使用tap模式
  • 客户端IP地址池10.8.0.0/24
  • 多个客户端直接可以通过OpenVPN实现内网通信

准备操作

添加EPEL源

1
yum install epel-release -y

替换为阿里云的源

1
2
3
4
5
sed -e 's,^#baseurl,baseurl,g' \
-e 's,^metalink,#metalink,g' \
-e 's,^mirrorlist=,#mirrorlist=,g' \
-e 's,http://download.fedoraproject.org/pub,https://mirrors.aliyun.com,g' \
-i /etc/yum.repos.d/epel.repo

更新软件

1
2
yum makecache
yum update -y

修改sysctl参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat > /etc/sysctl.d/99-net.conf <<EOF
# 二层的网桥在转发包时也会被iptables的FORWARD规则所过滤
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
# 关闭严格校验数据包的反向路径,默认值1
net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.all.rp_filter=0
# 设置 conntrack 的上限
net.netfilter.nf_conntrack_max=1048576
# 端口最大的监听队列的长度
net.core.somaxconn=21644
# TCP阻塞控制算法BBR,Linux内核版本4.9开始内置BBR算法
#net.ipv4.tcp_congestion_control=bbr
#net.core.default_qdisc=fq
# 打开ipv4数据包转发
net.ipv4.ip_forward=1
# TCP FastOpen
# 0:关闭 ; 1:作为客户端时使用 ; 2:作为服务器端时使用 ; 3:无论作为客户端还是服务器端都使用
net.ipv4.tcp_fastopen=3
EOF

修改limits参数

1
2
3
4
cat > /etc/security/limits.d/99-centos.conf <<EOF
* - nproc 1048576
* - nofile 1048576
EOF

安装OpenVPN软件

1
yum install -y openvpn easy-rsa lrzsz iptables-services

配置服务器证书

复制文件

拷贝easy-rsa的文件到/etc/openvpn下

1
2
cp -r /usr/share/easy-rsa/3.0.6 /etc/openvpn/easy-rsa
cp /usr/share/doc/easy-rsa-3.0.6/vars.example /etc/openvpn/easy-rsa/vars

修改vars

编辑vars文件

1
vim /etc/openvpn/easy-rsa/vars

修改以下几项

1
2
3
4
5
6
7
8
9
10
11
12
#set_var EASYRSA_REQ_COUNTRY     "US"
#set_var EASYRSA_REQ_PROVINCE "California"
#set_var EASYRSA_REQ_CITY "San Francisco"
#set_var EASYRSA_REQ_ORG "Copyleft Certificate Co"
#set_var EASYRSA_REQ_EMAIL "me@example.net"
#set_var EASYRSA_REQ_OU "My Organizational Unit"
#set_var EASYRSA_KEY_SIZE 4096
#set_var EASYRSA_ALGO rsa
#set_var EASYRSA_CA_EXPIRE 3650
#set_var EASYRSA_CERT_EXPIRE 365
#set_var EASYRSA_CERT_RENEW 180
#set_var EASYRSA_CRL_DAYS 60

初始化PKI和CA

切换目录

1
cd /etc/openvpn/easy-rsa

创建PKI

1
./easyrsa init-pki

创建CA

1
./easyrsa build-ca nopass

创建服务器证书

1
./easyrsa build-server-full openvpn-server nopass

创建DH证书

1
./easyrsa gen-dh

拷贝证书

1
2
3
4
5
6
7
8
mkdir -p /etc/openvpn/pki
cp /etc/openvpn/easy-rsa/pki/ca.crt \
/etc/openvpn/easy-rsa/pki/dh.pem \
/etc/openvpn/easy-rsa/pki/issued/openvpn-server.crt \
/etc/openvpn/easy-rsa/pki/private/openvpn-server.key \
/etc/openvpn/pki/
ln -sv /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/pki/crl.pem
chown -R root:openvpn /etc/openvpn/pki

创建ta.key

1
openvpn --genkey --secret /etc/openvpn/pki/ta.key

配置OpenVPN

创建日志目录

1
2
mkdir -p /var/log/openvpn
chown -R openvpn:openvpn /var/log/openvpn

创建客户端配置目录

1
2
mkdir -p /etc/openvpn/client/{config,user}
chown -R root:openvpn /etc/openvpn/client/{config,user}

配置OpenVPN服务器端

1
vim /etc/openvpn/server/srv.conf

下面内容按需更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 监听地址
#local 0.0.0.0
# 监听端口
port 51194
# 通信协议
proto tcp
# TUN模式还是TAP模式
dev tap
# 证书
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/openvpn-server.crt
key /etc/openvpn/pki/openvpn-server.key
dh /etc/openvpn/pki/dh.pem
crl-verify /etc/openvpn/pki/crl.pem
# 禁用OpenVPN自定义缓冲区大小,由操作系统控制
sndbuf 0
rcvbuf 0
# TLS rules “client” | “server”
#remote-cert-tls "client"
# TLS认证
tls-auth /etc/openvpn/pki/ta.key
# TLS最小版本
#tls-version-min "1.2"
# 重新协商数据交换的key,默认3600
#reneg-sec 3600
# 在此文件中维护客户端与虚拟IP地址之间的关联记录
# 如果OpenVPN重启,重新连接的客户端可以被分配到先前分配的虚拟IP地址
ifconfig-pool-persist /etc/openvpn/ipp.txt
# 配置client配置文件
client-config-dir /etc/openvpn/client/config
# 该网段为 open VPN 虚拟网卡网段,不要和内网网段冲突即可。
server 10.8.0.0 255.255.255.0
# 配置网桥模式,需要在OpenVPN服务添加启动关闭脚本,将tap设备桥接到物理网口
# 假定内网地址为192.168.0.0/24,内网网关是192.168.0.1
# 分配192.168.0.200-250给VPN使用
#server-bridge 192.168.0.1 255.255.255.0 192.168.0.200 192.168.0.250
# 给客户端推送自定义路由
#push "route 192.168.0.0 255.255.255.0"
# 所有客户端的默认网关都将重定向到VPN
#push "redirect-gateway def1 bypass-dhcp"
# 向客户端推送DNS配置
#push "dhcp-option DNS 223.5.5.5"
#push "dhcp-option DNS 223.6.6.6"
# 允许客户端之间互相访问
client-to-client
# 限制最大客户端数量
#max-clients 10
# 客户端连接时运行脚本
#client-connect ovpns.script
# 客户端断开连接时运行脚本
#client-disconnect ovpns.script
# 保持连接时间
keepalive 20 120
# 开启vpn压缩
comp-lzo
# 允许多人使用同一个证书连接VPN,不建议使用,注释状态
#duplicate-cn
# 运行用户
user openvpn
#运行组
group openvpn
# 持久化选项可以尽量避免访问那些在重启之后由于用户权限降低而无法访问的某些资源
persist-key
persist-tun
# 显示当前的连接状态
status /var/log/openvpn/openvpn-status.log
# 日志路径,不指定文件路径时输出到控制台
# log代表每次启动时清空日志文件
#log /var/log/openvpn/openvpn.log
# log-append代表追加写入到日志文件
#log-append /var/log/openvpn/openvpn.log
# 日志级别
verb 4
# 忽略过多的重复信息,相同类别的信息只有前20条会输出到日志文件中
mute 20

启动OpenVPN-server

1
2
3
chown -R root:openvpn /etc/openvpn
systemctl enable openvpn-server@srv.service
systemctl start openvpn-server@srv.service

添加SNAT访问内网

  • 10.8.0.0/24访问192.168.0.0/24的时候做SNAT,源地址改成OpenVPN服务器的内网地址
1
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -d 192.168.0.0/24 -j SNAT --to-source OPENVPN_SERVER_IP

定期更新CRL

说明

  • OpenVPN的server配置里面添加了crl-verify之后,需要给crl定期更新
  • 如果crl文件长期不更新,过期之后,OpenVPN会报无法验证证书,导致无法连接OpenVPN
  • 连接日志如下,提示CRL has expired: CN=client_1
1
2
3
4
5
6
7
8
9
10
11
12
13
openvpn[28570]: TCP connection established with [AF_INET]202.96.128.86:7688
openvpn[28570]: TCPv4_SERVER link local: (not bound)
openvpn[28570]: TCPv4_SERVER link remote: [AF_INET]202.96.128.86:7688
openvpn[28570]: 202.96.128.86:7688 TLS: Initial packet from [AF_INET]202.96.128.86:7688, sid=cec51529 1051f9aa
openvpn[28570]: 202.96.128.86:7688 WARNING: Failed to stat CRL file, not (re)loading CRL.
openvpn[28570]: 202.96.128.86:7688 VERIFY ERROR: depth=0, error=CRL has expired: CN=client_1
openvpn[28570]: 202.96.128.86:7688 OpenSSL: error:14089086:SSL routines:ssl3_get_client_certificate:certificate verify failed
openvpn[28570]: 202.96.128.86:7688 TLS_ERROR: BIO read tls_read_plaintext error
openvpn[28570]: 202.96.128.86:7688 TLS Error: TLS object -> incoming plaintext read error
openvpn[28570]: 202.96.128.86:7688 TLS Error: TLS handshake failed
openvpn[28570]: 202.96.128.86:7688 Fatal TLS error (check_tls_errors_co), restarting
openvpn[28570]: 202.96.128.86:7688 SIGUSR1[soft,tls-error] received, client-instance restarting
openvpn[28570]: TCP/UDP: Closing socket

配置systemd timer

定义Service

1
vim /usr/lib/systemd/system/easyrsa-gen-crl.service
1
2
3
4
5
6
7
8
9
[Unit]
Description=easyrsa-gen-crl
[Service]
Type=oneshot
# 重新生成CRL文件
ExecStart=/etc/openvpn/easy-rsa/easyrsa gen-crl
# 命令完成之后重启OpenVPN服务
ExecStartPost=/usr/bin/systemctl restart openvpn-server@srv.service
WorkingDirectory=/etc/openvpn/easy-rsa

定义timer

1
vim /usr/lib/systemd/system/easyrsa-gen-crl.timer
1
2
3
4
5
6
7
8
9
10
[Unit]
Description=easyrsa-gen-crl

[Timer]
# 每周一凌晨4点触发easyrsa-gen-crl.service
OnCalendar=Mon *-*-* 04:00:00
Persistent=true

[Install]
WantedBy=timers.target

启动timer

1
2
systemctl enable easyrsa-gen-crl.timer
systemctl start easyrsa-gen-crl.timer

立刻启动service

1
systemctl start easy-rsa-gen-crl.service

查看Service日志

1
journalctl -xfu easy-rsa-gen-crl.service

查看timer状态

1
systemctl status easyrsa-gen-crl.timer

客户端管理

客户端配置模板

1
vim /etc/openvpn/templates/ovpn.template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 配置为客户端模式
client
# 与服务器端保持一致
proto tcp
# 与服务器端保持一致
dev tap
# 配置服务器端的地址和端口
remote vpn-server 51194
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo
nice 0
verb 3
mute 10
# 禁用OpenVPN自定义缓冲区大小,由操作系统控制
sndbuf 0
rcvbuf 0
# 禁止在内存中缓存password
auth-nocache
# TLS rules “client” | “server”
#remote-cert-tls server
# 过滤从服务器端发过来的路由规则
#pull-filter ignore redirect-gateway
# 不从服务器端拉取路由规则
#route-nopull
# 手动指定所有流量走VPN
#redirect-gateway def1
# 客户端自定义路由,例如192.168.0.0/24走VPN,192.168.1.0/24不走VPN
#route 192.168.0.0 255.255.255.0 vpn_gateway
#route 192.168.1.0 255.255.255.0 net_gateway

指定客户端推送配置

模板

1
vim /etc/openvpn/templates/ipconfig_push.template
1
2
3
4
5
6
7
8
9
10
11
12
# 给客户端推送固定的IP地址
ifconfig-push #IP地址# 255.255.255.0
# 给客户端推送路由
#iroute 192.168.0.0 255.255.0.0
# 为客户端开启压缩
#comp-lzo yes
# 推送客户端配置
#push "comp-lzo yes"
# 客户端连接时,自动添加SNAT规则
#client-connect iptables -t nat -A POSTROUTING -s #IP地址#/24 -o eth0 -d 内网地址 -j MASQUERADE
# 客户端断开连接时,自动删除SNAT规则
#client-connect iptables -t nat -D POSTROUTING -s #IP地址#/24 -o eth0 -d 内网地址 -j MASQUERADE

客户端配置

这里的客户端名字指的是证书的Common Name,这里以user1为例

1
vim /etc/openvpn/client/config/user1
1
2
# 给客户端推送固定的IP地址10.8.0.100
ifconfig-push 10.8.0.100 255.255.255.0

用户管理脚本

1
2
vim /usr/local/bin/ovpn_mgmt.sh
chmod +x /usr/local/bin/ovpn_mgmt.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/bin/bash
set -e

# 定义环境变量
export EASY_RSA_DIR="/etc/openvpn/easy-rsa"
export CLIENT_CONFIG_DIR="/etc/openvpn/client/config"
export PKI_DIR="${EASY_RSA_DIR}/pki"
export TEMPLATES_DIR="/etc/openvpn/templates"
export CLIENT_USER_DIR="/etc/openvpn/client/user"

# 创建ovpn配置
generate_ovpn() {
USER_OVPN="${CLIENT_USER_DIR}/${EXPIRED}-${USER}.ovpn"
# 根据证书生成对应的ovpn文件
mkdir -p ${CLIENT_USER_DIR}/
cp -f ${TEMPLATES_DIR}/ovpn.template ${USER_OVPN}
echo "<ca>" >> ${USER_OVPN}
cat ${PKI_DIR}/ca.crt >> ${USER_OVPN}
echo "</ca>" >> ${USER_OVPN}
echo "<cert>" >> ${USER_OVPN}
cat ${PKI_DIR}/issued/${USER}.crt >> ${USER_OVPN}
echo "</cert>" >> ${USER_OVPN}
echo "<key>" >> ${USER_OVPN}
cat ${PKI_DIR}/private/${USER}.key >> ${USER_OVPN}
echo "</key>" >> ${USER_OVPN}
echo "<tls-auth>" >> ${USER_OVPN}
cat /etc/openvpn/pki/ta.key >> ${USER_OVPN}
echo "</tls-auth>" >> ${USER_OVPN}
sz --binary ${USER_OVPN}
}

# 添加客户端IP
client_ip_config() {
# 根据ip地址生成对应的client-config
sed -e "s,#ipaddress#,${IPADDRESS},g" \
${TEMPLATES_DIR}/ipconfig_push.template \
> ${CLIENT_CONFIG_DIR}/${USER}
}

# 生成客户端证书
add_cert() {
# 切换到easy-rsa目录
cd ${EASY_RSA_DIR}
${EASY_RSA_DIR}/easyrsa build-client-full ${USER} nopass
}

# 注销客户端证书
revoke_cert() {
# 切换到easy-rsa目录
cd ${EASY_RSA_DIR}
echo "yes" | ${EASY_RSA_DIR}/easyrsa revoke ${USER}
${EASY_RSA_DIR}/easyrsa gen-crl
}

# 更新客户端证书
renew_cert() {
# 切换到easy-rsa目录
cd ${EASY_RSA_DIR}
echo "yes" | ${EASY_RSA_DIR}/easyrsa renew ${USER} nopass
}

# 检查证书过期时间
check_cert_expired() {
export EXPIRED=$(date --date="$(openssl x509 -enddate -noout -in ${PKI_DIR}/issued/${USER}.crt |cut -d= -f 2)" --iso-8601)
}

# 创建用户
add_user() {
if [[ -e "${EASY_RSA_DIR}/pki/reqs/${USER}.req" ]];then
read -p"此用户已经申请证书,是否重新生成?[y/n]:" ANSWER
case ${ANSWER} in
y|Y)
revoke_cert
check_cert_expired
client_ip_config
add_cert
generate_ovpn
exit 0
;;
n|N)
check_cert_expired
client_ip_config
generate_ovpn
;;
esac
else
add_cert
check_cert_expired
client_ip_config
generate_ovpn
fi
}
# 删除用户
del_user() {
revoke_cert
rm -rf ${CLIENT_USER_DIR}/*${USER}.ovpn
rm -rf ${CLIENT_CONFIG_DIR}/${USER}
}
# 用户旧证过期重新生成证书
renew_user() {
if [[ -e "${EASY_RSA_DIR}/pki/reqs/${USER}.req" ]];then
renew_cert
check_cert_expired
generate_ovpn
else
echo "用户证书不存在!"
exit 1
fi
}

main() {
# 切换到easy-rsa目录
cd ${EASY_RSA_DIR}
# 根据传入的method运行对应函数
${METHOD}
}

# 获取参数
while getopts 'm:u:i:r' OPT;do
case $OPT in
u)
USER="$OPTARG"
echo "USER=${USER}"
;;
i)
IPADDRESS="$OPTARG"
echo "IPADDRESS=${IPADDRESS}"
;;
m)
METHOD="$OPTARG"
echo "METHOD=${METHOD}"
;;
?)
echo "Usage: $(base \"$0\") -m [add_user|del_user|renew_user] -u USER -i IPADDRESS"
exit 0
;;
esac
done

main

添加用户

1
/usr/local/bin/ovpn_mgmt.sh -m add_user -u USERNAME -i 10.8.0.10

删除用户

1
/usr/local/bin/ovpn_mgmt.sh -m del_user -u USERNAME