From b15d869de6a7844b3a1f652de21a4abccd0701cd Mon Sep 17 00:00:00 2001 From: jawher <jawher.moussa@mirakl.com> Date: Thu, 25 Mar 2021 12:15:35 +0100 Subject: [PATCH] Point of inception --- Makefile | 7 +++ README.md | 111 +++++++++++++++++++++++++++++++++++++ inventories/inventory.yml | 33 +++++++++++ ping.yml | 7 +++ templates/systemd.netdev | 19 +++++++ templates/systemd.network | 5 ++ wireguard.yml | 114 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 296 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 inventories/inventory.yml create mode 100644 ping.yml create mode 100644 templates/systemd.netdev create mode 100644 templates/systemd.network create mode 100644 wireguard.yml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b38f92 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +INVENTORY = inventory + +apply: + ansible-playbook -i "inventories/${INVENTORY}.yml" "wireguard.yml" + +test: + ansible-playbook -i "inventories/${INVENTORY}.yml" "ping.yml" diff --git a/README.md b/README.md new file mode 100644 index 0000000..0867c21 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# Multi server Wireguard mesh with ansible + +A playbook which given an inventory file with: + +* a list of hosts + * for each host a `wireguard_ip` variable with the desired host (private) Wireguard IP +* `wireguard_mask_bits` variable with the number of the wireguard (private) network mask bits +* `wireguard_port` variable with the UDP port to use + +will: + +* install wireguard in all hosts +* generate public/private key pairs in all hosts +* generate the pre-shared keys for all host pairs +* create a `wg0` virtual network device and a `wg0` network + +optionally, when the `ufw_enabled` variable is set to `true`: + +* enable ufw on all hosts +* reject everything by default +* allow ssh protocol from all sources +* allow traffic from all the inventory wireguard IPs + +More details and explanation can be found in this blog post: https://jawher.me/wireguard-ansible-systemd-ubuntu/ + +## Example + +In this example, we'll create 3 Hetzner cloud CX11 servers (~3€/month) using [Hetzner's cli](https://github.com/hetznercloud/cli), +1 in each of their 3 datacenters (Nuremberg, Falkenstein & Helsinki): + +```shell +env_id=wireguard-test +server_type=cx11 +image=ubuntu-20.04 + +args=() + +for k in $(hcloud ssh-key list -o=noheader -ocolumns=name); do + args+=("--ssh-key=$k") +done + +for datacenter in nbg1-dc3 fsn1-dc14 hel1-dc2; do + hcloud server create "${args[@]}" \ + --datacenter="${datacenter}" \ + --type="${server_type}" \ + --image="${image}" \ + --label=env="${env_id}" \ + --name="${env_id}-${datacenter}" +done +``` + + +### Inventory + +Next you need to prepapre an inventory file with the 3 servers we created in `inventories/inventory.yml`: + +Run `hcloud server list -l env=wireguard-test`: + +``` +ID NAME STATUS IPV4 IPV6 DATACENTER +10889123 wireguard-test-nbg1-dc3 running xxx.xx.xxx.xx 2a01:xxxx:xxxx:xxxx::/64 nbg1-dc3 +10889126 wireguard-test-fsn1-dc14 running xxx.xx.xxx.xxx 2a01:xxxx:xxxx:xxxx::/64 fsn1-dc14 +10889127 wireguard-test-hel1-dc2 running xx.xxx.xx.xxx 2a01:xxxx:xxxx:xxxx::/64 hel1-dc2 +``` + +And use the server names and IPv4s to build your inventory: + +```yml +all: + hosts: + + host1: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host1_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.1 + + host2: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host2_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.2 + + host3: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host3_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.3 + + vars: + ansible_become_method: su + + wireguard_mask_bits: 24 + wireguard_port: 51871 +``` + +### Apply + +Run `make apply` + +### Test connectivity + +Run `make test`, which will perform ping tests between the 3 servers using their wireguard private IPs. + +You could also ssh to each/any host and run `ping` manually if you prefer. diff --git a/inventories/inventory.yml b/inventories/inventory.yml new file mode 100644 index 0000000..4822d98 --- /dev/null +++ b/inventories/inventory.yml @@ -0,0 +1,33 @@ +all: + hosts: + + host1: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host1_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.1 + + host2: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host2_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.2 + + host3: + pipelining: true + ansible_ssh_user: root + ansible_host: "$host3_public_ip" + ansible_ssh_port: 22 + + wireguard_ip: 192.168.0.3 + + vars: + ansible_become_method: su + + wireguard_mask_bits: 24 + wireguard_port: 51871 + diff --git a/ping.yml b/ping.yml new file mode 100644 index 0000000..942c42a --- /dev/null +++ b/ping.yml @@ -0,0 +1,7 @@ +--- +- hosts: all + gather_facts: yes + tasks: + - name: ping + command: "ping -c3 {{ hostvars[item].wireguard_ip }}" + with_items: "{{ groups['all'] }}" diff --git a/templates/systemd.netdev b/templates/systemd.netdev new file mode 100644 index 0000000..7d52361 --- /dev/null +++ b/templates/systemd.netdev @@ -0,0 +1,19 @@ +[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 }} +PresharedKey={{ wireguard_preshared_keys[peer] if inventory_hostname < peer else hostvars[peer].wireguard_preshared_keys[inventory_hostname] }} +AllowedIPs={{ hostvars[peer].wireguard_ip }}/32 +Endpoint={{ hostvars[peer].ansible_host }}:{{ wireguard_port }} +{% endif %} +{% endfor %} diff --git a/templates/systemd.network b/templates/systemd.network new file mode 100644 index 0000000..4a584ff --- /dev/null +++ b/templates/systemd.network @@ -0,0 +1,5 @@ +[Match] +Name=wg0 + +[Network] +Address={{ wireguard_ip }}/{{ wireguard_mask_bits }} diff --git a/wireguard.yml b/wireguard.yml new file mode 100644 index 0000000..5ad3aff --- /dev/null +++ b/wireguard.yml @@ -0,0 +1,114 @@ +--- +- hosts: all + any_errors_fatal: true + gather_facts: yes + tasks: + - name: update packages + apt: + update_cache: yes + cache_valid_time: 3600 + become: yes + + - name: Allow SSH in UFW + ufw: + rule: allow + port: "{{ ansible_ssh_port }}" + proto: tcp + become: yes + when: ufw_enabled + + - name: Set ufw logging + ufw: + logging: "on" + become: yes + when: ufw_enabled + + - name: inter-node Wireguard UFW connectivity + ufw: + rule: allow + src: "{{ hostvars[item].wireguard_ip }}" + with_items: "{{ groups['all'] }}" + become: yes + when: ufw_enabled and item != inventory_hostname + + - name: Reject everything and enable UFW + ufw: + state: enabled + policy: reject + log: yes + become: yes + when: ufw_enabled + + - name: Install wireguard + apt: + name: wireguard + state: present + become: yes + + - name: Generate Wireguard keypair + shell: wg genkey | tee /etc/wireguard/privatekey | wg pubkey | tee /etc/wireguard/publickey + args: + creates: /etc/wireguard/privatekey + become: yes + + - name: register private key + shell: cat /etc/wireguard/privatekey + register: wireguard_private_key + changed_when: false + become: yes + + - name: register public key + shell: cat /etc/wireguard/publickey + register: wireguard_public_key + changed_when: false + become: yes + + - name: generate Preshared keyskeypair + shell: "wg genpsk > /etc/wireguard/psk-{{ item }}" + args: + creates: "/etc/wireguard/psk-{{ item }}" + when: inventory_hostname < item + with_items: "{{ groups['all'] }}" + become: yes + + - name: register preshared key + shell: "cat /etc/wireguard/psk-{{ item }}" + register: wireguard_preshared_key + changed_when: false + when: inventory_hostname < item + with_items: "{{ groups['all'] }}" + become: yes + + - name: massage preshared keys + set_fact: "wireguard_preshared_keys={{ wireguard_preshared_keys|default({}) | combine( {item.item: item.stdout} ) }}" + when: item.skipped is not defined + with_items: "{{ wireguard_preshared_key.results }}" + become: yes + + - name: Setup wg0 device + template: + src: ./templates/systemd.netdev + dest: /etc/systemd/network/99-wg0.netdev + owner: root + group: systemd-network + mode: 0640 + become: yes + notify: systemd network restart + + - name: Setup wg0 network + template: + src: ./templates/systemd.network + dest: /etc/systemd/network/99-wg0.network + owner: root + group: systemd-network + mode: 0640 + become: yes + notify: systemd network restart + + handlers: + - name: systemd network restart + service: + name: systemd-networkd + state: restarted + enabled: yes + become: yes