个人VPN的搭建(Wireguard)-内网穿透

Wireguard是一个市面上比较好用的VPN工具,也常常被用来做内网穿透。Wireguard支持利用公私钥对流量进行加密(支持加密是很重要的,不然数据传输存在有很大的不安全性),Wireguard可能已经被视为业内最安全、最易于使用和最简单的 VPN 解决方案。

Wireguard是一个市面上比较好用的VPN工具,也常常被用来做内网穿透。Wireguard支持利用公私钥对流量进行加密(支持加密是很重要的,不然数据传输存在有很大的不安全性),Wireguard可能已经被视为业内最安全、最易于使用和最简单的 VPN 解决方案。Wireguard的官网如下:Wireguard

首先我们需要知道的是,想要搭建一个VPN服务器,需要有一个公网IP,比如说拥有一个公网IP的云服务器也行。

实现原理是:Wireguard会在本地新增一个虚拟的网卡设备接口,当访问符合Wireguard的网段的要求的请求时,这个网卡设备接口会将流量加密后,传输给Wireguard服务端。Wireguard根据访问的目标虚拟IP,找到对应的真实机器,向目标机器发起请求。Wireguard服务器为两台机器之间建立连接,建立起了一条安全的隧道。

Wireguard的网络通信模型如下,对于所有的网络流量都将会通过Wireguard服务器进行转发,无法实现P2P的功能。

image-erdj.png

Wireguard的整体数据传输加密方式实现,和SSH的免密登录的实现方式类似。对于每个接入Wireguard的服务端,和Wireguard的客户端,都会拥有自己的公钥和私钥,在生成Wireguard配置文件时,则需要首先生成各自的公钥和私钥。对于服务端,持有所有的客户端的公钥,对于每个客户端的流量进行身份验证;对于客户端,持有服务端,则持有服务端的公钥,对服务端发送的流量进行身份验证。

我们下面的Wireguard的搭建过程,都是在云服务器上进行的搭建,因为云服务器有带公网IP,但是也会存在有问题,因为云服务器的带宽较小,流量传输的带宽上限也会在服务器的公网带宽。

Wireguard有很多种部署方式,包括原生的二进制、Docker、K8S等多种部署方式。我们下面主要介绍以Docker/K8S的方式进行Wireguard的部署的几种方式:

1.基于官方原生镜像搭建Wireguard服务

基于官方原生镜像搭建Wireguard时,需要自己准备好Wireguard的配置文件wg0.conf文件。对于客户端和服务端,都需要准备好对应的Wireguard配置文件。

1.1 Wireguard公钥和私钥的生成

首先需要在一台机器上安装Wireguard工具,用于进行各个客户端和服务端的公钥、私钥的生成。可以使用如下的命令去安装Wireguard:

# ubuntu
apt install resolvconf wireguard-tools
# centos
yum install resolvconf wireguard-tools

接着,我们需要使用如下的命令,生成Wireguard的私钥文件和私钥文件。

# 生成服务端的私钥
wg genkey > server.privatekey

# 利用服务端的私钥去生成服务端的公钥
wg pubkey < server.privatekey > server.publickey

如果想要生成一个客户端的公钥私钥,和生成一个服务端的公钥私钥的流程完全一致。

wg genkey > client1.privatekey
wg pubkey < client1.privatekey > client1.publickey

生成服务端和客户端都公私钥文件之后,下面我们来生成服务端和客户端的wg0配置文件。

服务端的Wireguard配置文件编写

在Interface处配置Wireguard的服务端的配置信息:

  • PrivateKey配置在上面生成的server.privatekey的当中的内容。私钥禁止泄露,它泄露代表了整个VPN下的所有的机器都已经泄露,非常危险!!!
  • ListenPort配置Wireguard服务端启动的端口号(注意是UDP协议,默认是51820端口,需要在防火墙放开51820的UDP端口)。
  • Address指定Wireguard服务端将会分配的IP地址。

在Peer处配置配置各个客户端的配置信息,如果有多个客户端,那么就需要配置多个Peer:

  • PublicKey处配置Wireguard客户端的公钥,也就是上面的client1.privatekey文件当中的内容。
  • AllowedIPs处用于配置,Wireguard服务端对于这个Peer客户端来说,需要处理哪些网段的数据包。
# Server
[Interface]
PrivateKey = {服务端私钥}
Address = 10.0.8.1/24
ListenPort = 51820
PreUp = 
PostUp = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0 -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT;
PreDown = 
PostDown = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0 -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; 


# Client1
[Peer]
PublicKey = {客户端公钥}
AllowedIPs = 10.0.8.2/32

# Client2
[Peer]
PublicKey = {客户端公钥}
AllowedIPs = 10.0.8.3/32

我们来解释一下上面的配置含义:Server端在50820端口UDP启动,在Wireguard虚拟网络当中的IP地址是10.0.8.1/24,有两个客户端10.0.8.2/3210.0.8.3/32,比如说10.0.8.2/32这个客户端在浏览器输入了10.0.8.3这个IP地址时,请求将会到达Wireguard服务端,在服务端进行流量的转发,由服务端将流量转发到10.0.8.3这个虚拟IP的Wireguard客户端。

