请注意技术使用场景的合规性!切勿滥用!

需求背景

众所周知,将SSH端口等敏感端口直接暴露在公网上是有一定风险的。尽管基于证书的验证是相当安全的,但是只要端口暴露了就总会有人试图往里面泼水,更别提那些没鉴权的软件了。在阿里云上也是一样。很显然,只是需要SSH连接的话可以用网页端的免密登录来进行操作。然而,有的时候需要的并不仅仅是一个SSH端口。例如,YDJSIR在做数据集成的作业时就发现Hadoop,Spark等软件一旦启动会暴露非常多的端口,且默认状态下都没有鉴权,这显然是很危险的。特别是Spark,总要绑定一个网卡实际有的IP才行,但那是阿里云内网IP,没法直接访问(其实有一个workaround,先给当前ECS实例加一个弹性网卡,然后开一个弹性公网IP,以EIP网卡可见模式绑定即可)。然而,即使可以访问,这也是非常不安全的。为了解决上述问题,最直接的思路显然是想办法让运维/开发人员的设备与云服务器置身同一内网中,也就是说,像是在同一个VPC里面一样。不过,阿里云提供的相关服务门槛相当高,不适合小微开发者(真的买不起,那些感觉都是给需要大型企业或组织,甚至是要过等保要求的那种组织用的)。既然如此,那么就自己动手,丰衣足食吧。

YDJSIR选择了开源的OPVN(UDP+TUN方案)来解决这样的需求。为什么选择它?他和传统L2TP,PPTP相比要现代、安全和高效,和Wireguard相比有更好的跨平台支持和文档(也许?)。那为什么YDJSIR不用光线呢?YDJSIR不想去体验特供茶叶的味道。OPVN阿里云是有在国内站给官方教程的,那就是它了吧(参考资料附)毕竟YDJSIR从初中开始就在用了。

参考资料

Docker和非Docker反正本来的东西都是一样的,所以也没什么特别的区别,对吧。所以首先是通用的参考资料。

序号 标题 备注
1 How To Set Up and Configure an OPVN Server on CentOS 7 本文主要参考的方案
2 【干货】ECS服务器OPVN搭建,方便管理所有内网服务器 本文配置的重要参考
3 在CentOS系统的ECS实例中如何配置OPVN 阿里云关于OPVN的帮助文档
4 解决通过OPVN能ping通服务器,tcp连接不通的问题 解决能ping不能TCP访问的问题
5 Linux实例常用内核网络参数介绍与常见问题处理 阿里云官方关于参考4的解释

image-20220531230511343

直接看Docker Hub上的版本就好。这部分的说明和他GitHub上的描述好像是一模一样的。

https://hub.docker.com/r/kylemanna/openvpn/

服务端-裸机方案

网络拓扑

image-20220421221916618

开发者可以以ECS0为跳板,连接其提供的OPVN后直接用内网IP访问VPC内多个虚拟交换机下的资源。ECS0配置选择1C512M,x86架构即可,毕竟按量对外一条宽带最大也就200M。

配置过程

下面命令均在阿里云网页版终端中使用一个非root但有管理员权限的用户ecs-assist-user完成。该云服务器的操作系统是CentOS 7.9,安全组已对外放开1194端口的UDP访问。

软件安装

1
2
3
4
sudo yum update -y
sudo yum install epel-release -y
sudo yum update -y
sudo yum install -y openvpn wget

按照参考1的说法,easy-rsa 2 的文档更为齐全,因此暂定使用easy-rsa2。请注意以下这里OpenVPN的版本,并在后面涉及的路径处自行对应修改。

