1 使用WireGuard技术实现外部节点与公司内网服务互访
1.1 场景介绍
对于大企业而言,在过去几十年总部和分支机构的组网需求中,MPLS-VPN一直以稳定性作为大型组网的首选之作
近些年以软件定义网络的SD-WAN组网解决方案为企业带来了弹性和经济性的优势
而对于个人移动办公节点,还存在远程用户(出差或家里)、公司的分支机构、商业合作伙伴、供应商等公司和自己的公司内部网络之间建立可信的安全连接或是局域网连接的需求,一些大企业往往采用专用VPN硬件设备,通过建立在公网基础上建立VPN隧道访问内网服务
VPN技术的基本原理本质是隧道技术,隧道技术其实就是对传输的报文进行封装,利用公网建立专用的数据传输通道
常见的VPN技术包括L2TP、IPSec、SSL VPN,由于IPSec设计过于复杂,人们有设计出了OpenVPN
随着近些年科学上网的一些需求,运营商对基于TCP的VPN技术进行阻断,而采用UDP的VPN技术也不失为一种办法
相较于当下IPSec,OpenVPN等VPN技术,WireGuard被誉为下一代VPN技术,而且Linux的管理者Linus Torvalds已经将WireGuard合并至内核5.6中
WireGuard可以实现更少的代码(意味更加安全)、更快的部署以及更安全的加密算法
WireGuard的速度完全超越其他VPN协议,与其他VPN协议性能比较详见1.2.6章节
在家庭部署一台软路由,在公司内网部署一台轻量级的Linux虚拟机,通过WireGuard技术完全可以实现两个内网互访的应用,满足在公司访问家庭NAS或者在家访问公司服务等各类sao操作
更多VPN的场景对于高性能、高效部署等需求更加密切
1、使用Wireguard技术实现多个异地k8s集群之间访问通讯
2、一个家庭下多个物理地址环境下智能家居、视频监控等组网需求,实现内网穿透
实际上目前运营商对UDP流量几乎都进行了限速和限制,在实际使用过程中会出现莫名的卡顿、包括TLS应用的访问失败
本文通过家庭群晖内的一台虚拟机作为外部节点A、阿里云ECS公网作为中间VPN网关B、公司内部一台虚拟机作为公司内部网节点C通过Wireguard进行组网
理论上家庭内的各终端只要能与外部节点A正常通讯,即IP可达(针对三层以上的防火墙应用另行讨论),且将网关指向外部节点A
同样公司内网的各终端只要能与公司内网节点C正常通讯,即IP可达(针对三层以上的防火墙应用另行讨论),且将网关指向外部节点C
两边的内部网络就可以通过现有公网,所有流量通过中间VPN网关B(本实验环境下阿里云ECS主机)实现互访
整个环境全部采用真实环境模拟,实施步骤简单
1、首先在中间VPN网关(阿里云ECS主机上,实际上可以在任何安装了wireguard-tools软件包的机器上)生成个节点的私钥和公钥,私钥由各节点自己保存,公钥提供给与自身通讯的节点
2、在剩余各个节点上安装wireguard-tools软件包,并在各节点上使用wg-quick服务配置WireGuard服务器配置文件
3、启动wg-quick服务配置启动Wireguard服务,确保三个节点成功组建Wireguard网络
本环境是采用了外部节点A、公司内部网节点C分别与作为中间VPN网关的中间节点B形成了WireGuar网络,但这样中间节点会转发所有流量,且容易形成流量瓶颈
实际环境是可以full-mesh组网架构,具体可以看
1.2.4 Wireguard组网架构
4、最后我们通过一个故障的分析定位及排查去针对性说明两个网络实现互访的关键操作
注
本文对Ubuntu22.04、iptables、最新的RHEL9以及nftables的NAT转换等操作均有详细说明
1.2 WireGuard技术介绍
Wikipedia上关于wireguard的说明
WireGuard是由Jason A. Donenfeld开发的开放源代码VPN,由于它可以运行在内核空间,因此可以高速提供安全的网络
除了可以跨平台之外,WireGuard 的最大优点之一就是易于部署
总的来说,部署非常简单,基本就是包括两个步骤,一是创建密钥对,二是创建配置文件,最后启动即可
1.2.1 客户端和服务端
实际上参与WireGuard VPN的所有主机都是同级的
使用客户端
只是用来来描述建立连接的主机
使用服务器
来描述固定主机名或客户端连接的 IP 地址的主机,并可选通过这个服务器路由所有流量
如果所有的节点都是公网可达的,则不需要考虑中间VPN网关(中继服务器),只有当有对等节点没有公网IP即位于NAT后面时才需要考虑使用VPN网关的方式进行组网
在 WireGuard 里,客户端和服务端基本是平等的,差别只是谁主动连接谁而已
如果双方都没有公网IP地址即都在NAT后,则需要使用类似信令服务器STUN的技术
1.2.2 密钥对的使用
当发送端WireGuard收到需要发送的数据时,当目标IP在配置文件[Peer]
的AllowedIPs
中时,会使用PrivateKey
进行加密
当接收端WireGuard收到数据时,首先会使用发送端的PublicKey
进行解密
由于非对称加密原理,只有私钥对应的公钥才能正确对加密数据进行解密
1.2.3 WireGuard使用协议
WireGuard的代码虽然短小【关于WireGuard精美之处,各位可以自行搜索Linus Torvalds对于WireGuard评价原文】,但是支持的加密算法非常完善
ChaCha20 用于通过 Poly1305 进行身份验证,使用带有关联数据(AEAD)的 Authenticated Encryption,如 RFC7539 所述
Curve25519 用于 Elliptic-curve Diffie-Hellman(ECDH)密钥交换
用于哈希和密钥哈希的 BLAKE2s,如 RFC7693所述
用于哈希表键的 SipHash24
用于密钥派生的 HKDF,如 RFC5869所述
1.2.4 Wireguard组网架构
正如前所述,实际上参与WireGuard VPN 的所有主机都是同级的
组网架构存在中心化和去中心化的组网架构
我们一般采用一台中间节点作为网关,其他WireGuard节点与本中间节点进行连接
这种中心化的组网架构缺点相当明显:
1、当Peer越来越多时,VPN网关就会变成垂直扩展的瓶颈
2、通过VPN网关转发流量的成本很高
3、通过VPN网关转发流量会带来很高的延迟
还有一种全互联模式(full mesh),任意一个Peer节点和其他所有Peer都是直连,但是又来带一个新问题,就是那些没有公网IP地址的peer节点,这里就可以采用Wireguard 自身NAT穿透解决问题,不在本次话题讨论中
1.2.5 关于Allowed IPs地址列表
这点非常重要,对于理解Wireguard的工作原理非常重要
发送数据时,可以把Allowed IPs地址列表看成路由表,将需要访问的VPN对端的地址添加到Allowed IPs地址列表中。如果是全局流量,可以将Allowed IPs
设置为0.0.0.0/0
,通过策略路由完成对路由表的不破坏目的
接收数据时,可以把Allowed IPs地址列表看成ACL
特别注意,实际上要注意在隧道的各节点通过iptables或者nftables进行NAT转换
而[Interface]
中的Address
只是wireguard虚拟网卡的接口地址,需要与Allowed IPs
进行区别对待
1.2.6 与其他VPN协议性能比较
WireGuard 与其他 VPN 协议的性能测试对比
测试环境【使用iperf3软件进行测试】
- ntel Core i7-3820QM and Intel Core i7-5200U
- Intel 82579LM and Intel I218LM gigabit ethernet cards
- Linux 4.6.1
- WireGuard configuration: 256-bit ChaCha20 with Poly1305 for MAC
- IPsec configuration 1: 256-bit ChaCha20 with Poly1305 for MAC
- IPsec configuration 2: AES-256-GCM-128 (with AES-NI)
- OpenVPN configuration: equivalently secure cipher suite of 256-bit AES with HMAC-SHA2-256, UDP mode
iperf3
was used and the results were averaged over 30 minutes
https://www.wireguard.com/performance/
1.2.7 WireGuard报文格式
WireGuard使用了UDP对需要进入VPN流量的报文进行了二次封装,被封装的数据通过TCP去确保数据的有效传输
例如中间VPN节点(VPN地址192.168.200.254)ping访问外部网络节点(VPN地址192.168.200.2)
18和19号的报文,分别是阿里云ESC的私网地址发往家庭公网出口地址(114.84.236.199,电信出口IP)以及对方发回的Wireguard报文
1.3 试验组网环境
1.3.1 组网示意图
本次实验组网环境采用中间网关的方式,即将阿里云ECS主机作为中间网关,家庭网络节点(后续统称外部网络节点)和公司内网节点的通讯都通过该节点进行数据转发
注意
因为需要中间网关的转发,因此必须开启该节点Linux的内核转发功能
因为各节点实际上都作为网关进行转发,因此实验环境下的各节点都需要开启Linux的内核转发功能
1
2 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
1.3.2 各节点说明
外部网络节点我们这里采用位于家庭网络的群晖上的一台虚拟机作为试验环境,硬件配置为2核(vCPU) 内存为2GiB
操作系统为Ubuntu 22.04.3 LTS
1 | root@synology:~# cat /etc/os-release |
中间公网节点为阿里云一台云服务器ECS,硬件配置为1核(vCPU) 内存为1GiB,操作系统为Rocky Linux9.1 64位
公司内网的节点是运行在公司虚拟化平台vSphere8上的一台Linux虚拟机,硬件配置为8核(vCPU) 内存为8GiB,操作系统Red Hat Enterprise Linux release 9.0 (Plow)
外部网络节点通过内核Wireguard客户端与位于公网阿里云ECS虚拟机建立VPN连接
公司内网的节点通过内核Wireguard与位于公网阿里云ECS虚拟机建立VPN连接
外部网络节点和公司内网节点都通过Iptables或者Nftables通过开启NAT转换
最终实现位于家庭的移动pad或者PC可以访问位于公司内网的相关服务,以及位于公司内网环境下访问家庭NAS服务
试验环境成功的几个重要注意点
1、需要访问公司内网服务的终端,例如家庭的移动pad或者PC需要将网关指向实验环境下群晖下Ubuntu虚拟机
2、Ubuntu虚拟机节点、中间VPN网关(阿里云ECS节点)、公司内网节点都需要开启Linux内核转发功能
3、Ubuntu虚拟机节点需要通过iptables创建SNAT转换规则
4、公司内网节点因为采用RHEL9版本,因此通过nftables创建NAT转换规则
各节点IP地址
1、三个节点之间私网地址我们这里配置为192.168.200.x/24
,实际上可以选择任何与其他当前网络以及公司网络节点不冲突的网段接口
VPN组网wireguard虚拟网卡接口地址 | 内网地址 | |
---|---|---|
中间公网节点 | 192.168.200.254 | / |
公司内网节点 | 192.168.200.1 | 192.168.3.36 |
外部网络节点 | 192.168.200.2 | 172.18.3.16 |
外部网络终端测试节点 | 172.18.3.50 | |
公司内网测试用服务节点 | 172.17.50.1 |
2、外部网络节点,通过电信路由器实现上网,公网地址为动态分配
3、中间公网节点阿里云ECS公网地址是47.100.39.165
4、公司内网节点的内网地址是172.18.3.16
,通过企业联通链路实现互联网访问
可以看出,实现外部节点对公司内网服务的访问[公司内网服务的地址为172.17.50.1],需要满足以下条件
1、外部移动节点和内网节点能够正常访问互联网,与中间节点实现正常的IP通讯
2、中间节点需要一个固定的公网地址,满足其他两个节点主动连接
2 实施步骤
2.1 中间公网节点操作
RHEL9内核为5.14.0
,已经内置WireGuard技术
2.1.1 安装wireguard-tools软件包
查看wg
命令是由wireguard-tools
软件包提供,采用dnf软件包安装
1 | [root@ethan ~]# dnf provides wg |
验证
1 | [root@ethan ~]# wg --version |
2.1.2 配置各节点所需要的私钥及对应公钥
发送端通过私钥加密,接收端通过对应的公钥解密,因此我们创建两对密钥,分别用于外部网络节点与中间公网节点,以及公司内网节点与中间公网节点的通讯加密用途
节点名称 | 私钥名称 | 公钥名称 |
---|---|---|
外部网络节点 | ubuntu.key.key | ubuntu.key.key.pub |
中间公网节点 | server1.key | server1.key.pub |
公司内网节点 | client1.key | client1.key.pub |
创建中间公网节点的私钥
1 | [root@ethan ~]# wg genkey > server1.key |
创建中间公网节点的公钥
1 | [root@ethan ~]# wg pubkey < server1.key > server1.key.pub |
注意
1、后续Wireguard是需要私钥和对应公钥的内容,而不是文件本身,实际上可以删除
2、但仍建议保留文件,并对上述文件进行权限设置,保证数据安全性
设置上述文件权限
1 | [root@ethan ~]# chmod 600 server1.key server1.key.pub |
其余两个节点的私钥和公钥操作一致,这里省略
验证
1 | [root@ethan wireguard]# ll |
2.1.3 使用wg-quick服务配置WireGuard服务器配置文件
使用wg-quick服务可以将wireguard作为systemd服务进行统一管理
注意
/etc/wireguard
目录下创建*.conf
文件的文件名将作为后续systemd服务管理名
进入/etc/wireguard
目录下创建wireguard0.conf
文件
1 | cd /etc/wireguard |
具体配置文件参考如下
1 | [Interface] |
配置参数说明
1、Address是中间公网VPN节点的Wireguard虚拟网卡的接口地址
2、PrivateKey是中间公网VPN节点的私钥
实际操作过程中注意替换其中内容
3、ListenPort是中间公网VPN节点的监听端口,默认是51820
4、PersistentKeepalive是保持keepalive心跳时间间隔
5、特别注意就是这里有两个[Peer]
字段
解释如下:从中间公网节点来看,它是与外部网络节点和公司内网节点两个节点进行VPN互联,因此需要配置两个[Peer]
字段
注意
1、当家庭网络访问公司内网时候,对于外部网络节点而言属于发送数据方向,可以把Allowed IPs地址列表看成路由表
2、因此将需要的访问公司内网的网段添加到
AllowedIPs
列表中
WireGuard会自动添加两条路由条目【详见第二条和第三条】
1 | [root@ethan wireguard]# ip route |
分别是172.17.50.0/24 dev wireguard0 scope link
和172.18.3.0/24 dev wireguard0 scope link
2.1.4 启动wg-quick服务
启用wg-quick服务
1 | systemctl enable wg-quick@wireguard0 --now |
注意
wg-quick@后面的名称需要和
/etc/wireguard
中配置的.conf
文件名一致
验证
1 | [root@ethan wireguard]# systemctl status wg-quick@wireguard0.service |
实际上/usr/bin/wg-quick
本质是一个shell文件
1 | !/usr/bin/bash |
2.1.5 防火墙设置
因为中间公网地址需要作为中间网关,接收其他两个节点的UDP连接
因此需要对配置文件中ListenPort
定义的端口进行放行,这里ListenPort
我们定义为默认的51820
对于阿里云ECS服务器,需要在控制台的安全组进行设置具体如下
如果操作系统开启了防火墙,需要通过firewall-cmd
添加监听端口
1 | [root@ethan wireguard]# firewall-cmd --add-port=51820/udp --permanent |
注意
1、需要开启firewalld的区内转发,RHEL8需要手动开启,RHEL9默认自动开启
2、需要确认物理网卡和Wireguard虚拟网卡需要在同一个firewall区域内
验证eth0
和wireguard0
属于同一个区域内,例如public
1 | [root@ethan wireguard]# firewall-cmd --get-active-zones |
如果wireguard0
虚拟网卡不属于eth0
同一个区域内,进行区域变更操作
1 | [root@ethan wireguard]# firewall-cmd --zone=public --change-interface=wireguard0 --permanent |
2.1.5 重要操作【开启内核转发】
开启Linux内核转发
1 | echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf |
注
中间VPN节点并不需要开启SNAT转换服务,源地址不需要改变
2.2 外部网络节点操作
由于本人没有专用软路由设备,因此从试验环境考虑,直接采用了家庭网络内群晖NAS的虚拟机作为外部网络节点
但从性能出发,在实际操作过程中可以考虑部署一台例如爱快或者ROS的专用软路由作为外部网络节点
2.2.1 群晖安装Ubuntu虚拟机
套件中心安装Virtual Machine Manager
安装完成后打开Virtual Machine Manager
剩下很多操作就是按向导一步一步操作,具体安装步骤省略
其中ens3
接口地址是192.168.3.36
1 | root@synology:~# ip ad |
确保能够正常访问中间节点阿里云的ECS公网地址
1 | root@synology:~# ping -c 3 47.100.39.165 |
2.2.2 Ubuntu虚拟机安装wireguard软件
通过apt命令安装
1 | apt install wireguard |
验证
1 | root@synology:~# wg --version |
2.2.3 使用wg-quick服务配置WireGuard服务器配置文件
由于私钥和公钥已经在中间节点上统一进行了配置
因此直接配置wireguard的配置文件
使用wg-quick服务可以将wireguard作为systemd服务进行统一管理
注意
/etc/wireguard
目录下创建*.conf
文件的文件名将作为后续systemd服务管理名
进入/etc/wireguard
目录下创建wireguard0.conf
文件
1 | cd /etc/wireguard |
具体配置文件参考如下
1 | [Interface] |
配置参数说明
1、Address是外部网络节点的Wireguard虚拟网卡的接口地址
2、PrivateKey是外部网络节点的私钥
3、PublicKey是中间VPN网关的公钥
4、Endpoint是连接至中间VPN网关的公网地址和端口
5、PersistentKeepalive是保持keepalive心跳时间间隔
注意
1、当家庭网络访问公司内网时候,对于外部网络节点而言属于发送数据方向,可以把Allowed IPs地址列表看成路由表
2、因此将需要访问公司内网的网段添加到
AllowedIPs
列表中3、因此作为外部网络节点的Ubuntu虚拟机作为请求发起方,Peer中
AllowedIPs
允许172.18.3.0/24, 172.17.50.0/24流量包会进入VPN隧道流入公网服务器。
2.2.4 启动wg-quick服务
启用wg-quick服务
1 | systemctl enable wg-quick@wireguard0 --now |
注意
wg-quick@后面的名称需要和
/etc/wireguard
中配置的.conf
文件名一致
验证,这里采用wg show
命令
1 | root@synology:~# wg show |
可以看出此时外部网络节点已经和中间公网节点建立了VPN连接
2.3 公司内网节点操作
2.3.1 安装wireguard-tools软件包
同样对于RHEL9系统,直接采用dnf软件包安装
查看wg
命令是由wireguard-tools
软件包提供,采用dnf软件包安装
1 | [root@RHEL9 ~]# dnf install -y wireguard-tools |
验证
1 | [root@RHEL9 ~]# wg --version |
由于私钥和公钥已经在中间节点上统一进行了配置
因此直接配置wireguard的配置文件
2.3.2 使用wg-quick服务配置WireGuard服务器配置文件
使用wg-quick服务可以将wireguard作为systemd服务进行统一管理
1 | [Interface] |
配置参数说明
1、Address是公司内网网络节点的Wireguard虚拟网卡的接口地址
2、PrivateKey是公司外部网络节点的私钥
3、PublicKey是中间VPN网关的公钥
4、Endpoint是连接至中间VPN网关的公网地址和端口
5、PersistentKeepalive是保持keepalive心跳时间间隔
2.3.3 启用wg-quick服务
1 | systemctl enable wg-quick@wireguard0 --now |
注意
wg-quick@后面的名称需要和
/etc/wireguard
中配置的.conf
文件名一致
验证,这里采用wg show
命令
1 | [root@RHEL9 ~]# wg show |
至此,外部网络节点和公司内网节点均与中间公网节点建立了VPN连接
再在中间公网节进行验证
1 | [root@ethan wireguard]# wg show |
可以看出,两个节点均已成功完成连接
至此,WireGuard的部署工作全部完成
3 外部网络与公司内网服务互访关键操作
最后我们通过一个故障的分析定位及排查去针对性说明两个网络实现互访的关键操作
上述操作步骤完成后,实际上外部网络节点和公司内网节点可以实现IP可达
分别在两节点上使用ping命令进行验证
外部网络节点验证
1 | root@synology:~# ping -c 3 192.168.200.1 |
公司内网节点验证
1 | [root@RHEL9 ~]# ping -c 3 192.168.200.2 |
同时由于我在公司内网节点上部署了本次环境的使用nginx服务
外部网络节点验证也可以直接访问该nginx节点
1 | root@synology:~# curl 172.18.3.16 |
注
1、如果启用了防火墙,需要开启80/tcp端口
2、由于172.18.3.16属于公司内网节点本机的ens33的接口地址,因此不需要开启内核转发以及其他NAT转换规则
3.1 问题:外部网络节点无法访问公司内网其他服务器
首先公司内网节点可以访问某一服务,服务测试地址为172.17.50.1,服务为http
1 | [root@RHEL9 ~]# curl 172.17.50.1 |
而外部网络节点无法正常访问,使用curl
命令无法正常返回
3.1.1 故障定位
首先判断从外部网络访问172.17.50.1
的网络包是否发送至中间公网节点
1 | [root@ethan wireguard]# tcpdump -i any host 172.17.50.1 -nn |
从tcpdump结果看出,中间公网节点已经从wireguard0
虚拟网卡收到并发出来自源地址是192.168.200.2
,目的地址是172.17.50.1
的网络包请求
然后判断公司内部网络节点是否将来自192.168.200.2
的网络包从ens33网卡发出
1 | [root@RHEL9 ~]# tcpdump -i ens33 host 172.17.50.1 -nn -v |
可以看出当公司内网网络节点收到源地址是192.168.200.2
的网络包时,的确从ens33网卡发出,但是由于网络包的源地址为Wireguard私网地址192.168.200.2/32
,导致目的节点172.17.50.1在收到网络包时,由于路由表没有相关条目,导致无法回包
3.1.2 故障处理
因此需要在公司内网节点开启内核转发及SNAT转换
配置内核转发
1 | echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf |
配置DNAT转换
1 | iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE |
实际上,如果采用nftables命令,效果一样
1 | nft add rule ip nat POSTROUTING oifname "ens33" counter masquerade |
此时,再在外部网络节点进行验证,故障排除