还有一部分也是很重要的,PostUp是Wireguard服务启动之后会执行的回调钩子,PostDown是Wireguard服务在关闭之后会执行的钩子,修改本机的iptables流量转发的规则:

PreUp = 
PostUp = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0 -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT;
PreDown = 
PostDown = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0 -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; 

我们来一条一条分析PostUp中的规则:

  • iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0 -j MASQUERADE规则的含义:对于服务端出口的流量,如果IP网段是10.0.8.0/24的话,把它交给eth0网卡进行流出,并通过-j MASQUERADE将源IP隐藏,修改成为eth0网卡的IP,实现的其实就是路由器的NAT地址转换功能。
  • iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT规则的含义:对于入流量访问UDP协议的51820端口直接放行(-j ACCEPT),其实就是放开入流量的UDP协议51820端口的防火墙规则,保证外网可以访问服务器的51820端口。
  • iptables -A FORWARD -i wg0 -j ACCEPT规则的含义:对于从wg0虚拟网卡接口进入进行转发的流量,一律放行。
  • iptables -A FORWARD -o wg0 -j ACCEPT规则的含义:对于从wg0虚拟网卡接口转发出去的流量,一律放行。

PostDown中的规则,就是把启动时新增的iptables规则进行删除掉,避免因为Wireguard的启停导致iptables规则产生突然。对于和PostUp在命令上的区别,就是将-A改为-D

客户端的Wireguard配置文件的编写

客户端的配置信息,和服务端的配置方式类似。

在Interface处填写客户端虚拟网卡接口的配置信息:

  • PrivateKey填写客户端私钥,不要对外泄露
  • Address填写在VPN网络中的当前客户端的IP地址。

Peer处则需要填写当前客户端需要怎么和服务端进行交互:

  • PublicKey处填写服务端的公钥。
  • AllowedIPs处填写对于客户端中的Wireguard虚拟网卡,需要处理哪些网段的数据包。
  • PersistentKeepalive用于填写,客户端和服务端之间的心跳包的时间。
  • Endpoint处填写服务端的目标地址,可以是IP+端口号的格式,也可以是域名+端口号的格式。
[Interface]
PrivateKey = {客户端私钥}
Address = 10.0.8.2/24
DNS = 114.114.114.114


[Peer]
PublicKey = {服务端公钥}
AllowedIPs = 10.0.8.0/24
PersistentKeepalive = 25
Endpoint = xxx.yyy.com:51820

1.2 基于Docker部署Wireguard服务端

如果是Docker进行的部署,那么可以使用如下的命令,去启动一个Wireguard服务的服务端。

docker run -d \
  --name=wireguard \
  -v ~/wireguard/wg0.conf:/config/wg0.conf \
  -p 51820:51820/udp \
  --cap-add=NET_ADMIN \
  --cap-add=SYS_MODULE \
  --sysctl="net.ipv4.conf.all.src_valid_mark=1" \
  --sysctl="net.ipv4.ip_forward=1" \
  --restart unless-stopped \
  linuxserver/wireguard

我们通过-v ~/wireguard/wg0.conf:/config/wg0.conf参数,将本机下的~/wireguard/wg0.conf文件挂载到Docker容器的/config/wg0.conf下。

1.3 基于K8S部署Wireguard服务端

只需要基于如下的K8S资源清单即可部署Wireguard服务:

apiVersion: v1
kind: Namespace
metadata:
  name: wireguard

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wireguard-pvc
  namespace: wireguard  # 在 wireguard 命名空间中创建 PVC
spec:
  accessModes:
    - ReadWriteMany  # NFS 允许多个节点同时访问
  resources:
    requests:
      storage: 10Gi  # 需要的存储大小
  storageClassName: nfs-storage  # 使用 nfs-storage storage class

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wireguard
  namespace: wireguard  # 在 wireguard 命名空间中创建 Deployment
  labels:
    app: wireguard
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wireguard
  template:
    metadata:
      labels:
        app: wireguard
    spec:
      initContainers:
        - name: set-systemctl
          image: harbor.wanna1314y.top/library/busybox
          command: 
            - "sh"
            - "-c" 
            - | 
              sysctl -w net.ipv4.conf.all.src_valid_mark=1 
              sysctl -w net.ipv4.ip_forward=1
          securityContext:
            privileged: true
          imagePullPolicy: Always
      containers:
        - name: wireguard
          image: harbor.wanna1314y.top/linuxserver/wireguard:latest
          volumeMounts:
            - name: wireguard-config
              mountPath: /config
          ports:
            - containerPort: 51820
              protocol: UDP
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"]
          resources: {}
      volumes:
        - name: wireguard-config
          persistentVolumeClaim:
            claimName: wireguard-pvc  # 使用 PVC
      restartPolicy: Always

---
apiVersion: v1
kind: Service
metadata:
  name: wireguard-service
  namespace: wireguard  # 在 wireguard 命名空间中创建 Service
spec:
  selector:
    app: wireguard
  ports:
    - protocol: UDP
      port: 51820
      targetPort: 51820
      nodePort: 31820  # 指定 NodePort 的端口号
      name: wireguard-udp
  type: NodePort  # 使用 NodePort 类型

2. Wireguard客户端接入

2.1 Linux/Macos客户端通过命令行的方式接入

客户端需要安装openresolv和wireguard-tools工具。

