Using VPN to secure NFS between two hosted servers

 · Systeemkabouter

For a small test setup I wanted to share a disk on a Hetzner VPS with a Hetzner dedicated server. Hetzner has the concept of private networks, but only on their 'Cloud' offering, so that does not extend to dedicated bare metal servers.

Sharing files over NFS seemed like the easiest choice, but sharing NFS data over the public Internet does not seem to be a thing ;-)

At first, I looked at setting up NFS version 4 which supports encryption. Apparently the only way to get encryption native to NFS v4 is setting up Kerberos. Settings up kerberos is entirely possible, but it seemed a bit too much for what I was trying to achieve right now.

The other option I found was using stunnel to wrap NFS traffic in TLS. But just glacing over the guide I found, I figured it was waaaay to involved and complex.

So, I chose the route of setting up a private network between the two machines using Wireguard VPN. Setting up a point to point link with wireguard is relativly straightforward and lightweight. To make it even easier, I created a small ansible role to do the work, so the setup only includes putting in the right YAML data.

Then it was as simple as configuring the nfs service to share a directory over the VPN private IP range.

Sample wireguard playbook setup:

tasks:

- name: "Ensure the wireguard package is present"
  package:
    name: "wireguard"
    state: "present"


- name: "Set up interface configurations"
  template:
    src: "wg.conf.j2"
    dest: "/etc/wireguard/{{ item.name }}.conf"
    owner: "root"
    group: "root"
    mode: "0440"
  loop: "{{ wireguard.interfaces }}"

- name: "Enable systemd service for configs"
  service:
    name: "wg-quick@{{ item.name}}"
    state: "started"
    enabled: true
  loop: "{{ wireguard.interfaces }}"

template:

[Interface]
PrivateKey = {{ item.private_key }}
Address = {{ item.ipv4_address }}
ListenPort = {{ item.port }}


{% for peer in item.peers %}
[Peer]
PublicKey = {{ peer.public_key }}
AllowedIPs = {{ peer.ipv4_address }}
Endpoint = {{ peer.endpoint }}
{% endfor %}

##  Key connection alive ##
PersistentKeepalive = 20

sample yaml data:

wireguard:
  interfaces:
    - name: "wg0"
      private_key: !vault |
        $ANSIBLE_VAULT;1.1;AES256
        38373361316130636438633236666630653030353COFFEE638333530386564636562636361323761
        39366661303137643234363COFFEE33030326564373531640a383663373133653336646337316638
        62383531326564363034653434666661623431316531346361353439643666633338336238623037
        6565633237303462640a643261326566306338313164353465353962636539326531393461396566
        33356639663464316261353234353COFFEE663353639356638643762626338326266383037386337
        3038646232643734313535616166643662306436623731306463
      ipv4_address: "10.XX.XX.1/24"
      port: "1194"
      peers:
        - public_key: "PakL8ZoFK1gdQ9tE+1EyfrPLeBcVIwLBQVn8Sazk0gc="
          ipv4_address: "10.XX.XX.6/32"
          endpoint: "hostname.lutra.it:1194"