请勿在不恰当的地方使用相关技术,否则后果自负

需求背景

众所周知,将SSH端口直接暴露在公网上是有一定风险的。尽管基于证书的验证是相当安全的,但是只要端口暴露了就总会有人试图往里面泼水。在阿里云上也是一样。很显然,只是需要SSH连接的话可以用网页端的免密登录来进行操作。然而,有的时候需要的并不仅仅是一个SSH端口。例如,YDJSIR在做数据集成的作业时就发现Hadoop,Spark等软件一旦启动会暴露非常多的端口,且默认状态下都没有鉴权,这显然是很危险的。特别是Spark,总要绑定一个网卡实际有的IP才行,但那是阿里云内网IP,没法直接访问(其实有一个workaround,先给当前ECS实例加一个弹性网卡,然后开一个弹性公网IP,以EIP网卡可见模式绑定即可)。然而,即使可以访问,这也是非常不安全的。为了解决上述问题,最直接的思路显然是想办法让运维/开发人员的设备与云服务器置身同一内网中。不过,阿里云提供的相关服务门槛相当高,不适合小微开发者。既然如此,那么就自己动手,丰衣足食吧。YDJSIR选择了开源的OpenVPN (UDP+TUN方案)来解决这样的需求。为什么选择它?他和传统L2TP,PPTP相比要现代、安全和高效,和Wireguard相比有更好的跨平台支持和文档(也许?)。那为什么YDJSIR不用第二代光线呢?YDJSIR知道非国际线路大概没事但是不想去触发alert。OpenVPN阿里云是有在国内站给官方教程的,那就是它了吧(参考资料附)毕竟YDJSIR从初中开始就在用OpenVPN了。

实现效果

image-20220421221916618

开发者可以以ECS0为跳板,连接其提供的OpenVPN后直接用内网IP访问VPC内多个虚拟交换机下的资源。

ECS0配置选择1C512M,x86架构即可,毕竟按量对外一条宽带最大也就200M。

参考资料

序号 标题 链接 备注
1 How To Set Up and Configure an OpenVPN Server on CentOS 7 https://www.digitalocean.com/community/tutorials/how-to-set-up-and-configure-an-openvpn-server-on-centos-7 本文主要参考的方案
2 【干货】ECS服务器OPENVPN搭建,方便管理所有内网服务器 https://chowdera.com/2021/05/20210528104005167Q.html 本文配置的重要参考
3 在CentOS系统的ECS实例中如何配置OpenVPN https://help.aliyun.com/document_detail/42521.html 阿里云关于OpenVPN的帮助文档
4 解决通过openvpn能ping通服务器,tcp连接不通的问题 https://www.icode9.com/content-4-148712.html 解决能ping不能TCP访问的问题
5 Linux实例常用内核网络参数介绍与常见问题处理 https://help.aliyun.com/document_detail/41334.html 阿里云官方关于参考4的解释

配置过程

下面命令均在阿里云网页版终端中使用一个非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将伴随系统一起启动。

客户端配置

配置文件与密钥

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

  • client.opvn
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 服务端前文已生成

Windows平台配置

1、安装OpenVPN提供的安装包。实测2.5.4可用。

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

image-20220421000320402

3、启动OpenVPN并连接。

image-20220421000336494

Linux平台配置

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

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

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

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

总结回顾

这本来可能是一次很常规的部署活动,却意外地引入了内核参数调整禁用TCP时间戳这一步。为了解决这个问题,YDJSIR研究了很久,一直以为是自己配置的问题。直到忍不住用搜索引擎去搜,才发现是OpenVPN之外的原因。计网是复杂的,对于TCP/UDP连接模式的拷问,是必要的。YDJSIR在计算机网络方面要学的还有很多。