Deploying Wireguard for private mesh networking
Author(s) | Helena Rasche |
OverviewQuestions:Objectives:
What is wireguard?
When is it useful?
Is it right for me?
Requirements:
Setup a wireguard mesh across a few nodes
- slides Slides: Ansible
- tutorial Hands-on: Ansible
- Three or more VMs (they can be tiny, 1 CPU, <1GB RAM)
Time estimation: 60 minutesSupporting Materials:
Published: Sep 21, 2022Last modification: Apr 16, 2023License: Tutorial Content is licensed under Creative Commons Attribution 4.0 International License. The GTN Framework is licensed under MITpurl PURL: https://gxy.io/GTN:T00026version Revision: 3
In this tutorial we will briefly cover what Wireguard is and how you can leverage it for your needs. This will not make you an expert on Wireguard but will give you the tools you need in order to setup a local Wireguard network.
Agenda
What is Wireguard?
Wireguard is a VPN like OpenVPN or IPSec, but instead of the hub and spoke model of those where all traffic must go through a central node, Wireguard creates a mesh network where machines can all talk individually to each other. Wireguard also uses modern encryption only, ensuring your data stays safe.
Is it right for me?
If you have machines that need to talk to each other privately, and you don’t have a better way to do it like a local network team, then yes, it’s a great solution to private, secure, fast networking. It has excellent performance despite the encryption, and is built directly into the kernel.
By using wireguard, you can let services listen only on the wireguard interface, and thus only known and trusted machines can access those services.
Setting up the infrastructure
Hands-on: Configuration files
Create a
ansible.cfg
file (next to your playbook) to configure settings like the inventory file (and save ourselves some typing!), or the Python interpreter to use:--- /dev/null +++ b/ansible.cfg @@ -0,0 +1,6 @@ +[defaults] +interpreter_python = /usr/bin/python3 +inventory = hosts +retry_files_enabled = false +[ssh_connection] +pipelining = true
As mentioned in the “Ubuntu or Debian, CentOS or RHEL?” comment above, if you are using CentOS7 do not set
interpreter_python
inansible.cfg
.Pipelining will make Ansible run faster by significantly reducing the number of new SSH connections that must be opened.
Create the
hosts
inventory file if you have not done so yet, defining an[wireguard]
group with every host you want to be part of the cluster. For each machine, also set a variablewireguard_ip
with an address from192.168.0.0/24
Input: Bashcat hosts
Output: BashYour hostname is probably different:
--- /dev/null +++ b/hosts @@ -0,0 +1,5 @@ +[wireguard] +1-wg.galaxy.training wireguard_ip=192.168.0.1 +2-wg.galaxy.training wireguard_ip=192.168.0.2 +3-wg.galaxy.training wireguard_ip=192.168.0.3 +4-wg.galaxy.training wireguard_ip=192.168.0.4
Wireguard can use any of the private network blocks, here we use 192.168.0.0/16
for familiarity, Tailscale uses 100.0.0.0/8
, which is the Carrier Grade NAT range, and behaves somewhat differently to classic Private Network prefixes.
Writing the playbook
First lets set up the playbook and install Wireguard, without configuring it.
Hands-on: Installing Wireguard
Create and open
wg.yml
which will be our playbook. Add the following:--- /dev/null +++ b/wg.yml @@ -0,0 +1,16 @@ +--- +- hosts: all + become: yes + vars: + wireguard_mask_bits: 24 + wireguard_port: 51871 + tasks: + - name: update packages + apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install wireguard + apt: + name: wireguard + state: present
Run the playbook:
Input: Bashansible-playbook wg.yml
Now we can start configuring it. Wireguard relies on each node having a private/public keypair which is used for encrypting communications between the nodes. By default this uses modern elliptic cryptography which is quite simple and provably secure, wireguard only has ~4k LOC compared to 400k+ LOC for most VPN solutions. Let’s generate those keys now:
Hands-on: Generating a keypair
Edit
wg.yml
and add--- a/wg.yml +++ b/wg.yml @@ -14,3 +14,8 @@ apt: name: wireguard state: present + + - name: Generate Wireguard keypair + shell: wg genkey | tee /etc/wireguard/privatekey | wg pubkey | tee /etc/wireguard/publickey + args: + creates: /etc/wireguard/privatekey
Run the playbook:
Input: Bashansible-playbook wg.yml
Go check out what the keys look like!
Ok, that’s got wireguard setup, but it still isn’t running. As of 2018 or so, systemd added built in support for wireguard and we’ll use that to setup the network devices, and the network itself.
Hands-on: Setting up the network
Edit
wg.yml
and add the following. We want to register the contents of these keys, as they’ll be used to configure our networks later.--- a/wg.yml +++ b/wg.yml @@ -19,3 +19,13 @@ shell: wg genkey | tee /etc/wireguard/privatekey | wg pubkey | tee /etc/wireguard/publickey args: creates: /etc/wireguard/privatekey + + - name: register private key + shell: cat /etc/wireguard/privatekey + register: wireguard_private_key + changed_when: false + + - name: register public key + shell: cat /etc/wireguard/publickey + register: wireguard_public_key + changed_when: false
Again editing
wg.yml
, we’ll setup the network device:--- a/wg.yml +++ b/wg.yml @@ -29,3 +29,28 @@ shell: cat /etc/wireguard/publickey register: wireguard_public_key changed_when: false + + - name: Setup wg0 device + copy: + content: | + [NetDev] + Name=wg0 + Kind=wireguard + Description=WireGuard tunnel wg0 + [WireGuard] + ListenPort={{ wireguard_port }} + PrivateKey={{ wireguard_private_key.stdout }} + {% for peer in groups['all'] %} + {% if peer != inventory_hostname %} + [WireGuardPeer] + PublicKey={{ hostvars[peer].wireguard_public_key.stdout }} + AllowedIPs={{ hostvars[peer].wireguard_ip }}/32 + Endpoint={{ hostvars[peer].inventory_hostname }}:{{ wireguard_port }} + PersistentKeepalive=25 + {% endif %} + {% endfor %} + dest: /etc/systemd/network/99-wg0.netdev + owner: root + group: systemd-network + mode: 0640 + notify: systemd network restart
Here we do a number of things:
- We configure a NetDev, a virtual network device. Systemd supports many types but we’ll use wireguard.
- Next we setup wireguard, specifying a
ListenPort
which is used for all wireguard communication, and specifies our PrivateKey which is used by the tunnel.- For every peer (all other instances than ourselves), we setup a
WireGuardPeer
with that peer’s public key, and which IPs are allowed to connect.Again editing
wg.yml
, we’ll setup the final bit, the network service.--- a/wg.yml +++ b/wg.yml @@ -54,3 +54,16 @@ group: systemd-network mode: 0640 notify: systemd network restart + + - name: Setup wg0 network + copy: + content: | + [Match] + Name=wg0 + [Network] + Address={{ wireguard_ip }}/{{ wireguard_mask_bits }} + dest: /etc/systemd/network/99-wg0.network + owner: root + group: systemd-network + mode: 0640 + notify: systemd network restart
Here we just setup the network, declare the interface name, and the address for it.
And last let’s restart the appropriate services.
--- a/wg.yml +++ b/wg.yml @@ -67,3 +67,10 @@ group: systemd-network mode: 0640 notify: systemd network restart + + handlers: + - name: systemd network restart + service: + name: systemd-networkd + state: restarted + enabled: yes
Run the playbook:
Input: Bashansible-playbook wg.yml
Go check out the network! Try pinging each of the wireguard IPs to see if you can reach each of the machines.