Using Docker Wireguard VPN on XPEnology/DSM


Recommended Posts

Unless you've been hiding under a rock, you will probably have heard about Wireguard - an emerging alternative to OpenVPN for quick, simple and secure VPN access.  It's nothing short of amazing and very fast.  It's so innovative that Linus Torvalds has cleared it for direct inclusion into the upcoming Linux kernel 5.6.  Unfortunately, it's not an option for DSM, even with Docker, because it requires the Wireguard kernel-mode extensions compiled into the kernel (which we can't easily do).

 

Enter an obscure docker module "masipcat/wireguard-go", compiled by an MIT researcher with a userspace (non-kernel) implementation interfacing to the /dev/net/tun device.  This isn't as fast as a kernel implementation, but it provides the basis to make Wireguard work on DSM!  Unfortunately the documentation and configuration examples are incredibly sparse, and Wireguard is positioned as a microservice whereas OpenVPN artificially embeds routing services (push commands). So it takes some doing to make Wireguard work beyond the most basic implementations.

 

The basis of Wireguard is public/private key exchange.  Each node of the VPN generates a private key, from which it then computes a public key.  Then you install the public key from node A on node B, and vice-versa.  This is a simple and quick configuration.

 

So here are two example use cases where I've been able to make Wireguard work on DSM, exactly as desired, using this dockerfile - they might be of interest to you.

 

Case #1: Remote access to a LAN behind a firewall (which includes a DSM server)

 

Typically, we would install OpenVPN Server from Package Center for this.  I'm not a fan of OpenVPN because the code on Synology is quite old, the configuration is unwieldy, and the interface is dumbed down to the simplest configurations. While it could work in this case, the Synology implementation cannot be used for more sophisticated or esoteric configurations.

 

Scenario:

Local network: 10.1.1.0/24, Client device 10.1.1.100

Remote LAN: 192.168.1.0/24, DSM server 192.168.1.50

The networks are each connected to consumer Internet (dynamic IP) services using port-forwarding capable firewalls

  1. First, download the Wireguard client for your client device (Windows, Mac etc)
     
  2. Create a new tunnel, which will compute a Public/Private key combination. Write down these two keys, which will be for the remote DSM server.

    image.thumb.png.75cdeaa3f096d54f026402703aab2584.png
     
  3. Then delete the tunnel and create another one. Note that I call the tunnel "remote" because that is what makes sense in the UI (I'm connecting to the remote site) but the keys are for the local client and network.

    image.thumb.png.2aad65b013cb4b611bf29e8046ef485e.png
     
  4. Now, configure the local client. The [Interface] section refers to the local device, and the [Peer] is the remote device. Use the PublicKey that you saved from the first step for the Peer. I'm arbitrarily picking 192.168.0.0/24 as the VPN network. The only restrictions are that it should use a private address scheme and not overlap any of the local or remote networks. The listen ports are also completely arbitrary; just don't create conflicts with existing services.

    In the Peer section, note the AllowedIPs. This refers to the networks that the local Wireguard client will intercept and encrypt onto the VPN.  We need 192.168.0.1 because it will be the target VPN address on the remote DSM, and 192.168.1.0/24 so that we will be able to access other devices on the remote network.  Finally note that we can use a DNS address, DDNS address or a static IP to find the remote endpoint on the Internet.

    image.thumb.png.ac79dbbf13482b778914d74a5eff64ca.png
     
  5. Now we need to configure the Wireguard docker container on the remote DSM server.  If you haven't already, install Docker from the Package Center. Unfortunately Synology Docker doesn't expose all the necessary config options in the UI, so we will need to create a container script manually.  Once the container is built, it can be monitored, stopped and started from the UI.
     
  6. SSH into your system, elevate to root, and find the docker folder. In this example we will assume docker is installed to Volume 1.  Then create a directory for Wireguard and an initiation script.
    # cd /volume1/docker
    # mkdir wireguard
    # cd wireguard
    # echo "docker run -d --name wireguard --restart=always --cap-drop=ALL --cap-add=NET_ADMIN --network=host -v /dev/net/tun:/dev/net/tun -v /volume1/docker/wireguard/wg0.conf:/etc/wireguard/wg0.conf --sysctl net.ipv4.ip_forward=1 --sysctl net.ipv6.conf.all.disable_ipv6=1 masipcat/wireguard-go" >wireguard.sh
    # chmod 700 wireguard.sh

    We do not have access to the capability or sysctl options with the Docker UI. 
     

  7. Now, create the remote DSM Wireguard interface configuration /volume1/docker/wireguard/wg0.conf using your choice of editor, using the correct Private and Public keys from our first steps.  Note there is no Endpoint configuration, which means that only the client will be able to start the tunnel. The ListenPort must match the Endpoint port on the client, and the remote AllowedIPs entry must match the Interface IP on the client.

    [Interface]
    Address = 192.168.0.1
    PrivateKey = 0IUlYn3c8DbZDlO2pbVtVerTI4jk7/57ZoMlsC6sOVo=
    ListenPort = 55556
    
    [Peer]
    PublicKey = uSB/0i8TW8AV9bu7ums51Rc29jQrCIpWg1XJSok99Xs=
    AllowedIPs = 192.168.0.2/32

    Note that because the Wireguard docker container directly connects to the host DSM network, the ListenPort must not conflict with any services inside DSM.
     

  8. Unless you are already running OpenVPN on DSM, the /dev/net/tun VPN device will not be initialized.  Download the attached loadtun.sh script and install in /usr/local/etc/rc.d folder. Set it to executable and run it to create your tun device.  It will automatically start on boot from now on.

    # chmod 700 /usr/local/etc/rc.d/loadtun.sh
    # /usr/local/etc/rc.d/loadtun.sh start

     

  9. Now you are ready to start up the Wireguard docker container.

    # /volume1/docker/wireguard/wireguard.sh

    If all goes well, the container will download, initialize and display a hex string indicating that it is running. If you get error messages, check the wireguard.sh file for completeness and accuracy, referencing step #6.
     

  10. Check to make sure the remote Wireguard is ready for a connection by launching the Docker UI (you should see the wireguard container running). Select the container details, and display the log - it should look something like the image below. If there are errors, check your wg0.conf for correct syntax.

    image.thumb.png.fd477f22763678c96245b24ca6feb295.png
     

  11. The last step is to port-forward the remote firewall to the DSM IP.  In this example, port 55556 is forwarded to 192.168.1.50.
     

  12. Now Activate the local client and you should have access to the remote DSM server and everything on its network!

loadtun.sh

Edited by flyride
  • Like 2
  • Thanks 1
Link to post
Share on other sites
Posted (edited)

Case #2: Secure NFS access to DSM folder

 

I run a VPS (virtual cloud server) to host a number of websites.  I use VEEAM for Linux Free edition to back up all my Linux servers.  However, VEEAM requires a NFS or SMB share on which to store the backup. This backup went to a second VPS server on the same hosting service, accessible via private network.  I decided to try and use Wireguard to make my own XPEnology DSM storage accessible as a backup target for VEEAM, thus saving the cost of the second VPS server. Note that if we tried to use Synology's OpenVPN to provide this access, we expose the entire NAS to the remote server (and essentially, the Internet) and would have to figure a way to firewall off everything but NFS.

 

This example will use a microservices approach to solving this problem by interfacing two docker containers together to accomplish three objectives:

  • Connect to high-speed shared storage (a DSM folder)
  • Make that storage accessible via NFS4
  • Provide a secure Wireguard VPN wrapper around the services and limit access only to the target IP

I won't go through all the details of the Linux Wireguard installation on the VPS server; just use the first post in this thread as an example of how to create key pairs, etc.

 

Scenario:

Local network: 10.1.1.0/24, DSM server 10.1.1.100

VPS server: 204.204.204.204

The local network is connected to consumer Internet using a firewall, and the VPS server is directly connected to the Internet at the hosting provider

  1. The VPS Wireguard configuration is very straightforward and looks a great deal like the step #7 configuration of the remote DSM server in the first post.  
    # cat /etc/wireguard/wg0.conf
    [Interface]
    Address = 192.168.0.1
    PrivateKey = <VPS private key>
    ListenPort = 55556
    
    [Peer]
    PublicKey = <client public key>
    AllowedIPs = 192.168.0.2/32
    Again, we are using the private network 192.168.0.0/24 arbitrarily as the VPN network.  Once this is done it can be set active with wg-quick (script that configures Linux firewalls and routes to allow the wg0 interface to work correctly).
     
  2. Now we need to build our docker container set.  Again, the Synology Docker UI doesn't expose all the docker functionality.  In this case we are going to use docker-compose to build a single application using two different container services. While we can see the containers running from the UI, starting and stopping the container set should be done using the docker-compose command line.

    If you haven't used docker-compose before, each application is entirely defined in a specific file, docker-compose.yml. The container set takes the name of the folder in which docker-compose.yml is stored.  We'll call this one "vps"

    We again use the example of docker installed to Volume 1. And we assume you now know how to build the /volume1/docker/vps/wg0.conf file in the folder, so the example just displays the contents for contextual reference.
    # cd /volume1/docker
    # mkdir vps
    # cd vps
    
    (someone created wg0.conf here)
    
    # cat wg0.conf
    [Interface]
    Address = 192.168.0.2
    PrivateKey = <local private key>
    ListenPort = 55555
    
    [Peer]
    PublicKey = <VPS public key>
    AllowedIPs = 192.168.0.1/32
    Endpoint = 204.204.204.204:55556
    PersistentKeepalive = 30
    Note that we have a new option, PersistentKeepalive. This, along with the Endpoint setting, ensures that the VPN tunnel initiates and stays up whenever the DSM service is running.
     
  3. Now to create the /volume1/docker/vps/docker-compose.yml file. 
    version: '3'
    
    services:
      vps-wireguard:
        hostname: vps-wireguard
        image: masipcat/wireguard-go
        container_name: vps-wireguard
        restart: always
        cap_drop:
          - ALL
        cap_add:
          - NET_ADMIN
        sysctls:
          - net.ipv4.ip_forward=1
          - net.ipv6.conf.all.disable_ipv6=1
        ports:
          - '55555:55555/udp'
        volumes:
          - /dev/net/tun:/dev/net/tun
          - /volume1/docker/vps/wg0.conf:/etc/wireguard/wg0.conf
    
      vps-nfs:
        depends_on:
          - vps-wireguard
        image: erichough/nfs-server
        privileged: true
        container_name: vps-nfs
        restart: always
        network_mode: "service:vps-wireguard"
        sysctls:
          - net.ipv6.conf.all.disable_ipv6=1
        volumes:
          - /volume1/NetBackup/vps:/NetBackup
        environment:
          - NFS_EXPORT_0=/NetBackup 192.168.0.1(rw,async,no_root_squash,no_subtree_check)
    Syntax is very critical (and tabs are not allowed). Note a few key items from this configuration:

    - the vps-nfs container requires privileged mode (basically root access) to hook the NFS daemon in DSM. This is a security risk and is discussed in the summary below.
    - docker-compose will dynamically create a private network for this container set on launch, and destroy it when it is shut down.  It is not on the host network like the first example, so we need to expose wireguard via docker port mapping.  
    - the vps-nfs container attaches to the wireguard container in order to become available to the remote peer using the network_mode directive. The two containers become, in effect, a single host.
    - the overwhelmingly useful feature of Synology Docker is direct access to shares and folders. The volume mapping allows us to expose whatever folders we want inside a share, directly to the NFS container.  With Synology native services, we have to build a dedicated share for NFS export.
     
  4. Now use docker-compose to activate and deactivate the container set:
    # cd /volume1/docker/vps
    
    # docker-compose up -d
    Creating network "vps_default" with the default driver
    Creating vps-wireguard ... done
    Creating vps-nfs       ... done
    
    # /volume1/docker/vps# docker-compose down
    Stopping vps-nfs       ... done
    Stopping vps-wireguard ... done
    Removing vps-nfs       ... done
    Removing vps-wireguard ... done
    Removing network vps_default
    Note that when the application is running, it is visible as two containers in the Synology Docker UI.
     

The /volume1/NetBackup is now available to the VPS server via mount -v 192.168.0.2:/NetBackup <mountfolder> protected by wireguard VPN wrapper!

 

From a security standpoint, the only local DSM ports I've exposed to my VPS host are NFS and the only device allowed to connect is the VPS VPN endpoint (192.168.0.1). While a hacker that roots the VPS host can make a mess of the system and delete data via NFS, I am protected via btrfs snapshots that cannot be touched. The DSM server running Docker is theoretically vulnerable to a Wireguard or NFS hack that gains container shell access. This would essentially give the attacker root for the whole system because of the privileged flag on vps-nfs. If the only exposure to the Internet is through the vps stack, it's very unlikely to be compromised, given that wireguard and nfs are incredibly mature and security tested products.  But if other Docker containers are Internet-exposed and are compromised, the likelihood for cross-container access is high.

 

To improve security, our primary objective should be to remove privileged mode access for vps-nfs through careful configuration - See the addendum below for how to do this.  Also, we could improve the container security by deploying Synology Docker to run without root access and with an isolated user for the vps stack.  I'm not sure the latter is possible, given Synology's tight integration with the rest of their ecosystem, and shouldn't be much of a concern if not exposing Syno Docker services to the Internet, but in this case might be worth looking at.

 

 

ADDENDUM - How to run the NFS server without privileged access

 

This isn't really a wireguard configuration so it's diverging from the original point of the thread and branches out into other areas of Linux/Synology security, so I decided to add as an addendum.  You can ignore if you are happy with the functionality example thus far, but if you want to eliminate privileged access on the NFS container, read on.

 

AppArmor is a linux kernel security utility that governs certain system resources from non-root user processes. It's very liberally configured within DSM, and Synology mostly uses it to manage and protect access to their official packages in Package Center.  However, Synology's default settings conflict with the privileges needed to avoid the "privileged=true" flag for the vps-nfs container, so we need to modify AppArmor to explicitly allow those privileges.

  1. First, we need to create an AppArmor profile for the container. Download the attached vps.aa file and save to the /volume1/docker/vps directory.
     
  2. The profile refers to a standard include file which is not present in Synology. Install it by downloading the attached container-base file and save it to the /etc/apparmor.d/abstractions directory (github source).
     
  3. When these files are in place, add the profile to AppArmor with
    # apparmor_parser -r -W /volume1/docker/vps/vps.aa

Now we need to update the docker-compose file to exactly specify the permissions Docker needs for the container, rather than the privileged=true flag.

  vps-nfs:
    depends_on:
      - vps-wireguard
    image: erichough/nfs-server
#   privileged: true
    cap_drop:
      - ALL
    cap_add:
      - SYS_ADMIN
      - AUDIT_WRITE
      - SETGID
      - SETUID
      - KILL
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    security_opt:
      - apparmor=erichough-nfs
    container_name: vps-nfs
    restart: always
    network_mode: "service:vps-wireguard"
    volumes:
      - /lib/modules:/lib/modules:ro
      - /volume1/NetBackup/vps:/NetBackup
    environment:
      - NFS_DISABLE_VERSION_3=true
      - NFS_EXPORT_0=/NetBackup 192.168.0.1(rw,async,no_root_squash,no_subtree_check)

Changes:

  • Delete the privileged: true flag
  • Eliminate all add-on capabilities for the container (cap_drop)
  • Add back in just the capabilities explicitly needed for it to run (cap_add)
  • Link the container to the apparmor profile (security_opt)
  • Link the container to the host kernel modules so that root is not required to reach the ones it needs to load (lib/modules mapping)

There is one non-critical change added that improves the security profile as well: NFS_DISABLE_VERSION_3=true ensures that all the obsolete NFS3 ports aren't accessible.

Edited by flyride
Addendum
  • Like 1
Link to post
Share on other sites

So I just got the time to read through all information: Thank you very much for this!!

 

Btw: We have a similar backup routine for backup "real" linux equipment (I use also Veeam Linux Free). :-) For now I have the only issue that Veeam currently is not supporting LUKS2-encryption (only LUKS1). But this is much off topic and only a matter of time.

Link to post
Share on other sites
Posted (edited)

Addendum created to post #2 which describes changes required to run NFS without container root (privileged: true).

Edited by flyride
  • Thanks 1
Link to post
Share on other sites
  • 2 weeks later...

So I have now build your setup by myself. Everything went well and only one step was missing :-)

 

I used some info from this article to get the missing pieces:

https://www.stavros.io/posts/how-to-configure-wireguard/

 

Quote

To actually access the server’s LAN, you’ll need to make a slight modification to the configuration. On the server’s config file, at the end of the the [Interface] section, add these two lines:


PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

 

 

After adding these two lines into the server configuration file I am able to access my LAN and also the DSM via my smartphone over wireguard! :-)

 

I think the procedure itself will be running fine even on a raspberry pi as there is also docker available.

Thank you very much again for this fine idea. 👏

 

Edit:  I was building the first part of your manual to connect to the DSM or local LAN via wireguard. Not the second extended manual with "NFS only" or even the unprivileged access. This is something for another day. :-)

Edited by Balrog
add infos
Link to post
Share on other sites
  • 2 months later...
  • 1 month later...

Since my old Fritzbox I used with openwrt and wireguard went to oblivion I want to make use of wiregiard on my xpenology (HP54NL), too.

 

The docker solution of https://github.com/runfalk/synology-wireguard sounds promising. But since it is a bit behind my horizon I would like to know what you think about it and if you got things nicely running before I start digging into it.

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.