From 260ee1b07ad830b6e60ffac93981b3c5204ed981 Mon Sep 17 00:00:00 2001 From: KemoNine Date: Tue, 4 Aug 2020 22:17:13 +0000 Subject: [PATCH] Initial import --- README.md | 7 + deploy-wireguard.sh | 6 + inventory | 39 ++ roles/wireguard/CHANGELOG.md | 123 +++++++ roles/wireguard/README.md | 336 ++++++++++++++++++ roles/wireguard/defaults/main.yml | 27 ++ roles/wireguard/handlers/main.yml | 28 ++ roles/wireguard/meta/main.yml | 27 ++ roles/wireguard/tasks/main.yml | 133 +++++++ roles/wireguard/tasks/setup-archlinux.yml | 32 ++ roles/wireguard/tasks/setup-centos.yml | 19 + .../wireguard/tasks/setup-debian-raspbian.yml | 93 +++++ .../wireguard/tasks/setup-debian-vanilla.yml | 37 ++ roles/wireguard/tasks/setup-debian.yml | 8 + roles/wireguard/tasks/setup-fedora.yml | 17 + roles/wireguard/tasks/setup-ubuntu.yml | 48 +++ roles/wireguard/templates/wg.conf.j2 | 70 ++++ setup-ansible-host.sh | 4 + setup-ansible.yml | 21 ++ update-software.yml | 16 + wireguard.yml | 5 + 21 files changed, 1096 insertions(+) create mode 100644 README.md create mode 100644 deploy-wireguard.sh create mode 100644 inventory create mode 100644 roles/wireguard/CHANGELOG.md create mode 100644 roles/wireguard/README.md create mode 100644 roles/wireguard/defaults/main.yml create mode 100644 roles/wireguard/handlers/main.yml create mode 100644 roles/wireguard/meta/main.yml create mode 100644 roles/wireguard/tasks/main.yml create mode 100644 roles/wireguard/tasks/setup-archlinux.yml create mode 100644 roles/wireguard/tasks/setup-centos.yml create mode 100644 roles/wireguard/tasks/setup-debian-raspbian.yml create mode 100644 roles/wireguard/tasks/setup-debian-vanilla.yml create mode 100644 roles/wireguard/tasks/setup-debian.yml create mode 100644 roles/wireguard/tasks/setup-fedora.yml create mode 100644 roles/wireguard/tasks/setup-ubuntu.yml create mode 100644 roles/wireguard/templates/wg.conf.j2 create mode 100644 setup-ansible-host.sh create mode 100644 setup-ansible.yml create mode 100644 update-software.yml create mode 100644 wireguard.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb984f9 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Ansible + +Some basic Ansible 'stuff' for managing PiFrames. + +This is very much WIP and nothing in this directory should **NOT** be used unless you plan on submitting patches / fixes / etc. + +For now the goal of this code is to get a basic WireGuard deployment going for the PiFrameFleet area of the project. diff --git a/deploy-wireguard.sh b/deploy-wireguard.sh new file mode 100644 index 0000000..40a7a3a --- /dev/null +++ b/deploy-wireguard.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# https://www.tauceti.blog/post/kubernetes-the-not-so-hard-way-with-ansible-wireguard/ +# https://github.com/githubixx/ansible-role-wireguard + +ansible-playbook -u ansible --private-key /opt/ansible/ssh.key -i inventory ./wireguard.yml diff --git a/inventory b/inventory new file mode 100644 index 0000000..98dfbb1 --- /dev/null +++ b/inventory @@ -0,0 +1,39 @@ +--- +all: + hosts: + frame1: + ansible_host: 10.5.5.177 + dispatcher: + ansible_connection: local +frames: + hosts: + frame1: +wg: + hosts: + frame1: + wireguard_address: 192.168.254.11/32 + dispatcher: + containerized: true + wireguard_containerized: {{ containerized }} + wireguard_remote_directory: "/opt/wireguard" + wireguard_address: 192.168.254.1/32 + wireguard_allowed_ips: "192.168.254.0/24" + wireguard_table: "off" + wireguard_postup: + - ip route add 192.168.254.0/24 via 192.168.254.1 dev wg0 + - iptables -t nat -A PREROUTING -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -A FORWARD -i wg0 -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -A FORWARD -i wg0 -s 192.168.254.0/24 -d 0.0.0.0/0 -j DROP + - iptables -A INPUT -i wg0 -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -A INPUT -i wg0 -s 192.168.254.0/24 -d 0.0.0.0/0 -j DROP + wireguard_postdown: + - ip route del 192.168.254.0/24 via 192.168.254.1 dev wg0 + - iptables -D -t nat -A PREROUTING -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -D -A FORWARD -i wg0 -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -D -A FORWARD -i wg0 -s 192.168.254.0/24 -d 0.0.0.0/0 -j DROP + - iptables -D -A INPUT -i wg0 -s 192.168.254.0/24 -d 192.168.254.0/24 -j ACCEPT + - iptables -D -A INPUT -i wg0 -s 192.168.254.0/24 -d 0.0.0.0/0 -j DROP + vars: + wireguard_port: 51821 + wireguard_endpoint: 10.5.5.246 + wireguard_persistent_keepalive: 30 diff --git a/roles/wireguard/CHANGELOG.md b/roles/wireguard/CHANGELOG.md new file mode 100644 index 0000000..a1a4441 --- /dev/null +++ b/roles/wireguard/CHANGELOG.md @@ -0,0 +1,123 @@ +Changelog +--------- + +**6.3.1** + +- Support Openstack Debian images (contribution by @pallinger) + +**6.3.0** + +- Support Raspbian (contribution by @penguineer) + +**6.2.0** + +- Support Ubuntu 20.04 (Focal Fossa) +- Introduce `wireguard_ubuntu_update_cache` and `wireguard_ubuntu_cache_valid_time` variables to specifiy individual Ubuntu package cache settings. Default values are the same as before. +- As kernel >= 5.6 (and kernel 5.4 in Ubuntu 20.04) now have `wireguard` module included `wireguard-dkms` package is no longer needed in that case. That's why WireGuard package installation is now part of the includes for the specific OS to make it easier to handle various cases. + +**6.1.0** + +- Archlinux: Linux kernel >= 5.6 contains `wireguard` module now. No need to install `wireguard-dkms` anymore in this case. Installations with LTS kernel installs `wireguard-lts` package now instead of `wireguard-dkms`. Installations with kernel <= 5.6 will still install `wireguard-dkms` package. + +**6.0.4** + +- Use the buster-backports repository on Debian Buster (or older), use package standard repositories on sid/bullseye. + standard repositories on sid/bullseye. + + The role no longer adds the unstable _repo_ nor the _apt preference_ for that repo. There is no need to clean the preference and unstable repository, since packages from your release have a higher priority. + + If you remove the apt preference (`/etc/apt/preferences.d/limit-unstable`) updates from `unstable` are accepted by apt. This likely is not what you want and may lead to an unstable state. + + If you want to clean up: + * remove `/etc/apt/preferences.d/limit-unstable` and + * remove `deb http://deb.debian.org/debian/ unstable main` from `/etc/apt/sources.list.d/deb_debian_org_debian.list`. + + The backports repository has a lower priority and does not need an apt preference. + +**6.0.3** + +- If `wg syncconf` command is not available do stop/start service instead of restart (contribution by @cristichiru) + +**6.0.2** + +- Debian: install `gnupg` package instead of `gpg`. (contribution by @zinefer) + +**6.0.1** + +- add shell options to syncconf handler to fail fast in case of error + +**6.0.0** + +- Newer versions of WireGuard (around November 2019) introduced `wg syncconf` subcommand. This has the advantage that changes to the WireGuard configuration can be applied without disturbing existing connections. With this change this role tries to use `wg syncconf` subcommand when available. This even works if you have hosts with older and newer WireGuard versions. + +**5.0.0** + +- `wireguard_(preup|postdown|preup|predown)` settings are now a list. If more `iptables` commands needs to be specified e.g. then this changes makes it more readable. The commands are executed in order as described in [wg-quick.8](https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8). Also see README for more examples. (contribution by @Madic-) + +**4.2.0** + +- Add support for Fedora (contribution by @ties) + + +**4.1.1** + +- Install GPG to be able to import WireGuard key (Debian) + +**4.1.0** + +- Allow to specifiy additional Wireguard interface options: `fwmark`, `mtu`, `table`, `preup` and `predown` (for more information and examples see [wg-quick.8](https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8)) +- Add host comments in Wireguard config file + +**4.0.0** + +- While the changes introduced are backwards compatible in general if you stay with your current settings some variables are no longer needed. So this is partly a breaking change and therefore justifies a new major version. +- Support multiple Wireguard interfaces. See README for examples (contribution by fbourqui) +- Make role stateless: In the previous versions the private and public keys of the Wireguard hosts were stored locally in the directory defined with the `wireguard_cert_directory` variable. This is no longer the case. The variables `wireguard_cert_directory`, `wireguard_cert_owner` and `wireguard_cert_group` are no longer needed and were removed. If you used this role before this release it's safe to remove them from your settings. The directory that was defined with the `wireguard_cert_directory` variable will be kept. While not tested it may enable you to go back to an older version of this role and it should still work (contribution by fbourqui) +- Reminder: `wireguard_cert_directory` default was `~/wireguard/certs`. Public and Private keys where stored on the host running ansible playbook. As a security best practice private keys of all your WireGuard endpoints should not be kept locally. + +**3.2.2** + +- remove unneeded `with_inventory_hostnames` loops (thanks to pierreozoux for initial PR) + +**3.2.1** + +- remove unecessary files (contribution by pierreozoux) + +**3.2.0** + +- add support for RHEL/CentOS (contribution by ahanselka) + +**3.1.0** + +- pass package list directly to some modules by using the new and prefered syntax instead `loop` or `with_items` (contribution by ahanselka) + +**3.0.1** + +- fix address in README + +**3.0.0** + +- support for Debian added (contribution by ties) + +**2.0.1** + +- make Ansible linter happy + +**2.0.0** + +- use correct semantic versioning as described in https://semver.org. Needed for Ansible Galaxy importer as it now insists on using semantic versioning. +- moved changelog entries to separate file +- make Ansible linter happy +- no major changes but decided to start a new major release as versioning scheme changed quite heavily + +**v1.0.2** + +- update README + +**v1.0.1** + +- update README + +**v1.0.0** + +- initial implementation diff --git a/roles/wireguard/README.md b/roles/wireguard/README.md new file mode 100644 index 0000000..0af8db3 --- /dev/null +++ b/roles/wireguard/README.md @@ -0,0 +1,336 @@ +# Fork of https://github.com/githubixx/ansible-role-wireguard.git with some minor tweaks to ensure PiFrameFleet can be provisioned properly + +ansible-role-wireguard +====================== + +This Ansible role is used in my blog series [Kubernetes the not so hard way with Ansible](https://www.tauceti.blog/post/kubernetes-the-not-so-hard-way-with-ansible-wireguard/) but can be used standalone of course. I use WireGuard and this Ansible role to setup a fully meshed VPN between all nodes of my little Kubernetes cluster. This VPN also includes two clients so that I can communicate securly with the Kubernetes API server. Also my Postfix mailserver running as K8s DaemonSet forwards mails to my internal Postfix through WireGuard VPN. + +I used [PeerVPN](https://peervpn.net/) before but that wasn't updated for a while. As I moved my cloud hosts from Scaleway to Hetzner cloud it was a good time to switch the VPN solution ;-) In general PeerVPN still works perfectly fine esp. if you need a easy to setup fully meshed network (where every node is able to talk to all other nodes and even if node `A` should be able to talk to Node `C` via node `B` ;-) ). But PeerVPN needs also lot of CPU resources and throuhput could be better. That's solved with [WireGuard](https://www.wireguard.io/). + +In general WireGuard is a network tunnel (VPN) for IPv4 and IPv6 that uses UDP. If you need more information about [WireGuard](https://www.wireguard.io/) you can find a good introduction here: [Installing WireGuard, the Modern VPN](https://research.kudelskisecurity.com/2017/06/07/installing-wireguard-the-modern-vpn/). + +This role is tested with Ubuntu 18.04 (Bionic Beaver), Ubuntu 20 (Focal Fossa) and Archlinux. Ubuntu 16.04 (Xenial Xerus), Debian 9 (Stretch), Debian 10 (Buster), Fedora 31 (or later) and CentOS 7 might also work or other distributions but haven't tested it (code for this operating systems was submitted by other contributors). If someone tested it let me please know if it works or send a pull request to make it work ;-) + +Versions +-------- + +I tag every release and try to stay with [semantic versioning](http://semver.org). If you want to use the role I recommend to checkout the latest tag. The master branch is basically development while the tags mark stable releases. But in general I try to keep master in good shape too. + +Requirements +------------ + +By default port `51820` (protocol UDP) should be accessable from the outside. But you can adjust the port by changing the variable `wireguard_port`. Also IP forwarding needs to be enabled e.g. via `echo 1 > /proc/sys/net/ipv4/ip_forward `. I decided not to implement this task in this Ansible role. IMHO that should be handled elsewhere. You can use my [ansible-role-harden-linux](https://github.com/githubixx/ansible-role-harden-linux) e.g. Besides changing sysctl entries (which you need to enable IP forwarding) it also manages firewall settings among other things. Nevertheless the `PreUp`, `PreDown`, `PostUp` and `PostDown` hooks may be a good place to do some network related stuff before a WireGuard interface comes up or goes down. + +Changelog +--------- + +see [CHANGELOG.md](https://github.com/githubixx/ansible-role-wireguard/blob/master/CHANGELOG.md) + +Role Variables +-------------- + +These variables can be changed in `group_vars/`: + +``` +# Directory to store WireGuard configuration on the remote hosts +wireguard_remote_directory: "/etc/wireguard" + +# The default port WireGuard will listen if not specified otherwise. +wireguard_port: "51820" + +# The default interface name that wireguard should use if not specified otherwise. +wireguard_interface: "wg0" +``` + +The following variable is mandatory and needs to be configured for every host in `host_vars/`: + +``` +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: + +``` +[Interface] +Address = 10.8.0.2/24 +PrivateKey = .... +ListenPort = 51820 + +[Peer] +PrivateKey = .... +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. + +``` +[Interface] +Address = 10.8.0.2/24 +PrivateKey = .... +ListenPort = 51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 0.0.0.0/0 +Endpoint = controller01.p.domain.tld:51820 +``` + +Now this is basically the same as above BUT now the config says: I want to route EVERY traffic originating from my workstation to the endpoint `controller01.p.domain.tld:51820`. If that endpoint can handle the traffic is of course another thing and it's up to you how you configure the endpoint routing ;-) + +You can specify further optional settings (they don't have a default and won't be set if not specified besides `wireguard_allowed_ips` as already mentioned) also per host in `host_vars/` (or in your Ansible hosts file if you like). The values for the following variables are just examples and no defaults (for more information and examples see [wg-quick.8](https://git.zx2c4.com/WireGuard/about/src/tools/man/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)` are specified as lists. Here are two examples: + +``` +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 +``` + +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). + +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. + +First, here is a part of my Ansible `hosts` file: + +``` +[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 +``` + +As you can see I've three gropus here: `vpn` (all hosts on that will get WireGuard installed), `k8s_controller` (the Kubernetes controller nodes) and `k8s_worker` (the Kubernetes worker nodes). The `i` in the domainname is for `internal`. All the `i.domain.tld` DNS entries have a `A` record that points to the WireGuard IP that we define shortly for every host e.g.: ` controller01.i.domain.tld. IN A 10.8.0.101`. The reason for that is that all Kubernetes components only binds and listen on the WireGuard interface in my setup. And since I need this internal IPs for all my Kubernetes components I specify the internal DNS entries in my Ansible `hosts` file. That way I can use the Ansible inventory hostnames and variables very easy in the playbooks and templates. + +For the Kubernetes controller nodes I've defined the following host variables: + +Ansible host file: `host_vars/controller01.i.domain.tld` +``` +--- +wireguard_address: "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 file: `host_vars/controller02.i.domain.tld`: +``` +--- +wireguard_address: "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 file: `host_vars/controller03.i.domain.tld`: +``` +--- +wireguard_address: "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. + +For the Kubernetes worker I've defined the following variables: + +Ansible host file: `host_vars/worker01.i.domain.tld` +``` +--- +wireguard_address: "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 file: `host_vars/worker02.i.domain.tld`: +``` +--- +wireguard_address: "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 +``` + +As you can see the variables are basically the same as the controller nodes have with one exception: `wireguard_persistent_keepalive: "30"`. My worker nodes (at Hetzner Cloud) and my internal server (my server at home) are connected because I've running Postfix at my cloud nodes and the external Postfix server forwards the received mails to my internal server (and vice versa). I needed the keepalive setting because from time to time the cloud instances and the internal server lost connection and this setting solved the problem. The reason for this is of course because my internal server is behind NAT and the firewall/router must keep the NAT/firewall mapping valid (NAT and Firewall Traversal Persistence). + +For my internal server at home (connected via DSL router to the internet) we've this configuration: + +``` +--- +wireguard_address: "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. + +And finally for my workstation (on which I run all `ansible-playbook` commands): + +``` +wireguard_address: "10.8.0.2/24" +wireguard_endpoint: "" +ansible_connection: local +ansible_become: false +``` + +As you can see `wireguard_endpoint: ""` is a empty string here. That means the Ansible role won't set an endpoint for my workstation. Since there is no need for the other hosts to connect to my workstation it doesn't makes sense to have a endpoint defined. So in this case I can access all hosts defined in the Ansible group `vpn` from my workstation but not the other way round. So the resulting WireGuard config for my workstation looks like this: + +``` +[Interface] +Address = 10.8.0.2/24 +PrivateKey = .... +ListenPort = 51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.101/32 +Endpoint = controller01.p.domain.tld:51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.102/32 +Endpoint = controller02.p.domain.tld:51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.103/32 +Endpoint = controller03.p.domain.tld:51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.111/32 +PersistentKeepalive = 30 +Endpoint = worker01.p.domain.tld:51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.112/32 +PersistentKeepalive = 30 +Endpoint = worker02.p.domain.tld:51820 + +[Peer] +PrivateKey = .... +AllowedIPs = 10.8.0.1/32 +PersistentKeepalive = 30 +Endpoint = server.at.home.p.domain.tld:51820 +``` + +The other WireGuard config files (`wg0.conf` by default) looks similar but of course `[Interface]` includes the config of that specific host and the `[Peer]` entries lists the config of the other hosts. + +Example Playbook +---------------- + +``` +- hosts: vpn + roles: + - wireguard +``` + +Example Inventory using two different WireGuard interfaces on host "multi" +-------------------------------------------------------------------------- + +This is a complex example using yaml inventory format: + +``` +vpn1: + hosts: + multi: + wireguard_address: 10.9.0.1/32 + wireguard_allowed_ips: "10.9.0.1/32, 192.168.2.0/24" + wireguard_endpoint: multi.exemple.com + nated: + wireguard_address: 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.exemple.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: + # Use a different name, and define ansible_host, to avoid mixing of vars without + # needing to prefix vars with interface name. + multi-wg1: + ansible_host: multi + 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_endpoint: multi.exemple.com + another: + wireguard_address: 10.9.1.2/32 + wireguard_endpoint: another.exemple.com +``` + +Playbooks +--------- + +``` +- hosts: vpn1 + roles: + - wireguard +``` + +``` +- hosts: vpn2 + roles: + - wireguard +``` + +License +------- + +GNU GENERAL PUBLIC LICENSE Version 3 + +Author Information +------------------ + +[http://www.tauceti.blog](http://www.tauceti.blog) diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml new file mode 100644 index 0000000..4101634 --- /dev/null +++ b/roles/wireguard/defaults/main.yml @@ -0,0 +1,27 @@ +--- +####################################### +# General settings +####################################### + +# Directory to store WireGuard configuration on the remote hosts +wireguard_remote_directory: "/etc/wireguard" + +# The default port WireGuard will listen if not specified otherwise. +wireguard_port: "51820" + +# The default interface name that wireguard should use if not specified otherwise. +wireguard_interface: "wg0" + +# Whether or not WireGuard is running in a container +wireguard_containerized: false + + +####################################### +# Settings only relevant for Ubuntu +####################################### + +# Set to "false" if package cache should not be updated +wireguard_ubuntu_update_cache: "true" + +# Set package cache valid time +wireguard_ubuntu_cache_valid_time: "3600" diff --git a/roles/wireguard/handlers/main.yml b/roles/wireguard/handlers/main.yml new file mode 100644 index 0000000..cd093d7 --- /dev/null +++ b/roles/wireguard/handlers/main.yml @@ -0,0 +1,28 @@ +--- +- name: restart wireguard + service: + name: "wg-quick@{{ wireguard_interface }}" + state: "{{ item }}" + loop: + - stopped + - started + when: not wg_syncconf and not wireguard_containerized + listen: "reconfigure wireguard" + +- name: syncconf wireguard + shell: | + set -o errexit + set -o pipefail + set -o nounset + systemctl is-active wg-quick@wg-quick@{{ wireguard_interface|quote }} || systemctl start wg-quick@{{ wireguard_interface|quote }} + wg syncconf {{ wireguard_interface|quote }} <(wg-quick strip /etc/wireguard/{{ wireguard_interface|quote }}.conf) + exit 0 + args: + executable: "/bin/bash" + when: wg_syncconf and not wireguard_containerized + listen: "reconfigure wireguard" + +- name: restart wireguard (container) + command: /usr/bin/s6-svc -r /var/run/s6/services/wireguard + when: wireguard_containerized + listen: "reconfigure wireguard" diff --git a/roles/wireguard/meta/main.yml b/roles/wireguard/meta/main.yml new file mode 100644 index 0000000..8133ea6 --- /dev/null +++ b/roles/wireguard/meta/main.yml @@ -0,0 +1,27 @@ +galaxy_info: + author: Robert Wimmer + description: Installs Wireguard incl. systemd integration + license: GPLv3 + min_ansible_version: 2.5 + platforms: + - name: ArchLinux + - name: Ubuntu + versions: + - bionic + - focal + - name: Debian + versions: + - stretch + - buster + - name: EL + versions: + - 7 + - name: Fedora + versions: + - 31 + galaxy_tags: + - networking + - security + - linux + - vpn + - wireguard diff --git a/roles/wireguard/tasks/main.yml b/roles/wireguard/tasks/main.yml new file mode 100644 index 0000000..81da6db --- /dev/null +++ b/roles/wireguard/tasks/main.yml @@ -0,0 +1,133 @@ +--- +- name: Gather instance facts + setup: + +- include_tasks: "setup-{{ ansible_distribution|lower }}.yml" + when: not wireguard_containerized + +- name: Enable WireGuard kernel module + modprobe: + name: wireguard + state: present + register: wireguard_module_enabled + until: wireguard_module_enabled is succeeded + retries: 10 + delay: 10 + failed_when: wireguard_module_enabled is failure + tags: + - wg-install + +- name: Set WireGuard IP (without mask) + set_fact: + wireguard_ip: "{{ wireguard_address.split('/')[0] }}" + +- name: Register if config/private key already exists on target host + stat: + path: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + register: config_file_stat + tags: + - wg-generate-keys + - wg-config + +- name: Get wg subcommands + command: "wg --help" + register: wg_subcommands + changed_when: false + +- name: Set default value for wg_syncconf variable (assume wg syncconf subcommand not available) + set_fact: + wg_syncconf: false + +- name: Check if wg syncconf subcommand is available + set_fact: + wg_syncconf: true + when: wg_subcommands.stdout | regex_search('syncconf:') + +- name: Show syncconf subcommand status + debug: + var: wg_syncconf + +- block: + - name: Generate WireGuard private key + command: "wg genkey" + register: wg_private_key_result + changed_when: false + tags: + - wg-generate-keys + + - name: Set private key fact + set_fact: + private_key: "{{ wg_private_key_result.stdout }}" + tags: + - wg-generate-keys + when: not config_file_stat.stat.exists + +- block: + - name: Read WireGuard config file + slurp: + src: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + register: wg_config + tags: + - wg-config + + - name: Set private key fact + set_fact: + private_key: "{{ wg_config['content'] | b64decode | regex_findall('PrivateKey = (.*)') | first }}" + tags: + - wg-config + when: config_file_stat.stat.exists + +- name: Derive WireGuard public key + shell: "echo '{{ private_key }}' | wg pubkey" # noqa 306 + register: wg_public_key_result + changed_when: false + tags: + - wg-config + +- name: Set public key fact + set_fact: + public_key: "{{ wg_public_key_result.stdout }}" + tags: + - wg-config + +- name: Create WireGuard configuration directory + file: + dest: "{{ wireguard_remote_directory }}" + state: directory + mode: 0700 + tags: + - wg-config + +- name: Generate WireGuard configuration file + template: + src: wg.conf.j2 + dest: "{{ wireguard_remote_directory }}/{{ wireguard_interface }}.conf" + owner: root + group: root + mode: 0600 + tags: + - wg-config + notify: + - reconfigure wireguard + +- name: Check if reload-module-on-update is set + stat: + path: "{{ wireguard_remote_directory }}/.reload-module-on-update" + register: reload_module_on_update + tags: + - wg-config + +- name: Set WireGuard reload-module-on-update + file: + dest: "{{ wireguard_remote_directory }}/.reload-module-on-update" + state: touch + when: not reload_module_on_update.stat.exists + tags: + - wg-config + +- name: Start and enable WireGuard service + service: + name: "wg-quick@{{ wireguard_interface }}" + state: started + enabled: yes + when: not wireguard_containerized diff --git a/roles/wireguard/tasks/setup-archlinux.yml b/roles/wireguard/tasks/setup-archlinux.yml new file mode 100644 index 0000000..8d34575 --- /dev/null +++ b/roles/wireguard/tasks/setup-archlinux.yml @@ -0,0 +1,32 @@ +--- +- name: (Archlinux) Install wireguard-lts package + pacman: + name: "{{ item.name }}" + state: "{{ item.state }}" + with_items: + - { name: wireguard-dkms, state: absent } + - { name: wireguard-lts, state: present } + become: yes + tags: + - wg-install + when: + - ansible_kernel is match(".*-lts$") + - ansible_kernel is version('5.6', '<') + +- name: (Archlinux) Install wireguard-dkms package + pacman: + name: wireguard-dkms + state: present + become: yes + tags: + - wg-install + when: + - not ansible_kernel is match(".*-lts$") + - ansible_kernel is version('5.6', '<') + +- name: (Archlinux) Install wireguard-tools package + pacman: + name: wireguard-tools + state: present + tags: + - wg-install diff --git a/roles/wireguard/tasks/setup-centos.yml b/roles/wireguard/tasks/setup-centos.yml new file mode 100644 index 0000000..4a17708 --- /dev/null +++ b/roles/wireguard/tasks/setup-centos.yml @@ -0,0 +1,19 @@ +--- +- name: (CentOS) Add WireGuard repository + get_url: + url: https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo + dest: /etc/yum.repos.d/wireguard.repo + +- name: (CentOS) Install EPEL repository + yum: + name: epel-release + update_cache: yes + +- name: (CentOS) Install wireguard packages + yum: + name: + - "wireguard-dkms" + - "wireguard-tools" + state: present + tags: + - wg-install diff --git a/roles/wireguard/tasks/setup-debian-raspbian.yml b/roles/wireguard/tasks/setup-debian-raspbian.yml new file mode 100644 index 0000000..8e7214b --- /dev/null +++ b/roles/wireguard/tasks/setup-debian-raspbian.yml @@ -0,0 +1,93 @@ +--- + +- name: (Raspbian) Install GPG - required to add wireguard key + apt: + name: gnupg + state: present + +- name: (Raspbian) Add Debian repository key + apt_key: + keyserver: "keyserver.ubuntu.com" + id: "04EE7237B7D453EC" + state: present + when: ansible_lsb.id == "Raspbian" + tags: + - wg-install + +- name: (Raspbian) Add Debian Unstable repository for WireGuard + apt_repository: + repo: "deb http://deb.debian.org/debian unstable main" + state: present + update_cache: yes + tags: + - wg-install + +- name: (Raspbian) Install latest kernel + apt: + name: + - "raspberrypi-kernel" + state: latest + register: kernel_update + tags: + - wg-install + +- name: (Raspbian) Reboot after kernel update (Ansible >= 2.8) + reboot: + search_paths: ['/lib/molly-guard', '/usr/sbin'] + when: + - ansible_version.full is version('2.8.0', '>=') + - kernel_update is changed + tags: + - wg-install + +- name: (Raspbian) Check if molly-guard is installed (Ansible < 2.8) + stat: + path: /lib/molly-guard/ + register: molly_guard + +- name: (Raspbian) Reboot after kernel update (Ansible < 2.8, no molly-guard) + reboot: + when: + - ansible_version.full is version('2.8.0', '<') + - kernel_update is changed + - not molly_guard.stat.exists + tags: + - wg-install + +- name: (Raspbian) Reboot after kernel update (Ansible < 2.8, with molly-guard) + command: /lib/molly-guard/shutdown -r now + async: 1 + poll: 0 + ignore_unreachable: yes + when: + - ansible_version.full is version('2.8.0', '<') + - kernel_update is changed + - molly_guard.stat.exists + tags: + - wg-install + +- name: (Raspbian) Waiting for host to be available (Ansible < 2.8, with molly-guard) + wait_for_connection: + when: + - ansible_version.full is version('2.8.0', '<') + - kernel_update is changed + - molly_guard.stat.exists + tags: + - wg-install + +- name: (Raspbian) Install latest kernel headers to compile Wireguard with DKMS + apt: + name: + - "raspberrypi-kernel-headers" + state: latest + tags: + - wg-install + +- name: (Raspbian) Install wireguard packages + apt: + name: + - "wireguard-dkms" + - "wireguard-tools" + state: present + tags: + - wg-install diff --git a/roles/wireguard/tasks/setup-debian-vanilla.yml b/roles/wireguard/tasks/setup-debian-vanilla.yml new file mode 100644 index 0000000..0b6aa0b --- /dev/null +++ b/roles/wireguard/tasks/setup-debian-vanilla.yml @@ -0,0 +1,37 @@ +--- +- name: (Debian) Install GPG - required to add wireguard key + apt: + name: gnupg + state: present + +- name: (Debian) Add WireGuard repository on buster or earlier + apt_repository: + repo: "deb http://deb.debian.org/debian buster-backports main" + state: present + update_cache: yes + when: ansible_distribution_version | int <= 10 + tags: + - wg-install + +- name: (Debian) Get architecture + command: "dpkg --print-architecture" + register: dpkg_arch + changed_when: False + +- set_fact: + kernel_header_version: "{{ ('-cloud-' in ansible_kernel) | ternary(ansible_kernel,dpkg_arch.stdout) }}" + +- name: (Debian) Install kernel headers to compile Wireguard with DKMS + apt: + name: + - "linux-headers-{{ kernel_header_version }}" + state: present + +- name: (Debian) Install wireguard packages + apt: + name: + - "wireguard-dkms" + - "wireguard-tools" + state: present + tags: + - wg-install diff --git a/roles/wireguard/tasks/setup-debian.yml b/roles/wireguard/tasks/setup-debian.yml new file mode 100644 index 0000000..62515ad --- /dev/null +++ b/roles/wireguard/tasks/setup-debian.yml @@ -0,0 +1,8 @@ +--- + +- include_tasks: "setup-debian-raspbian.yml" + when: ansible_lsb.id == "Raspbian" + register: raspbian_setup + +- include_tasks: "setup-debian-vanilla.yml" + when: raspbian_setup is skipped diff --git a/roles/wireguard/tasks/setup-fedora.yml b/roles/wireguard/tasks/setup-fedora.yml new file mode 100644 index 0000000..4561dba --- /dev/null +++ b/roles/wireguard/tasks/setup-fedora.yml @@ -0,0 +1,17 @@ +--- +- name: (Fedora) Add wireguard COPR + yum_repository: + name: "jdoss-wireguard" + description: "Copr repo for wireguard owned by jdoss" + baseurl: "https://copr-be.cloud.fedoraproject.org/results/jdoss/wireguard/fedora-$releasever-$basearch/" + gpgkey: "https://copr-be.cloud.fedoraproject.org/results/jdoss/wireguard/pubkey.gpg" + gpgcheck: yes + +- name: (Fedora) Install wireguard packages + yum: + name: + - "wireguard-dkms" + - "wireguard-tools" + state: present + tags: + - wg-install diff --git a/roles/wireguard/tasks/setup-ubuntu.yml b/roles/wireguard/tasks/setup-ubuntu.yml new file mode 100644 index 0000000..9df682e --- /dev/null +++ b/roles/wireguard/tasks/setup-ubuntu.yml @@ -0,0 +1,48 @@ +--- +- name: (Ubuntu) Update APT package cache + apt: + update_cache: "{{ wireguard_ubuntu_update_cache }}" + cache_valid_time: "{{ wireguard_ubuntu_cache_valid_time }}" + tags: + - wg-install + +- block: + - name: (Ubuntu) Install support packages needed for Wireguard (for Ubuntu < 19.10) + package: + name: "{{ packages }}" + state: present + vars: + packages: + - software-properties-common + - linux-headers-{{ ansible_kernel }} + tags: + - wg-install + + - name: (Ubuntu) Add WireGuard repository (for Ubuntu < 19.10) + apt_repository: + repo: "ppa:wireguard/wireguard" + state: present + update_cache: yes + tags: + - wg-install + + - name: (Ubuntu) Install wireguard packages (for Ubuntu < 19.10) + apt: + name: + - "wireguard-dkms" + - "wireguard-tools" + state: present + tags: + - wg-install + when: + - ansible_lsb.major_release is version('19.10', '<') + +- block: + - name: (Ubuntu) Install wireguard-tools package (for Ubuntu > 19.04) + apt: + name: "wireguard-tools" + state: present + tags: + - wg-install + when: + - ansible_lsb.major_release is version('19.04', '>') diff --git a/roles/wireguard/templates/wg.conf.j2 b/roles/wireguard/templates/wg.conf.j2 new file mode 100644 index 0000000..3b2ade2 --- /dev/null +++ b/roles/wireguard/templates/wg.conf.j2 @@ -0,0 +1,70 @@ +#jinja2: lstrip_blocks:"True",trim_blocks:"True" +[Interface] +# {{ inventory_hostname }} +Address = {{hostvars[inventory_hostname].wireguard_address}} +PrivateKey = {{private_key}} +ListenPort = {{wireguard_port}} +{% if hostvars[inventory_hostname].wireguard_dns is defined %} +DNS = {{hostvars[inventory_hostname].wireguard_dns}} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_fwmark is defined %} +FwMark = {{hostvars[inventory_hostname].wireguard_fwmark}} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_mtu is defined %} +MTU = {{hostvars[inventory_hostname].wireguard_mtu}} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_table is defined %} +Table = {{hostvars[inventory_hostname].wireguard_table}} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_preup is defined %} +{% for wg_preup in hostvars[inventory_hostname].wireguard_preup %} +PreUp = {{ wg_preup }} +{% endfor %} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_predown is defined %} +{% for wg_predown in hostvars[inventory_hostname].wireguard_predown %} +PreDown = {{ wg_predown }} +{% endfor %} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_postup is defined %} +{% for wg_postup in hostvars[inventory_hostname].wireguard_postup %} +PostUp = {{ wg_postup }} +{% endfor %} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_postdown is defined %} +{% for wg_postdown in hostvars[inventory_hostname].wireguard_postdown %} +PostDown = {{ wg_postdown }} +{% endfor %} +{% endif %} +{% if hostvars[inventory_hostname].wireguard_save_config is defined %} +SaveConfig = true +{% endif %} +{% for host in ansible_play_hosts_all %} +{% if host != inventory_hostname %} + +[Peer] +# {{ host }} +PublicKey = {{hostvars[host].public_key}} +{% if hostvars[host].wireguard_allowed_ips is defined %} +AllowedIPs = {{hostvars[host].wireguard_allowed_ips}} +{% else %} +AllowedIPs = {{hostvars[host].wireguard_ip}}/32 +{% endif %} +{% if hostvars[host].wireguard_persistent_keepalive is defined %} +PersistentKeepalive = {{hostvars[host].wireguard_persistent_keepalive}} +{% endif %} +{% if hostvars[host].wireguard_port is defined and hostvars[host].wireguard_port is number %} +{% if hostvars[host].wireguard_endpoint is defined and hostvars[host].wireguard_endpoint != "" %} +Endpoint = {{hostvars[host].wireguard_endpoint}}:{{hostvars[host].wireguard_port}} +{% else %} +Endpoint = {{host}}:{{hostvars[host].wireguard_port}} +{% endif %} +{% elif hostvars[host].wireguard_endpoint is defined and hostvars[host].wireguard_endpoint != "" %} +Endpoint = {{hostvars[host].wireguard_endpoint}}:{{wireguard_port}} +{% elif hostvars[host].wireguard_endpoint == "" %} +# No endpoint defined for this peer +{% else %} +Endpoint = {{host}}:{{wireguard_port}} +{% endif %} +{% endif %} +{% endfor %} diff --git a/setup-ansible-host.sh b/setup-ansible-host.sh new file mode 100644 index 0000000..7e8cbbc --- /dev/null +++ b/setup-ansible-host.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +ssh $2@$1 'echo Just ensuring the ssh key is accepted ahead of configuration' +ansible-playbook ./setup-ansible.yml -i $1, -u $2 -k diff --git a/setup-ansible.yml b/setup-ansible.yml new file mode 100644 index 0000000..5cee6bc --- /dev/null +++ b/setup-ansible.yml @@ -0,0 +1,21 @@ +--- + - hosts: all + become: yes + tasks: + - name: Add ansible user to PiFrame + user: + name: ansible + groups: + - sudo + shell: /bin/bash + - name: Add ansible ssh key as authorized key + authorized_key: + user: ansible + key: "{{ lookup('file', '/opt/ansible/ssh.key.pub') }}" + - name: Setup ansible with sudoers access + copy: + dest: /etc/sudoers.d/ansible + mode: '0600' + owner: root + group: root + content: ansible ALL=(ALL) NOPASSWD:ALL diff --git a/update-software.yml b/update-software.yml new file mode 100644 index 0000000..a29c7eb --- /dev/null +++ b/update-software.yml @@ -0,0 +1,16 @@ +--- + - hosts: all + become: yes + tasks: + - name: Refresh package list + apt: + cache_valid_time: 3600 + - name: Update installed packages + apt: + upgrade: dist + - name: Update restic + command: restic self-update + - name: /usr/bin/rclone rclone + shell: curl https://rclone.org/install.sh | bash + - name: Update FileBrowser + shell: curl -fsSL https://filebrowser.org/get.sh | bash diff --git a/wireguard.yml b/wireguard.yml new file mode 100644 index 0000000..474b218 --- /dev/null +++ b/wireguard.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + become: yes + roles: + - wireguard