How I use Wireguard


A few months ago I signed up for the free-tier of Oracle cloud to give it a shot, because I needed VPN egress from a Canadian datacenter in order to access some Canada-only services, god save the queen. My bank for example is stuck in the stone-age and would constantly hassle me for SMS 2FA which is a joke.

If you want to see how I deploy Wireguard on an arbitrary VPS, and whether to tax your sanity with Oracle cloud then read on.

Deploying wireguard was mostly easy, but as with every cloud provider out there they opt to name and structure all their services completely differently so I had to waste my time learning their snowflake stack. What is Oracle linux? Hell if I know or care.

Relevant variables

There are a couple of values you need from your vps. The rest are up to you. These key generation steps should be in a playbook too. Maybe later. You’ll need keypairs for each server and client(s).

$ umask 077 && wg genkey | tee privatekey | wg pubkey > publickey

~/projects/gwt/host_vars/secret_oracle_tunnel.yml

---
# ipv4 on which your VPS is reachable from the internet. 
# In Oracle's case it was NOT on the ens3 subnet, but still routed.
external_server_ipv4: ... 

# Could be eth0 or something else. Check with ip a.
public_network_interface: ens3 

# Subnet on ens3 that is used for external connection
public_subnet: 10.0.0.0/16 

# Up to you
wg_subnet_ipv4: 192.168.178.0/24 
wg_subnet_ipv6: fd42:42:42::/64
wg_port: 55107
wg_interface: wg0
wg_server_ipv4: 192.168.178.1
wg_server_ipv6: fd42:42:42::1
wg_server_private_key: "{{ vault_wg_oracle_server_private_key }}"
wg_server_public_key: "{{ vault_wg_oracle_server_public_key }}"

Firewall

Open port 55107 on the VPS.

Ansible playbooks for wg0.conf and client.conf

~/projects/gwt/wg-oracle-server.yml


---
- hosts: secret_oracle_tunnel
  gather_facts: false
  become: true
  tasks: 
    - name: Setup a wireguard server
      include_role: 
        name: wireguard
        tasks_from: server

~/projects/gwt/roles/wireguard/tasks/server.yml

---

- name: Update apt cache
  ansible.builtin.apt:
    update_cache: true
    cache_valid_time: 600
  changed_when: false

- name: Install packages
  ansible.builtin.apt:
    name:
      - wireguard
    state: present

- name: Create helpers directory
  ansible.builtin.file:
    path: /etc/wireguard/helpers
    state: directory
    owner: root
    group: root
    mode: "770"

- name: Copy NAT helper scripts
  ansible.builtin.template:
    src: "{{ item }}.j2"
    dest: "/etc/wireguard/helpers/{{ item }}"
    mode: "770"
  loop:
    - add-nat-rules.sh
    - remove-nat-rules.sh

- name: Copy wg0.conf 
  ansible.builtin.template:
    src: "wg0.conf.j2"
    dest: "/etc/wireguard/wg0.conf"
    mode: "770"
  notify: 
    - enable systemd daemon
    - restart wireguard interface

- name: Enable packet forwarding between internet NIC and wg0 
  become: true
  ansible.builtin.lineinfile:
    dest: /etc/sysctl.conf
    state: present
    line: "{{ item }}"
  loop:
    - net.ipv4.ip_forward=1
    - net.ipv6.conf.all.forwarding=1
  notify: reload sysctl config

~/projects/gwt/roles/wireguard/templates/wg0.conf.j2

[Interface]
Address = {{ wg_server_ipv4 }}/24, {{ wg_server_ipv6 }}/64
ListenPort = {{ wg_port }}
PrivateKey = {{ wg_server_private_key }} 

PostUp = /etc/wireguard/helpers/add-nat-rules.sh
PostDown = /etc/wireguard/helpers/remove-nat-rules.sh

~/projects/gwt/wg-client.yml

- hosts: secret_oracle_tunnel 
  gather_facts: false
  vars:
    wg_peer_ipv4: 192.168.178.2
    wg_peer_ipv6: fd42:42:42::2
    wg_peer_public_key: "{{ vault_wg_peer_public_key }}"
  tasks: 
    - name: Append peer publickey to wg0 server conf
      include_role: 
        name: wireguard
        tasks_from: client

