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转发),它还管理防火墙设置。 尽管如此,PreUpPreDownPostUpPostDown钩子可能是处理在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 8Rocky Linux 8CentOS 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_addresswireguard_addresses配置至少一个地址。wireguard_address只能包含一个IPv4,因此建议使用可以包含IPv4和IPv6地址数组的wireguard_addresses变量。

wireguard_addresses:
  - "10.8.0.101/24"

当然,所有IP应该在同一子网内,如上例中的/24。如果wireguard_allowed_ips未设置,则默认值是定义在wireguard_addresswireguard_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.tldi表示内部)。对于公共IP,我创建了一个DNS条目,如host01.p.domain.tldp表示公共)。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_addresswireguard_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_addresswireguard_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

许可证

GNU通用公共许可证v3.0或更高版本

作者信息

http://www.tauceti.blog

关于项目

Installs Wireguard incl. systemd integration

安装
ansible-galaxy install githubixx.ansible_role_wireguard
许可证
Unknown
下载
239.1k
拥有者
Senior System Engineer - Python, Go, Cloud, Kubernetes, Commodore, Retro, 80's ;-)