# ubuntu/debain
apt update
apt install resolvconf wireguard-tools

# centos
yum install resolvconf wireguard-tools

# macos
brew install wireguard-tools

接着,需要从Wireguard后台Web页面下载客户端的Wireguard配置文件,接着执行wg-quick脚本,需要指定wg的配置文件。

# 启动wireguard客户端, 指定配置文件
wg-quick up ./wg0.conf

# 停止wireguar客户端, 指定配置文件
wg-quick down ./wg0.conf

如果出现下面的情况,说明这个文件的权限太高了,我们可以降低一下权限,不让其他人可以查看,可以执行sudo chmod 700 node.conf进行降低权限。

Warning: `/root/wg/node.conf' is world accessible

接着通过ifconfig查看机器的网卡可以发现,新增了一个虚拟的网卡设备。

node: flags=209<UP,POINTOPOINT,RUNNING,NOARP>  mtu 1420
        inet 10.0.8.3  netmask 255.255.255.0  destination 10.0.8.3
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 1000  (UNSPEC)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 27  bytes 3996 (3.9 KB)
        TX errors 0  dropped 1 overruns 0  carrier 0  collisions 0

Linux客户端实现Wireguard自启动,比如目前我有一个配置文件wg0.conf(需要更换成为自己的配置文件名),那么我可以使用如下的命令去实现Wireguard客户端的自启动。

# 拷贝Wireguard客户端配置文件
sudo cp ./wg0.conf /etc/wireguard/
# 确保文件有权限
sudo chmod 600 /etc/wireguard/wg0.conf

# 启动Wireguard自启动的service
sudo systemctl enable wg-quick@wg0.service

# 使用systemctl命令查看service是否已经开启自启动(看看有没有enabled属性)
sudo systemctl status wg-quick@wg0.service

验证配置开启自启动是否生效:

# 重启服务器
reboot

# 查看网卡端口是否包含我们新创建的Wireguard的网卡接口
ifconfig

2.2 Mac/iphone客户端通过官方客户端接入

Wireguard的客户端下载链接如下:Wireguard-Install

iphone接入需要一个美区账号,在AppleStore才能下载,国区账号,好像没有什么别的好办法。可以去淘宝买一个美区账号,登录AppleStore,下载然后退出账号!

需要注意的是,千万别使用在手机的设置当中登录,因为你登录的是别人的号,万一别人改密码了,你的手机也就锁定了,没办法进去了,手机就废了!

共享的美区账号链接参考:美区账号

下载Wireguard APP之后,选择导入配置文件即可,导入我们上面生成的wg0配置文件。在后面我们接入图形化的Wireguard之后,iphone客户端可以通过扫码的方式导入wg0配置文件。

3. 基于wg-easy搭建Wireguard

Wg-easy在基础的Wireguard的基础上,新增了简单的图形化管理页面,可以方便我们快速进行管理Wireguard的客户端。

3.1基于Docker部署wg-easy

Wireguard服务器的搭建

可以参考Github:

我们使用docker pull命令去进行拉取镜像:

docker pull weejewel/wg-easy

接着,我们使用如下的命令去启动wireguard容器。

docker run -d \
  --name=wg-easy \
  -e WG_HOST=1.1.1.1 \
  -e PASSWORD=passwd123  \
  -e WG_DEFAULT_ADDRESS=10.0.8.x \
  -e WG_DEFAULT_DNS=114.114.114.114 \
  -e WG_ALLOWED_IPS=10.0.8.0/24 \
  -e WG_PERSISTENT_KEEPALIVE=25 \
  -v ~/.wg-easy:/etc/wireguard \
  -p 51820:51820/udp \
  -p 51821:51821/tcp \
  --cap-add=NET_ADMIN \
  --cap-add=SYS_MODULE \
  --sysctl="net.ipv4.conf.all.src_valid_mark=1" \
  --sysctl="net.ipv4.ip_forward=1" \
  --restart unless-stopped \
  weejewel/wg-easy

这里有几项是我们必须要进行修改的:

  • WG_HOST指定Wireguard服务器的地址,可以是IP和域名。
  • PASSWORD指定Wireguard服务器的后台登录密码。
  • -p 51820:51820/udp指定将Wireguard服务器启动的UDP协议的51820端口,映射成为宿主机的51820端口。
  • -p 51821:51821/tcp指定将Wireguard服务器WebUI的TCP协议的51821端口,映射成为宿主机的51821端口,后续我们可以通过Wireguard服务器IP加上51821端口进行访问Wireguard服务。

启动完Docker容器之后,我们访问服务器的公网IP的51821端口,即可进入到Wireguard的网页管理页面,并输入密码进入到Wireguard后台。

image-xu4q.png

进入到Wireguard后台之后,可以点击New按钮,新增一个Wireguard客户端。

image-e4uo.png

Wireguard客户端接入

新建客户端之后,可以点击客户端右侧的,二维码和下载按钮,二维码是给手机客户端准备的,下载按钮是给电脑端/Linux等系统准备的。

image-pqwx.png

当成功连接上时,客户端上会有一个小红点,代表这个客户端已经在线。

image-utdg.png

页面上这里展示的10.0.8.3就是这个客户端的IP,后续我们可以通过10.0.8.3这个IP去访问我们的NAS。

我们同时将NAS和Mac接入Wireguard客户端之后,我们就可以在Mac设备上通过VPN的虚拟IP去访问NAS的服务了。

3.2通过K8S部署wg-easy

可以通过如下的K8S资源清单,去创建Wireguard的Deployment和Service。

apiVersion: v1
kind: Namespace
metadata:
  name: wireguard
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wg-easy-pvc
  namespace: wireguard  # 在 wireguard 命名空间中创建 PVC
spec:
  accessModes:
    - ReadWriteMany  # NFS 允许多个节点同时访问
  resources:
    requests:
      storage: 10Gi  # 需要的存储大小
  storageClassName: k8s-nfs-storage  # 使用 nfs-storage storage class
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wg-easy
  namespace: wireguard  # 在 wireguard 命名空间中创建 Deployment
  labels:
    app: wg-easy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wg-easy
  template:
    metadata:
      labels:
        app: wg-easy
    spec:
      initContainers:
        - name: set-systemctl
          image: harbor.wanna1314y.top/library/busybox
          command: 
            - "sh"
            - "-c" 
            - | 
              sysctl -w net.ipv4.conf.all.src_valid_mark=1 
              sysctl -w net.ipv4.ip_forward=1
          securityContext:
            privileged: true
          imagePullPolicy: Always
      containers:
        - name: wg-easy
          image: weejewel/wg-easy  # harbor.wanna1314y.top/linuxserver/wg-easy
          env:
            - name: WG_HOST
              value: "xxx.yyy.com"  # 外部 IP 地址(宿主机 IP 或外网 IP)
            - name: PORT
              value: "51821"
            - name: PASSWORD
              value: "passwd123"  # 密码
            - name: WG_PORT
              value: "31820"
            - name: WG_CONFIG_PORT
              value: "31824"
            - name: WG_DEFAULT_ADDRESS
              value: "10.0.8.x"  # 默认的 WireGuard 地址
            - name: WG_DEFAULT_DNS
              value: "114.114.114.114"  # 默认 DNS
            - name: WG_ALLOWED_IPS
              value: "10.0.8.0/24"  # 允许的 IP 范围
            - name: WG_PERSISTENT_KEEPALIVE
              value: "25"  # Keepalive 时间
            - name: UI_CHART_TYPE
              value: "1"
          volumeMounts:
            - name: wg-easy-config
              mountPath: /etc/wireguard
          ports:
            - containerPort: 51820
              protocol: UDP
            - containerPort: 51821
              protocol: TCP
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"]
          resources: {}
      volumes:
        - name: wg-easy-config
          persistentVolumeClaim:
            claimName: wg-easy-pvc  # 使用 PVC
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: wg-easy-service
  namespace: wireguard  # 在 wireguard 命名空间中创建 Service
spec:
  selector:
    app: wg-easy
  ports:
    - protocol: UDP
      port: 51820
      targetPort: 51820
      nodePort: 31820  # 指定 NodePort 的端口号
      name: wireguard-udp
    - protocol: TCP
      port: 51821
      targetPort: 51821
      nodePort: 31821  # 指定 NodePort 的端口号
      name: wireguard-webui
  type: NodePort  # 使用 NodePort 类型

需要注意的是:

  • WG_HOST环境变量的配置,在K8S当中配置成为IP并不合适,因为K8S当中不一定会将这个Pod调度哪台宿主机器,适合配置成为域名的方式
  • WG_PORT环境变量的配置,要和Service的wireguard-udpnodePort端口一致,因为WG_PORT的配置实际上是用于wg的客户端配置文件的生成,并不会影响Wireguard在哪个端口启动,Wireguard默认在51820端口暴露,好像wg-easy没有提供修改Wireguard的默认51820的配置。
  • PORT环境变量配置的是Pod当中Wireguard的WebUI页面的启动端口,默认是51821,没必要修改,通过Service映射成为31821的端口。
  • 最终生成客户端的配置文件,连接Wireguard服务的地址,是用的WG_HOST:WG_PORT的格式进行生成的,如果出现连不上的情况,需要检查这两个配置是否正确。

部署之后,可以通过访问机器的31821端口进入到Wireguard的后台管理页面,使用方式,以及客户端的连接,和上面的Docker的使用方式一样。

如果后台页面需要使用HTTPS的方式访问(推荐使用,Wireguard使用HTTPS可以最大程度保证安全性,因为Wireguard管理页面密码泄露,意味着其他人都可以新增Wireguard访问我们的内网中的服务),或者想要通过Ingress进行对外访问,可以新增一条Ingress路由配置。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wireguard-nginx-ingress
  namespace: wireguard
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - {domain}
      secretName: {ssl-secret}
  rules:
     - host: {domain}   # 指定当访问这个域名时才将请求交给下面的Service
       http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wg-easy-service  # 指定要路由到的 Service 名称
                port:
                  number: 51821       # 这里需要配置Service暴露的端口号, 而不是NodePort的端口号

3.3 wg-easy总结

使用wg-easy安装Wireguard最大的优点,就是它真的特别简单,容器部署成功之后,只需要在页面上新建客户端,接着只需要扫码/导入配置文件,即可轻松接入Wireguard。

但是wg-easy对于爱折腾的人来说,它也会存在有一些问题,扩展性不够高,比如我想要自定义Wireguard的PostUp和PreDown脚本时,wg-easy似乎没有给我们提供相关的功能,但是wg0配置文件还是交给wg-easy进行管理的,我们又不能自定义wg0配置文件。

wg-easy使用简单的密码登录,但是作为Wireguard这种VPN工具来说,安全也很重要,可以考虑接入动态OTP的方式去提高安全性。

如果想要兼容简单、安全、扩展性好,可以使用下面我们会介绍到的wgdashboard工具,这个工具会牺牲一定的简单的特性,但是安全和扩展性都已经做的比较好。

4. 通过WGdashboard安装Wireguard

wgdashboard是Github一个开源项目,提供了好看的WebUI作为后台Wireguard的管理后台页面,方便我们通过图形化的方式去管理Wireguard,并且其安全性做的很好,支持接入OTP动态验证码提供身份认证

具体可以参考Github:Github-donaldzou/WGDashboard

4.1 通过Docker方式安装wgdashboard

Docker解决方案参考WGDashboard-docker

只需要通过如下的命令,即可轻松配置wgdashboard。

docker run -d \
  --name wgdashboard \
  --restart unless-stopped \
  -e enable=wg0 \
  -e isolate=wg0 \
  -p 10086:10086/tcp \
  -p 51820:51820/udp \
  -v ~/data:/data \
  -v ~/conf:/etc/wireguard \
  --cap-add=SYS_MODULE \
  --sysctl="net.ipv4.conf.all.src_valid_mark=1" \
  --sysctl="net.ipv4.ip_forward=1" \
  --cap-add NET_ADMIN \
  donaldzou/wgdashboard:latest

4.2 通过K8S部署wgdashboard

使用如下的K8S资源清单去部署WgDashboard:

apiVersion: v1
kind: Namespace
metadata:
  name: wireguard

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wireguard-dashboard-data-pvc
  namespace: wireguard  # 在 wireguard 命名空间中创建 PVC
spec:
  accessModes:
    - ReadWriteMany  # NFS 允许多个节点同时访问
  resources:
    requests:
      storage: 10Gi  # 需要的存储大小
  storageClassName: k8s-nfs-storage  # 使用 nfs-storage storage class
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wireguard-dashboard-config-pvc
  namespace: wireguard  # 在 wireguard 命名空间中创建 PVC
spec:
  accessModes:
    - ReadWriteMany  # NFS 允许多个节点同时访问
  resources:
    requests:
      storage: 10Gi  # 需要的存储大小
  storageClassName: k8s-nfs-storage  # 使用 nfs-storage storage classapiVersion: v1

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wireguard-dashboard
  namespace: wireguard  # 在 wireguard 命名空间中创建 Deployment
  labels:
    app: wireguard-dashboard
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wireguard-dashboard
  template:
    metadata:
      labels:
        app: wireguard-dashboard
    spec:
      initContainers:
        - name: set-systemctl
          image: harbor.wanna1314y.top/library/busybox
          command: 
            - "sh"
            - "-c" 
            - | 
              sysctl -w net.ipv4.conf.all.src_valid_mark=1 
              sysctl -w net.ipv4.ip_forward=1
          securityContext:
            privileged: true
          imagePullPolicy: Always
      containers:
        - name: wireguard
          image: harbor.wanna1314y.top/linuxserver/wgdashboard:latest
          env: 
            - name: enable
              value: "wg0"
            - name: isolate
              value: "wg0"
            - name: public_ip
              value: "xxx.yyy.com"
          volumeMounts:
            - name: host-time
              readOnly: true
              mountPath: /etc/localtime
            - name: wireguard-dashboard-config # wg config
              mountPath: /etc/wireguard
            - name: wireguard-dashboard-data  # wg data
              mountPath: /data
          ports:
            - containerPort: 31820
              protocol: UDP
            - containerPort: 31821
              protocol: UDP
            - containerPort: 31822
              protocol: UDP
            - containerPort: 10086
              protocol: TCP
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"]
          resources: {}
          readinessProbe:  # 就绪探测
            httpGet:
              path: /  # 根据应用的配置,调整探测的路径
              port: 10086
              scheme: HTTP
            initialDelaySeconds: 60  # 容器启动后探测的延迟时间
            periodSeconds: 5         # 探测间隔
            timeoutSeconds: 3        # 探测超时时间
            successThreshold: 1      # 成功次数阈值
            failureThreshold: 3      # 失败次数阈值
          livenessProbe:  # 存活探测
            httpGet:
              path: /  # 根据应用的配置,调整探测的路径
              port: 10086
              scheme: HTTP
            initialDelaySeconds: 300  # 容器启动后探测的延迟时间
            periodSeconds: 10        # 探测间隔
            timeoutSeconds: 3        # 探测超时时间
            successThreshold: 1      # 成功次数阈值
            failureThreshold: 3      # 失败次数阈值
      volumes:
        - name: host-time
          hostPath:
            path: /etc/localtime
            type: ''
        - name: wireguard-dashboard-config
          persistentVolumeClaim:
            claimName: wireguard-dashboard-config-pvc  # 使用 PVC
        - name: wireguard-dashboard-data
          persistentVolumeClaim:
            claimName: wireguard-dashboard-data-pvc  # 使用 PVC
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: wireguard-dashboard-service
  namespace: wireguard  # 在 wireguard 命名空间中创建 Service
spec:
  selector:
    app: wireguard-dashboard
  ports:
    - protocol: UDP
      port: 31820
      targetPort: 31820
      nodePort: 31820  # 指定 NodePort 的端口号
      name: wireguard-udp
    - protocol: TCP
      port: 10086
      targetPort: 10086
      nodePort: 30086  # 指定 NodePort 的端口号
      name: wireguard-dashboard
  type: NodePort  # 使用 NodePort 类型

接着,可以访问30086端口去访问Web页面,当然也可以部署Ingress实现HTTPS访问。

4.3 客户端接入wgdashboard服务

部署好之后,使用admin作为账号和密码进行登录,接着注册账号和密码之后,可以进入到如下的WGDashboard后台管理页面。

可以新增Wg配置文件:

image-7thv.png

下方的高级设置当中,还可以自定义PostUp/PreDown等脚本,如果需要进行更多的自定义的话,可能需要用到这个自定义脚本。

image-hsxv.png

接着,可以点击左侧的wg1,进入到wg1的配置文件的管理页面当中来,wg1默认是没生效的,可以点击右上角的Status按钮,让当前配置文件生效。

image-6p3r.png

如果后期需要要修改Wg服务器的配置信息,那么可以点击右下角的设置,去修改Wg服务器相关的配置信息。

image-eiyo.png

如果想要新增连接的终端设备的话,可以点击左下角的Peer去进行新增,接着进入到下面这样的Peer配置页面。

image-alsr.png

编辑完成之后,点击Add新增一个Peer,回到主页。

image-cab7.png

在主页当中,可以点击下载、二维码、查看当前Peer的配置文件信息,对于一般的Linux系统、Mac系统之类的可以使用下载的方式,对于使用Wg的移动端(Android/IOS),可以使用二维码的方式进行配置文件的加载。

对于端点Peer的配置规则如下:

  • "允许的IP地址"(AllowedIPs)用于生成服务端的Peer当中的AllowedIPs和客户端的Address。
    • 其实就是,对于Wireguard服务端来说,这个Peer需要处理哪些IP网段的报文。比如10.0.9.3/32,10.168.1.0/24,就代表这个Peer需要处理这两个网段的网络交互,对于一个10.168.1.100的请求,Wireguard服务端就会根据网段去定位到这个客户端,将流量转发到这个Wireguard客户端。
    • 需要注意的是,这个网段,不要和客户端本机的其他网卡的网段重复,不然可能会导致其他网段的网卡不能正常生效,全部都走的Wireguard的虚拟网卡。
  • "终结点允许的IP地址"(Endpoint AllowedIPs)用于生成客户端的AllowedIPs。
    • 其中就是,对于Wireguard客户端来说,这个Peer需要处理哪些IP网段的报文。

image-h3uc.png

image-dz3s.png

5. 基于Wireguard实现外部访问家里内网(内网穿透)

需求就是:我在外网有一个设备(比如手机、电脑),想要去访问我家里的10.168.1.0/24这个内网网段的机器。

整体的网络结构图如下:

image-jk6j.png

Wireguard内网网段:10.0.8.0/24,家里的网络的网段:10.168.1.0/24。现在想要实现的是:外网想要访问家里的10.168.1.0/24网段的网络。

我们来思考一下:既然10.0.8.210.0.8.310.0.8.4这三个IP,都已经在Wireguard虚拟网络当中可以互相访问,10.0.8.4这台机器又可以访问家里的10.168.1.0/24这整个网络,那么理论上我们可以通过转发的方式实现,10.0.8.2也可以访问10.168.1.0/24的机器。

我们下面的介绍,都是基于最终生成的配置文件wg0.conf的内容去进行的介绍,如果是使用的WGDashboard进行的介绍,在对应的Web页面参照wg0.conf的配置信息进行对应的配置即可。

5.1 Wireguard服务端配置允许访问家里内网的网段

对于服务端来说,我们需要将10.168.1.0/24这个内网网段的流量,全部都转发到10.8.0.4这台机器上,我们找到这台机器的Wireguard的Peer配置,往AllowedIPs当中新增配置项10.168.1.0/24,这样服务端的Wireguard虚拟网卡接口wg0,在接收到流量时,查找路由表时,就可以找到10.0.8.4/32这台机器能同时处理10.0.8.4/32,10.168.1.0/24这两个网段的网络。

Wireguard的服务端配置wg0.conf文件:

# Server
[Interface]
PrivateKey = ......
Address = 10.0.8.1/24
ListenPort = 51820

PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; iptables -I FORWARD -s 10.0.8.0/24 -i wg0 -d 10.0.8.0/24 -j ACCEPT; iptables -I FORWARD -s 10.0.8.0/24 -i wg0 -d 10.168.1.0/24 -j ACCEPT; iptables -I FORWARD -s 10.168.1.0/24 -i wg0 -d 10.0.8.0/24 -j ACCEPT;

PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; iptables -D FORWARD -s 10.0.8.0/24 -i wg0 -d 10.0.8.0/24 -j ACCEPT; iptables -D FORWARD -s 10.0.8.0/24 -i wg0 -d 10.168.1.0/24 -j ACCEPT; iptables -D FORWARD -s 10.168.1.0/24 -i wg0 -d 10.0.8.0/24 -j ACCEPT;

# Client: gmk (640e7741-430f-4ffe-bae0-3320cbbfe14a)
[Peer]
PublicKey = ...
PresharedKey = ...
AllowedIPs = 10.0.8.4/32,10.168.1.0/24

我们还得新增两条iptables规则,iptables -I FORWARD -s 10.0.8.0/24 -i wg0 -d 10.168.1.0/24 -j ACCEPT; iptables -I FORWARD -s 10.168.1.0/24 -i wg0 -d 10.0.8.0/24 -j ACCEPT;,用于支持两个网段的流量之间进行转发时可以互通。

5.2 内网服务器配置允许访问家里内网

我们来想象一下10.0.8.2这台机器请求10.168.1.101这个IP地址流量的流转过程:

  • 1.流量在10.8.0.2这台机器上,通过之前与Wireguard服务端的UDP协议51820端口建立的Socket(这中间其实也会涉及到很多层的路由器的跳转,我们这里简化),将数据包发送给10.0.8.1这台机器(也就是公网IP机器)。
  • 2.首先会将流量经过Wireguard服务端,Wireguard服务端会将流量通过10.0.8.4(10.168.1.100)这台机器进行转发,并将sourceIp修改为服务器的公网IP。
  • 3.流量达到10.0.8.4(10.168.1.100)这台家里的服务器上,此时sourceIp为Wireguard服务端的公网IP地址,destinationIp还是10.168.1.102,在这台机器上会查找路由表,找到另外一个网卡接口有处理10.168.1.0/24这个网段的能力,将流量从这个网卡接口流出。
  • 3.流量到达10.168.1.101这台机器,这台机器在处理请求的数据包之后,正常来说接着响应数据包会重新流转到10.0.8.4(10.168.1.100)这台家里的服务器上。
    • 但是这个流程存在有一个问题,那就是响应报文的sourceIp10.168.1.101destinationIp为服务器的公网IP,但是**10.168.1.101这台机器并未和公网机器建立连接,正确的结果是它需要将报文回给10.0.8.4(10.168.1.100)这台机器**。解决这个问题有两个方案:(1)在上一个阶段,在10.0.8.4(10.168.1.100)机器将流量转发给10.168.1.101这台机器之前,我们将sourceIp修改成为10.168.1.100,这样在最终处理响应报文时10.168.1.101就能自动识别到目标地址是10.168.1.100,从而将报文转发送给10.168.1.100。(2)在10.168.1.101这台机器上,使用NAT手动将公网IP的目标地址转换成为10.168.1.100,但是这意味着每台机器都得修改。因此使用第一种方案,是最为合理的。
  • 5.响应报文到达10.0.8.4(10.168.1.100)这台机器,这台机器通过之前和公网服务器建立的Socket,将流量重新转发给公网服务器10.0.8.1,此时sourceIp10.168.1.100destinationIp为公网服务器的IP。
  • 6.流量到达Wireguard服务端,也就是公网服务器的50820端口,公网服务器Wireguard服务,通过之前和10.8.0.2这台机器建立的Socket(这中间其实也会涉及到很多层的路由器的跳转,我们这里简化),将流量重新返回给10.8.0.2这台机器。
  • 7.最终结果:10.8.0.2这台机器收到了来自10.168.1.101内网机器的响应。

因此我们需要家里内网(gmk)的机器的Wireguard配置:

[Interface]
PrivateKey = .....
Address = 10.0.8.4/24
DNS = 114.114.114.114

PostUp = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -j SNAT --to-source 10.168.1.100
PostDown = iptables -t nat -D POSTROUTING -s 10.0.8.0/24 -j SNAT --to-source 10.168.1.100;

[Peer]
PublicKey = ...
PresharedKey = .....
AllowedIPs = 10.0.8.0/24
PersistentKeepalive = 25
Endpoint = xxx.yyy.com:51820

主要修改一个地方,就是新增iptables规则,实现在内网之间的流量转发时,将sourceIp修改成为当前机器的IP,适用于内网的IP地址不会变更的情况。

PostUp = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -j SNAT --to-source 10.168.1.100
PostDown = iptables -t nat -D POSTROUTING -s 10.0.8.0/24 -j SNAT --to-source 10.168.1.100;

也可以指定如下的内容,通过-j MASQUERADE直接修改sourceIpeth0网卡的IP,好处是可以如果机器的IP地址发生变更,那么也能动态的探测内网的IP地址,比如使用了动态的DHCP服务的时候。

PostUp = iptables -t nat -A POSTROUTING -s 10.0.8.0/24 -o eth0  -j MASQUERADE;
PostDown = iptables -t nat -D POSTROUTING -s 10.0.8.0/24 -o eth0  -j MASQUERADE;

5.3 外网访问的客户端接入家里内网

外网需要访问家里内网的机器的Wireguard客户端配置,需要配置AllowedIPs,将10.168.1.0/24也放入到其中,标识对于10.168.1.0/24这个网段的流量,也将它转发给Wireguard服务器进行流量的转发,不然凭空产生的10.168.1.0/24的流量,机器当然是无法处理的。

[Interface]
PrivateKey = ......
Address = 10.0.8.3/24
DNS = 114.114.114.114


[Peer]
PublicKey = ......
PresharedKey = ......
AllowedIPs = 10.0.8.0/24,10.168.1.0/24
PersistentKeepalive = 25
Endpoint = xxx.yyy.com:51820

修改配置之后,需要重新启动Wireguard客户端。

sudo wg-quick down ./wg0.conf
sudo wg-quick up ./wg0.conf 

对于移动端(IOS/Android)的Wireguard客户端,可以直接在Wireguard客户端的配置页面去修改AllowedIPs

5.4 K8S集群的容器组IP接入Wireguard VPN

如果我们想要在外网访问K8S集群当中的各个容器组IP的话,我们可以创建一个Pod,让这个Pod去接入Wireguard服务端,从而桥接整个K8S集群的容器组IP。

内部的K8S集群的IP地址网段CIDR是10.233.64.0/18,具体配置可以参考如下的资源清单:

apiVersion: v1
kind: Namespace
metadata:
  name: linux
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: k8s-cluster-vpn-internet
  namespace: linux
  annotations:
    kubesphere.io/creator: admin
    kubesphere.io/description: ''
data:
  wg0.conf: >
    [Interface]
    
    PrivateKey = {客户端私钥}
    
    Address = 10.0.8.12/32
    
    MTU = 1420
    
    DNS = 114.114.114.114

    PostUp = iptables -t nat -A POSTROUTING -s 10.233.64.0/18 -o eth0  -j
    MASQUERADE;
    
    PostUp = iptables -t nat -D POSTROUTING -s 10.233.64.0/18 -o eth0  -j
    MASQUERADE;

    [Peer]
    
    PublicKey = {服务端公钥}
    
    AllowedIPs = 10.0.8.0/24
    
    Endpoint = xxx.yyy.com:51820
    
    PersistentKeepalive = 25
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wg-k8s-vpn-internet
  namespace: linux
  labels:
    app: wg-k8s-vpn-internet
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wg-k8s-vpn-internet
  template:
    metadata:
      labels:
        app: wg-k8s-vpn-internet
    spec:
      initContainers:
        - name: set-systemctl
          image: harbor.wanna1314y.top/library/busybox
          command:
            - "sh"
            - "-c"
            - |
              sysctl -w net.ipv4.conf.all.src_valid_mark=1
              sysctl -w net.ipv4.ip_forward=1
          securityContext:
            privileged: true
          imagePullPolicy: Always
      containers:
        - name: wireguard
          image: harbor.wanna1314y.top/linuxserver/wireguard:latest
          command:
            - "sh"
            - "-c"
            - "wg-quick up wg0 && tail -f /dev/null"
          volumeMounts:
            - name: host-time
              mountPath: /etc/localtime
            - name: vpn-config
              mountPath: /etc/wireguard/wg0.conf
              readOnly: true
              subPath: wg0.conf  # 这里将 wg0.conf 挂载到容器的 /etc/wireguard/wg0.conf
          securityContext:
            capabilities:
              add: ["NET_ADMIN", "SYS_MODULE"]
          resources: {}
      volumes:
        - name: host-time
          hostPath:
            type: ''
            path: /etc/localtime
        - name: vpn-config
          configMap:
            name: k8s-cluster-vpn-internet

我们使用ConfigMap的方式,将Wireguard的客户端wg0配置文件挂载到Pod当中,接着在Pod当中,运行Wireguard客户端服务,去连接Wireguard服务端。

我们仅仅展示桥接的客户端的配置信息(对应5.2部分),对于Wireguard服务端的配置信息和外放访问K8S集群的客户端信息配置,参照上面我们介绍的5.15.3这两个部分。

5.5 通过前面提到的多种方式进行接入时的注意点

使用官方原始的方式接入Wireguard注意点

没什么问题,就是无法做到可视化,得自己修改配置文件,无论什么功能都能实现。

通过wg-easy的方式接入Wireguard注意点

说明一下:因为wg-easy实现的是简单,因此没有提供我们改服务端的wg0配置文件的方式,但是我们上面的方式,是需要修改服务端的wg0配置文件进行定制化的,因此通过wg-easy实现不了这个功能。

通过wgdashboard的方式接入Wireguard注意点:

wgdashboard接入时,会有两个问题:

  • 1.wgdashboard在进行Peer的生成时,服务端对于客户端的Peer配置AllowedIPs和客户端的Address是同一个值。比如10.0.8.4(10.168.1.100)这台机器的配置就会是10.0.8.4/32,10.168.1.0/24。但是如果是同一个值的话,会导致Wireguard客户端处理的网段,和内网的网段冲突,会导致内网中无法访问该设备,只有通过Wireguard连上VPN之后才能访问使用内网IP访问10.168.1.100。因此方案是:在下载配置文件之后,手动修改Address,将10.168.1.0/24这个网段删掉就行。
  • 2.无法定义客户端的wg0配置文件的PostUp相关的钩子回调,只能在下载配置文件之后,手动新增PostUpPostDown,并添加iptables规则。

主要就上面的两个问题,其实问题不大,都只是10.0.8.4(10.168.1.100)这一台机器上的配置文件,需要下载后修改一下。

6. Wireguard实现VPN组网总结

Comment