NFS server over WireGuard in Alpine

Categories: alpine linux

I have recently built a new storage server at home consisting of 6x2TB hard drives configured as RAID-Z2 with ZFS. For the operating system I have chosen Alpine Linux, which is a distribution that I really like for its lightweightness and its simplicity.

Previously I had a storage server running Debian which I was sharing via sshfs. sshfs is really easy to set up, as you just need to be running an ssh server, nevertheless it has some disadvantages:

  • On the client it runs via fuse, so all I/O operations go through userspace, adding overhead
  • For each sshfs process, you can only access with a single user ID and group ID. I use different users in my system so this is inconvenient.

I know that in Linux the de facto protocol to share a filesystem is NFS, but in the past I decided not to use it because NFS doesn’t run the protocol under an encrypted channel, which is something that I wanted. But now we have WireGuard, which is a very performant and easy to set up VPN protocol, so I decided to set up NFS sharing over WireGuard for my new storage server.

Here’s my setup:

  • server: Apline Linux (hostname: host.foo.bar)
  • client: Arch Linux

Here are some reference I used to accomplish this setup:

Wireguard

We will setup a wireguard connection between the server and the client using the following parameters:

  • server
    • interface: wg0
    • IP: 10.8.0.1
  • client
    • interface: wgNFS
    • IP: 10.8.0.2

Server

First of all we install the wireguard tools required to generate keys and manage the wireguard interface.

apk add wireguard-tools-wg wireguard-tools-wg-quick wireguard-tools-doc wireguard-tools

Then we load the wireguard module and configure our server to load it again on boot. In Alpine wireguard is already available in the kernel.

modprobe wireguard
echo "wireguard" >> /etc/modules

Now we need to generate a key pair.

umask 077 
wg genkey > privatekey
wg pubkey < privatekey > publickey

We edit the wireguard configuration for the interface wg0 by editing the file /etc/wireguard/wg0.conf with the following contents (where SERVER_PRIVATE_KEY are the contents of the server’s privatekey and CLIENT_PUBLIC_KEY are the contents of the client’s publickey, which is generated in the next section):

[Interface]
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY

[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.8.0.2/32

Don’t forget to delete the privatekey file once you’ve copied it’s contents in the wireguard configuration file.

rm privatekey

Now we edit the network configuration file at /etc/network/interfaces and add the following, so that the wireguard interface is set up automatically (Notice that we didn’t set an interface address in the wireguard configuration, but we are specifying it here):

auto wg0
iface wg0 inet static
        requires eth0
        use wireguard
        address 10.8.0.1

Now we start the wireguard interface manually (in the next reboot, it will be started automatically):

ifup wg0

Client

Install wireguard tools:

sudo pacman -S wireguard-tools

Like in the server, we create a pair of keys:

umask 077 
wg genkey > privatekey
wg pubkey < privatekey > publickey

I choose a different name for the client interface. To configure wireguard we edit /etc/wireguard/wgNFS.conf (where CLIENT_PRIVATE_KEY are the contents of the client’s privatekey and SERVER_PRIVATE_KEY are the contents of the servers’s publickey, which is generated in the previous section):

[Interface]
Address = 10.8.0.2/24
PrivateKey = CLIENT_PRIVATE_KEY

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = host.foo.bar:51820
AllowedIPs = 10.8.0.0/24

Don’t forget to delete the privatekey file once you’ve copied it’s contents in the wireguard configuration file.

rm privatekey

Finally we manage the set up of the interface with systemd, and start it.

sudo systemctl enable wg-quick@wgNFS.service
sudo systemctl daemon-reload
sudo systemctl start wg-quick@wgNFS.service

The wireguard setup should be working now. You can verify that you have a wireguard communication between server and client by doing pings over the wireguard interface.

From server:

ping 10.8.0.2

From client:

ping 10.8.0.1

Both ways should work if everything is working as expected.

NFS

Server

Install nfs utilities:

apk add nfs-utils nfs-utils-openrc nfs-utils-doc

We edit the nfs daemon configuration parameters in order to disable nfs version 2 and 3 (leaving only version 4, which is the latest), and we also instruct nfs rpc to listen on the wireguard interface only. To do this we edit the file /etc/conf.d/nfs and change the corresponding part:

[...]

# Options to pass to rpc.nfsd
OPTS_RPC_NFSD="--no-nfs-version 2 --no-nfs-version 3 --nfs-version 4 --host 10.8.0.1 8"

# Options to pass to rpc.mountd
# ex. OPTS_RPC_MOUNTD="-p 32767"
OPTS_RPC_MOUNTD="--no-nfs-version 2 --no-nfs-version 3 --nfs-version 4"

[...]

We also create and edit the file /etc/nfs.conf to make the nfsd process to only listen on the wireguard interface:

[nfsd]
host=10.8.0.1

Finally, we add a line to /etc/exports to set the path we want to share over NFS:

/data          10.8.0.0/24(rw,sync,no_subtree_check,crossmnt,no_root_squash)

We run the following command to load the recently updated exports file:

exportfs -a

And finally we add the nfs service to the default target in OpenRC and start it.

rc-update add nfs
rc-service nfs start

Client

On the client side, we add an entry to fstab indicating the NFS mount. Using systemd we specify the service requirements for this mount:

echo "10.8.0.1:/data   /data    nfs vers=4.2,_netdev,noauto,x-systemd.automount,x-systemd.requires=wg-quick@wgNFS.service" >> /etc/fstab

And finally, we restart the remote-fs systemd target which will trigger the NFS mount by reading our fstab file.

sudo systemctl daemon-reload 
sudo systemctl restart remote-fs.target

Now in our client we should be able to access the storage folder at /data.

Written by Dhole