- hosts: 127.0.0.1
  connection: local
  vars_files:
  - host_vars/secret_oracle_tunnel.yml
  vars:
    wg_peer_private_key: "{{ vault_wg_peer_private_key }}"
    wg_peer_ipv4: 192.168.178.2
    wg_peer_ipv6: fd42:42:42::2
  tasks:
    - name: Generate a client.conf in the playbook dir
      ansible.builtin.template:
        src: roles/wireguard/templates/client.conf.j2 
        dest: "{{ playbook_dir }}/client.conf"

~/projects/gwt/roles/wireguard/tasks/client.yml

- hosts: secret_oracle_tunnel 
  gather_facts: false
  vars:
    wg_peer_ipv4: 192.168.178.2
    wg_peer_ipv6: fd42:42:42::2
    wg_peer_public_key: "{{ vault_wg_peer_public_key }}"
  tasks: 
    - name: Append peer publickey to wg0 server conf
      include_role: 
        name: wireguard
        tasks_from: client

- hosts: 127.0.0.1
  connection: local
  vars_files:
  - host_vars/secret_oracle_tunnel.yml
  vars:
    wg_peer_private_key: "{{ vault_wg_peer_private_key }}"
    wg_peer_ipv4: 192.168.178.2
    wg_peer_ipv6: fd42:42:42::2
  tasks:
    - name: Generate a client.conf in the playbook dir
      ansible.builtin.template:
        src: roles/wireguard/templates/client.conf.j2 
        dest: "{{ playbook_dir }}/client.conf"
[Interface]
Address = {{ wg_server_ipv4 }}/24, {{ wg_server_ipv6 }}/64
ListenPort = {{ wg_port }}
PrivateKey = {{ wg_server_private_key }} 

PostUp = /etc/wireguard/helpers/add-nat-rules.sh
PostDown = /etc/wireguard/helpers/remove-nat-rules.sh

Wireguard will execute any shell scripts you place in /etc/wireguard/helpers so that’s a good spot for all the iptables PostUp and PostDown stuff below.

~/projects/gwt/roles/wireguard/templates/add-nat-rules.sh.j2

#!/bin/bash

## IPv4
iptables -t nat -I POSTROUTING 1 -s {{ public_subnet }} -o {{ public_network_interface }} -j MASQUERADE

iptables -t nat -I POSTROUTING 1 -s {{ wg_subnet_ipv4 }} -o {{ public_network_interface }} -j MASQUERADE
#Allow all traffic on {{ wg_interface }} interface:
iptables -I INPUT 1 -i {{ wg_interface }} -j ACCEPT
iptables -I FORWARD 1 -i {{ public_network_interface }} -o {{ wg_interface }} -j ACCEPT
iptables -I FORWARD 1 -i {{ wg_interface }} -o {{ public_network_interface }} -j ACCEPT
iptables -I INPUT 1 -i {{ public_network_interface }} -p udp --dport {{ wg_port }} -j ACCEPT

## IPv6
ip6tables -t nat -I POSTROUTING 1 -s {{ wg_subnet_ipv6 }} -o {{ public_network_interface }} -j MASQUERADE
ip6tables -I INPUT 1 -i {{ wg_interface }} -j ACCEPT
ip6tables -I FORWARD 1 -i {{ public_network_interface }} -o {{ wg_interface }} -j ACCEPT
ip6tables -I FORWARD 1 -i {{ wg_interface }} -o {{ public_network_interface }} -j ACCEPT

remove-nat-rules.sh.j2 is just the reverse, with -D instead of -I

Success so clearly in view, or is it merely a trick of the light?

After starting the tunnel with wg-quick up client.conf and verifying that ping 192.168.178.1 succeeds and curl -4 icanhazip.com reports the vps ip, I was ready to chalk up another victory.

Except then the emails started coming in a few days later:

Very confusing. I thought my account was locked and the VPS deleted, but that was not the case. Just some really weirdly threatening copy.

Welcome to my digital domain