1
2
3
4
wget -O /tmp/easyrsa https://github.com/OpenVPN/easy-rsa-old/archive/2.3.3.tar.gz
tar zxvf /tmp/easyrsa
sudo mkdir /etc/openvpn/easy-rsa
sudo cp -rf /tmp/easy-rsa-old-2.3.3/easy-rsa/2.0/* /etc/openvpn/easy-rsa

生成证书

首先,新建一个用来放密钥的文件目录。

1
sudo mkdir /etc/openvpn/easy-rsa/keys

而后,修改生成密钥所需要的变量。当然,具体生成证书的时候,下面的值只是默认值,可以修改,但是显然这里写好了下面省事。

1
sudo vim /etc/openvpn/easy-rsa/vars
1
2
3
4
5
6
7
8
9
# /etc/openvpn/easy-rsa/vars 在对应的位置修改
export KEY_COUNTRY="CN"
export KEY_PROVINCE="JS"
export KEY_CITY="Nanjing"
export KEY_ORG="DI"
export KEY_EMAIL="ydjsirhere@gmail.com"
export KEY_CN=ydjsir.com.cn
export KEY_NAME="server"
export KEY_OU="HW"

而后,让这些环境变量在当前终端中生效。

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

接下来,让我们生成所需要的证书。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cd /etc/openvpn/easy-rsa
./clean-all # 初始化
./build-ca # 生成根证书

# 生成服务端证书
./build-key-server server # 生成给服务端的证书
./build-dh # 生成迪菲-赫尔曼密钥交换文件 Diffie–Hellman key exchange
sudo openvpn --genkey --secret /etc/openvpn/myvpn.tlsauth

# 生成客户端证书
./build-key client # 生成给客户端的证书,注意到这一步可以执行多次,生成多个不同配置的证书

# 分发配置
sudo cp key/dh2048.pem key/ca.crt key/server.crt key/server.key /etc/openvpn # 复制服务端密钥文件到对应位置
cp /etc/openvpn/easy-rsa/openssl-1.0.0.cnf /etc/openvpn/easy-rsa/openssl.cnf

服务端配置

配置文件

下面的配置文件为/etc/openvpn/server.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local $"要绑定的网卡的==内网==IP"
port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
dh dh2048.pem
key server.key
tls-crypt myvpn.tlsauth
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
duplicate-cn
keepalive 10 120
comp-lzo
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
log /data/logs/openvpn/openvpn.log
verb 3
push "route 172.26.128.0 255.255.240.0"
push "route 172.66.0.0 255.255.255.0"
配置详解

1、==OpenVPN监听的地址==。这里必须写系统内ifconfig显示的实际IP,不能写它对应的公网IP。OpenVPN通过这个绑定到具体网卡。当然,如果你启用了EIP可见模式,那么这里显然就是公网IP了。

2、监听的端口号

3、选择TCP/UDP模式。

4、选择TAP/TUN模式。注意TCP要搭配TAP食用。TAP工作在第二层而TUN工作在第三层,后者效率可能更高。具体区别和优缺点请自行检索。

5、CA证书路径

6、服务端证书路径

7、密钥分发文件路径、

8、服务端证书密钥路径

9、TLS加密文件,详见https://openvpn.net/vpn-server-resources/tls-control-channel-security-in-openvpn-access-server/

10、OpenVPN的内网网段和子网掩码

11、存储已分配的IP地址的文件位置

12、==允许为同一个客户端证书对应的多个连接分配多个IP地址==。如果希望复用一份配置文件的话,这是必须的,不然会出现IP地址冲突。

13、保活时间

14、传输时的压缩算法

15&16、使OpenVPN以低权限等级运行,以此增强安全性。

17&18、

19&20、状态与日志文件的路径

21、日志文件级别

23&24、==配置你需要让客户端能访问到的阿里云VPC网段==。这些网段首先肯定是你的服务器本身就能访问到的网段。这一部分请根据业务实际需要具体配置。

配置路由

配置转发规则

我们利用firewalld及其背后的iptables实现转发。在之前,我们只是完成了OpenVPN链路上本身的连接,要让数据在OpenVPN网卡和目标网卡中自由流动,我们需要配置路由与防火墙规则。第5行,将OpenVPN子网下的地址用静态NAT的方式映射到目标网卡上。这样一来,我们的OpenVPN就能联通其他内网了。注意这里只转发了IPv4,不过问题不大。

之所以使用firewalld而不是直接使用iptables,是因为前者可以持久化保存已配置

1
2
3
4
5
6
7
sudo firewall-cmd --permanent --add-masquerade # 开启IP masquerade
sudo firewall-cmd --permanent --add-service=openvpn # 放通服务
sudo firewall-cmd --add-port=1194/udp --permanent # 放行端口
sudo firewall-cmd --permanent --direct --passthrough ipv4 -t nat -A POSTROUTING -s 10.8.0.0/24 -j SNAT --to-source $"要绑定的网卡的==内网==IP"
sudo firewall-cmd --reload # 重载规则
sudo systemctl reload firewalld # 重载服务
sudo systemctl restart firewalld # 重启服务
修改系统内核参数
1
sudo vim /etc/sysctl.conf

修改完成后的效果如下面所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vm.swappiness = 0
kernel.sysrq = 1

net.ipv4.ip_forward = 1 # 允许系统IPv4转发,这是所有路由转发的必备前提
net.ipv4.neigh.default.gc_stale_time = 120

# see details in https://help.aliyun.com/knowledge_detail/39428.html
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2

# see details in https://help.aliyun.com/knowledge_detail/41334.html
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_slow_start_after_idle = 0

net.ipv4.tcp_timestamps = 0 # 禁用时间戳,不然会能ping不能tcp连接
net.ipv4.tcp_tw_recycle = 0 # 是否开启TCP连接中TIME-WAIT的sockets快速回收功能

最后两行,具体情况请参考5的问题5。这是通过搜索引擎得出参考4后额外找的资料。

不配置这个会导致能ping通阿里云内网其他设备,但是无法与他们建立任何TCP连接的情况。

输入以下命令,使得以上的参数生效。

1
sudo sysctl -p

启动服务端

1
2
systemctl enable openvpn@server
systemctl enable firewalld

重启服务器,接下来,OpenVPN将伴随系统一起启动。

服务端-Docker方案

网络拓扑

image-20220601000005204

配置过程

常规不看部分

按照官网一步步直接操作就行,没什么特别之处。开服务器防火墙这样的老生常谈更是没必要再提。顶多说自己改一下生成时CA的密码(这个项目要求你有密码的地方一个都不能少,必须输入,你以后还得能记住)。

特殊之处

路由配置

很显然你的服务器所在的VPC(即eth0所在的那个阿里云VPC)以及其他你还要连接的其他VPC是什么样就怎么填。为了防止IP地址冲突(毕竟直接填IP的时候又不能加上CIDR),YDJSIR的轻量云里的Docker的网段是172.18.0.0/16而不是默认的172.17.0.0/16。不过,即使加上了去Docker网络的路由,也是没办法直接访问其他Docker的。连用docker0网卡的地址访问宿主机都不行。不过很显然这并不是什么问题。

下面仅举一个例子。反正你要连几个子网就写几个这样的项。

1
push "route 172.17.0.0 255.255.192.0"
不要全局代理

原本生成出来的配置文件默认是让客户端全局流量走远端的。这显然是不符合我们的需要的。我们只需要连接到VPC内的资源时需要走远端。那么如何修改配置文件呢?

服务端把那些看起来都被注释掉的push相关配置全部删掉即可。下方举一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server 192.168.255.0 255.255.255.0
verb 3
key /etc/openvpn/pki/private/blog.ydjsir.com.cn.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/blog.ydjsir.com.cn.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key
key-direction 0
keepalive 10 60
persist-key
persist-tun
duplicate-cn

proto udp
# Rely on Docker to do port mapping, internally always 1194
port 1194
dev tun0
status /tmp/openvpn-status.log

user nobody
group nogroup


push "route 172.17.0.0 255.255.192.0"

YDJSIR不是很懂为什么那些看起来被注释掉的东西还是要删掉才能不起作用。不过既然解决了就行。客户端的看下面。

客户端配置

配置文件与密钥

服务端-裸机方案

下方的文件应该是跨平台的。客户端需要以下内容:

  • client.ovpn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
client 
dev tun
proto udp
remote $"要绑定的网卡的==公网==IP" 1194
remote-cert-tls server
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
tls-crypt myvpn.tlsauth
comp-lzo
verb 3
  • ca.crt 服务端前文已生成
  • client.crt 服务端前文已生成
  • client.key 服务端前文已生成
  • myvpn.tlsauth 服务端前文已生成

服务端-Docker方案

这个镜像会给你生成一个完整的client.ovpn,所有配置都打包进去了。

客户端配置删掉最后的redirect-gateway def1。然后就可以食用了。

Windows平台配置

1、安装OPVN官网提供的Community安装包。实测2.5.4可用。当然你喜欢的话,用那个闭源的更好看的客户端也行。

2、把配置文件复制进用户目录下的OpenVPN子目录下。效果如下图所示。

image-20220421000320402

3、启动OpenVPN并连接。

image-20220421000336494

Linux平台配置

Linux系统多样性太厉害了。试验过x86架构下,64位的Manjaro,Arch和Ubuntu。反正自行探索吧。

1、安装OPVN。这个直接从软件源装即可,Ubuntu用apt,CentOS用yum……在此不再赘述。版本尽量在2.4-2.5之间。

2、将配置文件及其依赖的文件复制到/etc/openvpn/client/config_v2

3、运行如下命令启动OPVN并连接。

1
sudo openvpn --daemon --cd /etc/openvpn/client/config_v2 --config client.ovpn --log-append /var/log/openvpn.log

Mac平台设置

去官网下那个闭源的客户端,导入配置文件即可。实测M1和Intel版本都可以用。

Android平台

自己想办法装APK得了。版本不记得了。

iOS / iPadOS平台

越狱了和安卓一样。没越狱,非国区的自己去APP Store下。国区的,考虑怎么走一个设备做中介吧。剩下的就不多说了。

用户管理

前文已经讲解了如何新建用户。直接复制那个操作就可以了。问题的关键只是如何吊销用户。

这里 已经有完整的讲解。不过社区版这种每次操作都要重新启动整个OPVN实例的做法确实挺头疼的。

附加内容

可以只代理特定IP吗?然后那个被访问的IP方便设置IP白名单到跳板机!

当然可以。你把那个IP当成是子网掩码全255的就行。

1
push "route 1.1.1.1 255.255.255.255"

可以同时连接多个OPVN吗?

你需要配置有多个OPVN可以使用的虚拟网卡。YDJSIR没有做此方面的研究。不过一个项目起一套得了吧。除非你要同时开多个方案。