githubixx.ansible_role_wireguard
ansible角色-wireguard
这个Ansible角色在我的博客系列用Ansible简化Kubernetes中使用,但当然也可以独立使用。我使用WireGuard和这个Ansible角色来在我的小型Kubernetes集群的所有节点之间建立一个完全网状的VPN。
一般来说,WireGuard是一个用于IPv4和IPv6的网络隧道(VPN),使用UDP协议。如果你需要更多关于WireGuard的信息,可以在这里找到一个好的介绍:安装WireGuard,现代VPN。
Linux
此角色应与以下版本兼容:
- Ubuntu 20.04 (Focal Fossa)
- Ubuntu 22.04 (Jammy Jellyfish)
- Ubuntu 24.04 (Noble Numbat)
- Archlinux
- Debian 11 (Bullseye)
- Debian 12 (Bookworm)
- Fedora 39
- AlmaLinux 9
- Rocky Linux 9
- openSUSE Leap 15.5
- openSUSE Leap 15.6
- Oracle Linux 9
最佳努力支持
- AlmaLinux 8
- Rocky Linux 8
- elementary OS 6
- CentOS 7(自2024年6月底已结束生命周期)
Molecule测试可用(见下文)。它也应该能在Raspbian Buster
上工作,但没有可用的测试。MacOS(见下文)也应部分工作,但仅为最佳努力支持。
MacOS
在Linux上,这个剧本配置、启用并启动一个systemd
服务,不需要额外的操作;在MacOS上,它安装所需的包,并仅生成正确的wg0.conf
文件,然后将其放在指定的wireguard_remote_directory
中(默认是/opt/local/etc/wireguard
)。要运行VPN,你需要:
sudo wg-quick up wg0
要停用它:
sudo wg-quick down wg0
或者你可以安装官方应用并导入wg0.conf
文件。
版本
我会给每个发布版本打标签,并尝试遵循语义版本控制。如果你想使用这个角色,我建议你下载最新的标签。master分支基本上是开发分支,而标签则标记稳定的发布版本。不过一般来说,我也会使master保持良好状态。
要求
默认情况下,端口51820
(协议为UDP)应该可以从外部访问。但你可以通过更改变量wireguard_port
来调整这个端口。同时需要启用IP转发,例如通过执行echo 1 > /proc/sys/net/ipv4/ip_forward
。我决定不在这个Ansible角色中实现这个任务。依我之见,这应该在其他地方处理。
你可以使用我的ansible-role-harden-linux。除了更改sysctl条目(你需要启用IP转发),它还管理防火墙设置。
尽管如此,PreUp
、PreDown
、PostUp
和PostDown
钩子可能是处理在WireGuard接口启动或停止之前的网络相关操作的好地方。
更新日志
变更历史:
查看完整的CHANGELOG.md
最近的更改:
17.0.0
重大变更
- 移除对
openSUSE 15.4
的支持(已结束生命周期)
- 移除对
新功能
- 增加对
Ubuntu 24.04
的支持 - 增加对
openSUSE 15.6
的支持
- 增加对
MOLECULE
- 移除过时的
Proxmox
代码 - 将Vagrant box
rockylinux/9
替换为bento/rockylinux-9
- 对AlmaLinux使用
ansible.builtin.package
- 移除
AlmaLinux 8
、Rocky Linux 8
和CentOS 7
(过时的Python使其难以与Ansible测试)
- 移除过时的
安装
从Github直接下载(在克隆之前切换到Ansible角色目录):
git clone https://github.com/githubixx/ansible-role-wireguard.git githubixx.ansible_role_wireguard
通过
ansible-galaxy
命令从Ansible Galaxy直接下载:ansible-galaxy role install githubixx.ansible_role_wireguard
创建一个
requirements.yml
文件,内容如下(这将从Github下载角色)并使用以下命令安装:ansible-galaxy role install -r requirements.yml
:
---
roles:
- name: githubixx.ansible_role_wireguard
src: https://github.com/githubixx/ansible-role-wireguard.git
version: 17.0.0
角色变量
这些变量可以在group_vars/
中更改,例如:
# 在远程主机上存储WireGuard配置的目录
wireguard_remote_directory: "/etc/wireguard" # 在Linux上
# wireguard_remote_directory: "/opt/local/etc/wireguard" # 在MacOS上
# 默认情况下WireGuard监听的端口(如果未另作指定)。
wireguard_port: "51820"
# 默认情况下WireGuard使用的接口名称(如果未另作指定)。
wireguard_interface: "wg0"
# wg.conf文件的默认所有者
wireguard_conf_owner: root
# wg.conf文件的默认组别
wireguard_conf_group: "{{ 'root' if not ansible_os_family == 'Darwin' else 'wheel' }}"
# wg.conf文件的默认模式
wireguard_conf_mode: 0600
# 是否应备份wg.conf文件的任何更改
wireguard_conf_backup: false
# wireguard服务的默认状态
wireguard_service_enabled: "yes"
wireguard_service_state: "started"
# 默认情况下使用"wg syncconf"来应用WireGuard接口设置,如果它们已更改。较旧的WireGuard工具不提供此选项。
# 在这种情况下,作为后备,WireGuard接口将被重启。这将导致短暂的网络连接中断。
#
# 因此,即使"false"是默认值,角色也会判断"wg"工具的"syncconf"选项是否可用,如果不可用,则回退为"true"。
#
# 可能的选项:
# - false(默认)
# - true
#
# 两个选项各有优缺点。默认的"false"选项(不重启接口)
# - 不需要重启WireGuard接口以应用更改
# - 在应用更改时不会导致短暂的VPN连接中断
# - 可能导致网络路由未正确重新加载
#
# 将此选项值设置为"true"将
# - 在发生更改时重启WireGuard接口
# - 在应用更改时导致短暂的VPN连接中断
# - 确保网络路由被正确重新加载
#
# 因此,这取决于你的设置,哪个选项最有效。如果你没有过于复杂的路由,且很少变动,
# 使用"false"通常是足够的。例如,如果你只想通过VPN连接几个服务器,并且通常保持不变。
#
# 如果你有一个更动态的路由设置,那么将其设置为"true"可能是最安全的选择。如果你想避免可能创建一些
# 难以发现的副作用,这个选项也是可以考虑的。
wireguard_interface_restart: false
# 通常,在第一次没有WireGuard配置时,角色会自动创建一个私钥。
# 但此选项允许你在真正需要时提供自己的WireGuard私钥。由于这是一个非常敏感的值,
# 你可以考虑使用Ansible Vault等工具来加密存储。
# wireguard_private_key:
# 如果不需要更新包缓存(仅在相关的软件包管理器支持此选项时相关),请设置为"false"
wireguard_update_cache: "true"
# 通常,角色会在适当的地方安装和激活wireguard内核模块。
# 在某些情况下,我们可能无法加载内核模块,例如在没有特权的LXC客户机中。
# 如果你将此设置为false,则必须确保在内核中可用wireguard模块!
wireguard_install_kernel_module: true
还有一些特定于Linux发行版的设置:
#######################################
# 仅与以下相关的设置:
# - Ubuntu
# - elementary OS
#######################################
# 已弃用:请使用"wireguard_update_cache"代替。
# 如果不应更新包缓存,请设置为"false"。
wireguard_ubuntu_update_cache: "{{ wireguard_update_cache }}"
# 设置包缓存有效时间
wireguard_ubuntu_cache_valid_time: "3600"
#######################################
# 仅与CentOS 7相关的设置
#######################################
# 将wireguard_centos7_installation_method设置为"kernel-plus"
# 以使用内置的、签名的WireGuard模块的kernel-plus内核。
#
# 默认的"standard"将使用标准内核和ELRepo模块安装WireGuard。
wireguard_centos7_installation_method: "standard"
# 在使用"kernel-plus"内核时,必要时重启主机
wireguard_centos7_kernel_plus_reboot: true
# 内核使用"kernel-plus"时等待机器重启并响应的默认秒数。
# 该选项仅与"wireguard_centos7_kernel_plus_reboot"设置为"true"时相关。
wireguard_centos7_kernel_plus_reboot_timeout: "600"
# 在使用标准内核时,必要时重启主机
wireguard_centos7_standard_reboot: true
# 在使用"standard"内核时等待机器重启并响应的默认秒数。
# 该选项仅与"wireguard_centos7_standard_reboot"设置为"true"时相关。
wireguard_centos7_standard_reboot_timeout: "600"
#########################################
# 仅与RockyLinux 8相关的设置
#########################################
# 将wireguard_rockylinux8_installation_method设置为"dkms"
# 以基于源代码构建WireGuard模块,使用wireguard-dkms。
# 如果你使用自定义内核和/或架构不是x86_64,则需要此设置。
#
# 默认的"standard"将使用ELRepo中的kmod-wireguard安装内核模块。
wireguard_rockylinux8_installation_method: "standard"
在host_vars/
中的每个主机应该通过wireguard_address
或wireguard_addresses
配置至少一个地址。wireguard_address
只能包含一个IPv4,因此建议使用可以包含IPv4和IPv6地址数组的wireguard_addresses
变量。
wireguard_addresses:
- "10.8.0.101/24"
当然,所有IP应该在同一子网内,如上例中的/24
。如果wireguard_allowed_ips
未设置,则默认值是定义在wireguard_address
和wireguard_addresses
中的IP,但不带CIDR,而是使用/32
(IPv4)或/128
(IPv6),这基本上是主机路由(请查看templates/wg.conf.j2
)。让我们看看这个例子,假设你没有明确设置wireguard_allowed_ips
:
[Interface]
Address = 10.8.0.2/24
PrivateKey = ....
ListenPort = 51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.101/32
Endpoint = controller01.p.domain.tld:51820
这是我工作站的WireGuard配置的一部分。它的VPN IP是10.8.0.2
,我们有一个/24
子网,所有的WireGuard主机都在其中。你还可以看到我们有一个端点为controller01.p.domain.tld:51820
的对等方。当wireguard_allowed_ips
没有明确设置时,Ansible模板将添加一个AllowedIPs
条目,包含该主机的IP以及/32
或/128
。在WireGuard中,这基本上指定了路由。上面的配置表示:在我的工作站IP为10.8.0.2
的情况下,我希望将所有流量发送到10.8.0.101/32
的端点controller01.p.domain.tld:51820
。现在假设我们设置wireguard_allowed_ips: "0.0.0.0/0"
。那么结果配置看起来是这样的:
[Interface]
Address = 10.8.0.2/24
PrivateKey = ....
ListenPort = 51820
[Peer]
PublicKey = ....
AllowedIPs = 0.0.0.0/0
Endpoint = controller01.p.domain.tld:51820
现在,这实际上与上面的配置相同,但现在配置表示:我想将我工作站上发出的每一流量路由到端点controller01.p.domain.tld:51820
。如果该端点可以处理这些流量当然是另一回事,具体取决于你如何配置端点路由 ;-)
你可以在host_vars/
中(或在你的Ansible主机文件中,如果你愿意的话)为每个主机指定进一步的可选设置(它们没有默认值,除非是wireguard_allowed_ips
,如前所述)。以下变量的值只是示例,没有默认值(更多信息和示例请参阅wg-quick.8):
wireguard_allowed_ips: ""
wireguard_endpoint: "host1.domain.tld"
wireguard_persistent_keepalive: "30"
wireguard_dns: "1.1.1.1"
wireguard_fwmark: "1234"
wireguard_mtu: "1492"
wireguard_table: "5000"
wireguard_preup:
- ...
wireguard_predown:
- ...
wireguard_postup:
- ...
wireguard_postdown:
- ...
wireguard_save_config: "true"
wireguard_(preup|predown|postup|postdown)
指定为列表。这里有两个示例:
wireguard_postup:
- iptables -t nat -A POSTROUTING -o ens12 -j MASQUERADE
- iptables -A FORWARD -i %i -j ACCEPT
- iptables -A FORWARD -o %i -j ACCEPT
wireguard_preup:
- echo 1 > /proc/sys/net/ipv4/ip_forward
- ufw allow 51820/udp
这些命令按顺序执行,如wg-quick.8所述。
此外,还可以添加“未管理”的对等方。这些对等方不由Ansible处理,也不属于vpn
Ansible主机组,例如:
wireguard_unmanaged_peers:
client.example.com:
public_key: 5zsSBeZZ8P9pQaaJvY9RbELQulcwC5VBXaZ93egzOlI=
# preshared_key: ... e.g. from ansible-vault?
allowed_ips: 10.0.0.3/32
endpoint: client.example.com:51820
persistent_keepalive: 0
如前所述,wireguard_address
(已弃用)或wireguard_addresses
(推荐)之一是必需的。它是定义的接口名称(默认是wg0
)的IP地址。每个主机当然需要至少一个唯一的VPN IP。如果你不设置wireguard_endpoint
,剧本将使用在vpn
主机组中定义的主机名(Ansible库存主机名)。如果你将wireguard_endpoint
设置为空字符串(""
),则该对等方将没有端点。这意味着该主机只能访问拥有wireguard_endpoint
的主机。这对于不向VPN暴露任何服务、仅想访问其他主机服务的客户端很有用。因此,如果你只定义一个主机,且设置wireguard_endpoint
,而所有其他主机的wireguard_endpoint
设置为""
(空字符串),这基本上意味着除了一个WireGuard服务器外,你只有客户端。第三种可能性是将wireguard_endpoint
设置为某个主机名。例如,如果你为该主机的私有和公共DNS有不同的主机名,并且需要不同的DNS条目,设置wireguard_endpoint
会很方便。以上面的IP为例:wireguard_address: "10.8.0.101"
。这是一个私有IP,我为该私有IP创建了一个DNS条目,如host01.i.domain.tld
(i
表示内部)。对于公共IP,我创建了一个DNS条目,如host01.p.domain.tld
(p
表示公共)。wireguard_endpoint
需要是其他vpn
组成员可以连接的接口。因此,在这种情况下,我会将wireguard_endpoint
设置为host01.p.domain.tld
,因为WireGuard通常需要能够连接到其他主机的公共IP。
这是我使用该剧本的一个小例子:我使用WireGuard来建立一个完全网状的VPN(每个主机可以直接连接到其他每个主机),并在Hetzner Cloud上运行我的Kubernetes(K8s)集群(但你应该能够使用任何你想要的主机)。所以K8s控制器和工作节点(包括pod)等重要组件只通过加密的WireGuard VPN进行通信。此外(如前所述),我有两个客户端。两个客户端都安装了kubectl
,并能够通过WireGuard VPN与内部Kubernetes API服务器交互。其中一个客户端还公开了WireGuard端点,因为云中的Postfix邮件服务器和我的内部Postfix需要能够相互通信。我想这可能不是WireGuard的常见用例 :D 但它展示了可能性。所以让我解释一下设置,这可能有助于你使用这个Ansible角色。
首先,这里是我Ansible hosts
文件的一部分:
[vpn]
controller0[1:3].i.domain.tld
worker0[1:2].i.domain.tld
server.at.home.i.domain.tld
workstation.i.domain.tld
[k8s_controller]
controller0[1:3].i.domain.tld
[k8s_worker]
worker0[1:2].i.domain.tld
正如你所看到的,我有三个组:vpn
(所有将在其上安装WireGuard的主机)、k8s_controller
(Kubernetes控制节点)和k8s_worker
(Kubernetes工作节点)。域名中的i
是代表内部
。所有的i.domain.tld
DNS条目都有一个A
记录,指向我们稍后为每个主机定义的WireGuard IP,例如:controller01.i.domain.tld. IN A 10.8.0.101
。这样做的原因是所有Kubernetes组件在我的设置中仅绑定并监听WireGuard接口。由于我需要这些内部IP用于所有Kubernetes组件,所以在Ansible hosts
文件中指定内部DNS条目。这样,我可以在剧本和模板中很轻松地使用Ansible库存主机名和变量。
对于Kubernetes控制节点,我定义了以下主机变量:
Ansible主机文件:host_vars/controller01.i.domain.tld
---
wireguard_addresses:
- "10.8.0.101/24"
wireguard_endpoint: "controller01.p.domain.tld"
ansible_host: "controller01.p.domain.tld"
ansible_python_interpreter: /usr/bin/python3
Ansible主机文件:host_vars/controller02.i.domain.tld
:
---
wireguard_addresses:
- "10.8.0.102/24"
wireguard_endpoint: "controller02.p.domain.tld"
ansible_host: "controller02.p.domain.tld"
ansible_python_interpreter: /usr/bin/python3
Ansible主机文件:host_vars/controller03.i.domain.tld
:
---
wireguard_addresses:
- "10.8.0.103/24"
wireguard_endpoint: "controller03.p.domain.tld"
ansible_host: "controller03.p.domain.tld"
ansible_python_interpreter: /usr/bin/python3
我为每个节点指定了ansible_python_interpreter
,因为控制节点使用Ubuntu 18.04,默认安装了Python 3。ansible_host
设置为该主机的公共DNS。Ansible将使用此主机名通过SSH连接到主机。由于同样的原因,我也将wireguard_endpoint
设置为相同的值。WireGuard对等方需要通过公共IP连接到其他对等方。通过wireguard_address
或wireguard_addresses
指定的IP当然需要对每个主机唯一。
对于Kubernetes工作节点,我定义了以下变量:
Ansible主机文件:host_vars/worker01.i.domain.tld
---
wireguard_addresses:
- "10.8.0.111/24"
wireguard_endpoint: "worker01.p.domain.tld"
wireguard_persistent_keepalive: "30"
ansible_host: "worker01.p.domain.tld"
ansible_python_interpreter: /usr/bin/python3
Ansible主机文件:host_vars/worker02.i.domain.tld
:
---
wireguard_addresses:
- "10.8.0.112/24"
wireguard_endpoint: "worker02.p.domain.tld"
wireguard_persistent_keepalive: "30"
ansible_host: "worker02.p.domain.tld"
ansible_python_interpreter: /usr/bin/python3
如你所见,变量基本上与控制节点的相同,唯一的例外是:wireguard_persistent_keepalive: "30"
。我的工作节点(在Hetzner Cloud上)和我的内部服务器(我家中的服务器)是通过运行Postfix而连接的,云节点中的外部Postfix服务器会将接收到的邮件转发到我的内部服务器(反之亦然)。我需要保持活动的设置,因为有时云实例和内部服务器会失去连接,而这个设置解决了这个问题。原因当然是我的内部服务器处于NAT后面,防火墙/路由器必须保持NAT/防火墙映射有效(NAT和防火墙穿越持久性)。
对于我家中的内部服务器(通过DSL路由器连接到互联网)我们有如下配置:
---
wireguard_addresses:
- "10.8.0.1/24"
wireguard_endpoint: "server.at.home.p.domain.tld"
wireguard_persistent_keepalive: "30"
ansible_host: 192.168.2.254
ansible_port: 22
默认情况下,所有公共节点的SSH守护进程使用的端口与22不同,但在内部我使用的是22
,这就是在这里设置ansible_port: 22
的原因。ansible_host
当然是该主机的内部IP。wireguard_endpoint
的值是一个动态DNS条目。由于我在家中的IP不是静态的,我需要每分钟在我的家庭服务器上运行一个脚本,检查IP是否发生了变化,并在需要时调整我的DNS记录。我使用OVH的DynHost功能来实现这一点,但你当然可以使用任何DynDNS提供商。此外,我将到达51820/UDP
端口的传入流量转发到我的内部服务器,以允许传入的WireGuard流量。通过wireguard_address
和wireguard_addresses
指定的IP当然需要是我们的WireGuard子集的一部分。
最后,对于我的工作站(我在其中运行所有ansible-playbook
命令):
wireguard_addresses:
- "10.8.0.2/24"
wireguard_endpoint: ""
ansible_connection: local
ansible_become: false
正如你所看到的,这里wireguard_endpoint: ""
是空字符串。这意味着Ansible角色不会为我的工作站设置端点。由于没有必要让其他主机连接到我的工作站,因此定义一个端点没有意义。因此,在这种情况下,我可以从我的工作站访问Ansible组vpn
中定义的所有主机,而其他主机无法访问。我的工作站的结果WireGuard配置看起来如下:
[Interface]
Address = 10.8.0.2/24
PrivateKey = ....
ListenPort = 51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.101/32
Endpoint = controller01.p.domain.tld:51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.102/32
Endpoint = controller02.p.domain.tld:51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.103/32
Endpoint = controller03.p.domain.tld:51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.111/32
PersistentKeepalive = 30
Endpoint = worker01.p.domain.tld:51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.112/32
PersistentKeepalive = 30
Endpoint = worker02.p.domain.tld:51820
[Peer]
PublicKey = ....
AllowedIPs = 10.8.0.1/32
PersistentKeepalive = 30
Endpoint = server.at.home.p.domain.tld:51820
其他WireGuard配置文件(默认是wg0.conf
)看起来相似,但当然[Interface]
部分包含特定主机的配置,[Peer]
条目列出了其他主机的配置。
示例剧本
- hosts: vpn
roles:
- githubixx.ansible_role_wireguard
- hosts: vpn
roles:
-
role: githubixx.ansible_role_wireguard
tags: role-wireguard
使用两个不同WireGuard接口的主机"multi"的示例库存
这是一个复杂的示例,使用yaml库存格式:
vpn1:
hosts:
multi:
wireguard_addresses:
- "10.9.0.1/32"
wireguard_allowed_ips: "10.9.0.1/32, 192.168.2.0/24"
wireguard_endpoint: multi.example.com
nated:
wireguard_addresses:
- "10.9.0.2/32"
wireguard_allowed_ips: "10.9.0.2/32, 192.168.3.0/24"
wireguard_persistent_keepalive: 15
wireguard_endpoint: nated.example.com
wireguard_postup:
- iptables -t nat -A POSTROUTING -o ens12 -j MASQUERADE
- iptables -A FORWARD -i %i -j ACCEPT
- iptables -A FORWARD -o %i -j ACCEPT
wireguard_postdown:
- iptables -t nat -D POSTROUTING -o ens12 -j MASQUERADE
- iptables -D FORWARD -i %i -j ACCEPT
- iptables -D FORWARD -o %i -j ACCEPT
vpn2:
hosts:
# 使用不同的名称,并定义ansible_host,以避免混合变量
# 而不需要为变量添加接口名称前缀。
multi-wg1:
ansible_host: multi
wireguard_interface: wg1
# 当在一个主机上使用多个接口时,我们必须使用不同的端口
wireguard_port: 51821
wireguard_addresses:
- "10.9.1.1/32"
wireguard_endpoint: multi.example.com
another:
wireguard_address:
- "10.9.1.2/32"
wireguard_endpoint: another.example.com
示例剧本如下:
- hosts: vpn1
roles:
- githubixx.ansible_role_wireguard
- hosts: vpn2
roles:
- githubixx.ansible_role_wireguard
测试
此角色有一个小的测试设置,使用Molecule、libvirt (vagrant-libvirt)和QEMU/KVM创建。请查看我的博客文章使用Molecule、libvirt(vagrant-libvirt)和QEMU/KVM测试Ansible角色了解如何设置。测试配置在这里。
然后可以执行Molecule:
molecule converge
这将设置相当多的虚拟机(VM),使用不同的支持的Linux操作系统。要运行一些测试:
molecule verify
要清理运行:
molecule destroy
还有一个小的Molecule设置,模拟一个中央WireGuard服务器与几个客户端:
molecule converge -s single-server
许可证
作者信息
ansible-galaxy install githubixx.ansible_role_wireguard