Allow to use multiple addresses and added IPv6 support (#174)

* Basic IPv6 support

Hosts can now have one IPv6, by specifying 'wireguard_address_v6' variable. This IP is added to peer's AllowedIPs.

Future plans :
- Support IPv6 only hosts (No 'wireguard_address')
- Allow the endpoint to be an IPv6 address

* Added 'wireguard_addresses' to use multiple IPs

Added the 'wireguard_addresses' variable to specify an array of IPv4 and IPv6. The old 'wireguard_address' variable can be deprecated even she still work to specify one IPv4.

The 'wireguard_address_v6' from last commit was deleted.

* Updating the README to use `wireguard_addresses`

* 13.0.0 changelog
master 13.0.0
Mathéo Cimbaro 1 year ago committed by GitHub
parent 4631fbdc06
commit dd64b7bf2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
# Changelog
## 13.0.0
- add IPv6 support (contribution by @DiscowZombie)
- introduce `wireguard_addresses` variable (contribution by @DiscowZombie)
## 12.0.0
- remove Fedora 35 support (reached EOL)

@ -184,13 +184,14 @@ wireguard_centos7_kernel_plus_reboot_timeout: "600"
wireguard_rockylinux8_installation_method: "standard"
```
The following variable is mandatory and needs to be configured for every host in `host_vars/` e.g.:
Every host in `host_vars/` should configure at least one address via `wireguard_address` or `wireguard_addresses`. The `wireguard_address` can only contain one IPv4, thus it's recommended to use the `wireguard_addresses` variable that can contain an array of both IPv4 and IPv6 addresses.
```yaml
wireguard_address: "10.8.0.101/24"
wireguard_address:
- "10.8.0.101/24"
```
Of course all IP's should be in the same subnet like `/24` we see in the example above. If `wireguard_allowed_ips` is not set then the default value is the value from `wireguard_address` without the CIDR but instead with `/32` which is basically a host route (have a look `templates/wg.conf.j2`). Let's see this example and let's assume you don't set `wireguard_allowed_ips` explicitly:
Of course all IP's should be in the same subnet like `/24` we see in the example above. If `wireguard_allowed_ips` is not set then the default values are IPs defined in `wireguard_address` and `wireguard_addresses` without the CIDR but instead with `/32` (IPv4) or `/128` (IPv6) which is basically a host route (have a look `templates/wg.conf.j2`). Let's see this example and let's assume you don't set `wireguard_allowed_ips` explicitly:
```ini
[Interface]
@ -204,7 +205,7 @@ AllowedIPs = 10.8.0.101/32
Endpoint = controller01.p.domain.tld:51820
```
This is part of the WireGuard config from my workstation. It has the VPN IP `10.8.0.2` and we've a `/24` subnet in which all my WireGuard hosts are located. Also you can see we've a peer here that has the endpoint `controller01.p.domain.tld:51820`. When `wireguard_allowed_ips` is not explicitly set the Ansible template will add an `AllowedIPs` entry with the IP of that host plus `/32`. In WireGuard this basically specifies the routing. The config above says: On my workstation with the IP `10.8.0.2` I want send all traffic to `10.8.0.101/32` to the endpoint `controller01.p.domain.tld:51820`. Now let's assume we set `wireguard_allowed_ips: "0.0.0.0/0"`. Then the resulting config looks like this.
This is part of the WireGuard config from my workstation. It has the VPN IP `10.8.0.2` and we've a `/24` subnet in which all my WireGuard hosts are located. Also you can see we've a peer here that has the endpoint `controller01.p.domain.tld:51820`. When `wireguard_allowed_ips` is not explicitly set the Ansible template will add an `AllowedIPs` entry with the IP of that host plus `/32` or `/128`. In WireGuard this basically specifies the routing. The config above says: On my workstation with the IP `10.8.0.2` I want send all traffic to `10.8.0.101/32` to the endpoint `controller01.p.domain.tld:51820`. Now let's assume we set `wireguard_allowed_ips: "0.0.0.0/0"`. Then the resulting config looks like this.
```ini
[Interface]
@ -265,7 +266,7 @@ wireguard_preup:
The commands are executed in order as described in [wg-quick.8](https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8).
`wireguard_address` is required as already mentioned. It's the IP of the interface name defined with `wireguard_interface` variable (`wg0` by default). Every host needs a unique VPN IP of course. If you don't set `wireguard_endpoint` the playbook will use the hostname defined in the `vpn` hosts group (the Ansible inventory hostname). If you set `wireguard_endpoint` to `""` (empty string) that peer won't have a endpoint. That means that this host can only access hosts that have a `wireguard_endpoint`. That's useful for clients that don't expose any services to the VPN and only want to access services on other hosts. So if you only define one host with `wireguard_endpoint` set and all other hosts have `wireguard_endpoint` set to `""` (empty string) that basically means you've only clients besides one which in that case is the WireGuard server. The third possibility is to set `wireguard_endpoint` to some hostname. E.g. if you have different hostnames for the private and public DNS of that host and need different DNS entries for that case setting `wireguard_endpoint` becomes handy. Take for example the IP above: `wireguard_address: "10.8.0.101"`. That's a private IP and I've created a DNS entry for that private IP like `host01.i.domain.tld` (`i` for internal in that case). For the public IP I've created a DNS entry like `host01.p.domain.tld` (`p` for public). The `wireguard_endpoint` needs to be a interface that the other members in the `vpn` group can connect to. So in that case I would set `wireguard_endpoint` to `host01.p.domain.tld` because WireGuard normally needs to be able to connect to the public IP of the other host(s).
One of `wireguard_address` (deprecated) or `wireguard_addresses` (recommended) is required as already mentioned. It's the IPs of the interface name defined with `wireguard_interface` variable (`wg0` by default). Every host needs at least one unique VPN IP of course. If you don't set `wireguard_endpoint` the playbook will use the hostname defined in the `vpn` hosts group (the Ansible inventory hostname). If you set `wireguard_endpoint` to `""` (empty string) that peer won't have a endpoint. That means that this host can only access hosts that have a `wireguard_endpoint`. That's useful for clients that don't expose any services to the VPN and only want to access services on other hosts. So if you only define one host with `wireguard_endpoint` set and all other hosts have `wireguard_endpoint` set to `""` (empty string) that basically means you've only clients besides one which in that case is the WireGuard server. The third possibility is to set `wireguard_endpoint` to some hostname. E.g. if you have different hostnames for the private and public DNS of that host and need different DNS entries for that case setting `wireguard_endpoint` becomes handy. Take for example the IP above: `wireguard_address: "10.8.0.101"`. That's a private IP and I've created a DNS entry for that private IP like `host01.i.domain.tld` (`i` for internal in that case). For the public IP I've created a DNS entry like `host01.p.domain.tld` (`p` for public). The `wireguard_endpoint` needs to be a interface that the other members in the `vpn` group can connect to. So in that case I would set `wireguard_endpoint` to `host01.p.domain.tld` because WireGuard normally needs to be able to connect to the public IP of the other host(s).
Here is a litte example for what I use the playbook: I use WireGuard to setup a fully meshed VPN (every host can directly connect to every other host) and run my Kubernetes (K8s) cluster at Hetzner Cloud (but you should be able to use any hoster you want). So the important components like the K8s controller and worker nodes (which includes the pods) only communicate via encrypted WireGuard VPN. Also (as already mentioned) I've two clients. Both have `kubectl` installed and are able to talk to the internal Kubernetes API server by using WireGuard VPN. One of the two clients also exposes a WireGuard endpoint because the Postfix mailserver in the cloud and my internal Postfix needs to be able to talk to each other. I guess that's maybe a not so common use case for WireGuard :D But it shows what's possible. So let me explain the setup which might help you to use this Ansible role.
@ -293,7 +294,8 @@ Ansible host file: `host_vars/controller01.i.domain.tld`
```yaml
---
wireguard_address: "10.8.0.101/24"
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
@ -303,7 +305,8 @@ Ansible host file: `host_vars/controller02.i.domain.tld`:
```yaml
---
wireguard_address: "10.8.0.102/24"
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
@ -313,13 +316,14 @@ Ansible host file: `host_vars/controller03.i.domain.tld`:
```yaml
---
wireguard_address: "10.8.0.103/24"
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
```
I've specified `ansible_python_interpreter` here for every node as the controller nodes use Ubuntu 18.04 which has Python 3 installed by default. `ansible_host` is set to the public DNS of that host. Ansible will use this hostname to connect to the host via SSH. I use the same value also for `wireguard_endpoint` because of the same reason. The WireGuard peers needs to connect to the other peers via a public IP (well at least via a IP that the WireGuard hosts can connect to - that could be of course also a internal IP if it works for you). The `wireguard_address` needs to be unique of course for every host.
I've specified `ansible_python_interpreter` here for every node as the controller nodes use Ubuntu 18.04 which has Python 3 installed by default. `ansible_host` is set to the public DNS of that host. Ansible will use this hostname to connect to the host via SSH. I use the same value also for `wireguard_endpoint` because of the same reason. The WireGuard peers needs to connect to the other peers via a public IP (well at least via a IP that the WireGuard hosts can connect to - that could be of course also a internal IP if it works for you). IPs specified by `wireguard_address` or `wireguard_addresses` needs to be unique of course for every host.
For the Kubernetes worker I've defined the following variables:
@ -327,7 +331,8 @@ Ansible host file: `host_vars/worker01.i.domain.tld`
```yaml
---
wireguard_address: "10.8.0.111/24"
wireguard_addresses:
- "10.8.0.111/24"
wireguard_endpoint: "worker01.p.domain.tld"
wireguard_persistent_keepalive: "30"
ansible_host: "worker01.p.domain.tld"
@ -338,7 +343,8 @@ Ansible host file: `host_vars/worker02.i.domain.tld`:
```yaml
---
wireguard_address: "10.8.0.112/24"
wireguard_addresses:
- "10.8.0.112/24"
wireguard_endpoint: "worker02.p.domain.tld"
wireguard_persistent_keepalive: "30"
ansible_host: "worker02.p.domain.tld"
@ -351,19 +357,21 @@ For my internal server at home (connected via DSL router to the internet) we've
```yaml
---
wireguard_address: "10.8.0.1/24"
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
```
By default the SSH daemon is listening on a different port than 22 on all of my public nodes but internally I use `22` and that's the reason to set `ansible_port: 22` here. Also `ansible_host` is of course a internal IP for that host. The `wireguard_endpoint` value is a dynamic DNS entry. Since my IP at home isn't static I need to run a script every minute at my home server that checks if the IP has changed and if so adjusts my DNS record. I use OVH's DynHost feature to accomplish this but you can use and DynDNS provider you want of course. Also I forward incoming traffic on port `51820/UDP` to my internal server to allow incoming WireGuard traffic. The `wireguard_address` needs to be of course part of our WireGuard subnet.
By default the SSH daemon is listening on a different port than 22 on all of my public nodes but internally I use `22` and that's the reason to set `ansible_port: 22` here. Also `ansible_host` is of course a internal IP for that host. The `wireguard_endpoint` value is a dynamic DNS entry. Since my IP at home isn't static I need to run a script every minute at my home server that checks if the IP has changed and if so adjusts my DNS record. I use OVH's DynHost feature to accomplish this but you can use and DynDNS provider you want of course. Also I forward incoming traffic on port `51820/UDP` to my internal server to allow incoming WireGuard traffic. IPs from `wireguard_address` and `wireguard_addresses` needs to be of course part of our WireGuard subnet.
And finally for my workstation (on which I run all `ansible-playbook` commands):
```yaml
wireguard_address: "10.8.0.2/24"
wireguard_addresses:
- "10.8.0.2/24"
wireguard_endpoint: ""
ansible_connection: local
ansible_become: false
@ -439,11 +447,13 @@ This is a complex example using yaml inventory format:
vpn1:
hosts:
multi:
wireguard_address: 10.9.0.1/32
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_address: 10.9.0.2/32
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
@ -465,10 +475,12 @@ vpn2:
wireguard_interface: wg1
# when using several interface on one host, we must use different ports
wireguard_port: 51821
wireguard_address: 10.9.1.1/32
wireguard_addresses:
- "10.9.1.1/32"
wireguard_endpoint: multi.example.com
another:
wireguard_address: 10.9.1.2/32
wireguard_address:
- "10.9.1.2/32"
wireguard_endpoint: another.example.com
```

@ -6,7 +6,14 @@
[Interface]
# {{ inventory_hostname }}
{% if wireguard_address is defined %}
Address = {{ wireguard_address }}
{% endif %}
{% if wireguard_addresses is defined %}
{% for wg_addr in wireguard_addresses %}
Address = {{ wg_addr }}
{% endfor %}
{% endif %}
PrivateKey = {{ wireguard_private_key }}
ListenPort = {{ wireguard_port }}
{% if wireguard_dns is defined %}
@ -53,7 +60,18 @@ PublicKey = {{hostvars[host].wireguard__fact_public_key}}
{% if hostvars[host].wireguard_allowed_ips is defined %}
AllowedIPs = {{hostvars[host].wireguard_allowed_ips}}
{% else %}
{% if wireguard_address is defined %}
AllowedIPs = {{ hostvars[host].wireguard_address.split('/')[0] }}/32
{% endif %}
{% if wireguard_addresses is defined %}
{% for wg_addr in hostvars[host].wireguard_addresses %}
{% if (wg_addr | ansible.utils.ipv4) %}
AllowedIPs = {{ wg_addr.split('/')[0] }}/32
{% elif (wg_addr | ansible.utils.ipv6) %}
AllowedIPs = {{ wg_addr.split('/')[0] }}/128
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if hostvars[host].wireguard_persistent_keepalive is defined %}
PersistentKeepalive = {{hostvars[host].wireguard_persistent_keepalive}}

Loading…
Cancel
Save