Accessing (Docker/OCI) Containers over a WireGuard VPN (Part 1)

2020-06-30

In this tutorial we'll be setting up a WireGuard VPN. In the second part we'll configuring WireGuard further and access a container (Docker) using the VPN (from either Windows or Ubuntu). The next post will be available next week, so stay tuned.

Why would you want to do this? Well, you might be running on a public cloud and need access to certain internal docker services such as:

  • npm/docker/scala/java repositories such as a nexus server,

  • monitoring tools such as Kibana, Prometheus and Grafana or

  • CD tooling such as jenkins and gitlab

  • Admin console such as keycloak or other identity servers

  • you might have in-house admin applications that need access over the Internet

    There are a few ways of restricting access to aforementioned types of services:

  • You could use a allow-list on your reverse proxy (e.g., Traefik's IPWhiteList). This might be a little difficult to manage. Especially when people connect from non-stable IP addresses or when they are on the road.
  • There are plenty of VPN containers that make it easy to connect to or from docker containers. One such image is available at (linuxserver//docker-wireguard)[https://github.com/linuxserver/docker-wireguard]. However, oftentimes you already have a VPN infrastrucutr. In addition some will argue that using a docker container is a bit convoluted.
  • You could obviously always just keep the service "open" on the network and use Authentication. In all likelihood, you can use your reverse proxy for this (for Traefik you could for example use BasicAuth, or use mTLS)
  • Use an existing (WireGuard) VPN network (sorry for the RAS Syndrome. And use IP routing. Directly or in-direct through docker networks and port mappings.

The two blog articles focus on the latter.

The steps in the tutorial are for Ubuntu. Any Ubuntu 18.04 or higher will do.

Here is an overview of the vpn we'll be setting up

Install WireGuard on the Server

The obvious first step is to install WireGuard on your system:

server:/ $ sudo apt install wireguard

Generate key's on the server

WireGuard uses base-64 encoded asymmetric keys (public and private keys). We'll be generated a pair using the WireGuard tools. We'll keep the keys inside the /etc/wireguard directory.wireguard

The following steps need to be run as root. Use sudo to switch (see sudo -i if you're not familiar with the command below)

server:/ $ sudo -i

Create the conf directory (server)

We want to make sure we have the /etc/wireguard directory. Let's check if it has been created for us during the installation of WireGuard:

server:/ $ ls /etc/wireguard

If you are getting no error, then skip the next step/command

You received a "No such file or directory" error (by the way, if you go "permission denied", then you most likely forgot to switch to root).

To create the directory, run the following commands:

server:/ $ mkdir /etc/wireguard

Next, we'll check the file permission. Recall we are creating this directory to store a private key. Therefore, we want only the owner (root) of this directory to have to read, write and execute permissions. Everyone else should have no permissions.

This means we need a file permission octal of 0077 (which translates to u=rwx,g=,o=)

  1. Go into the /etc/wireguard directory:

    server:/ $ cd /etc/wireguard
  2. Check the permissions, by running umask without any arguments:

    server:/etc/wireguard $ umask

If you see 0022, then you'll need to change it:

server:/etc/wireguard $ umask 077

Check your permissions (you can also add the -S option to see a symbolic output)

server:/etc/wireguard $ umask
0077

Generate the key-pair (server)

First we generate a private key for the server. We'll use the wg command for this by passing the genkey subcommands and piping (>) the output to a file named privatekey.

server:/etc/wireguard $ wg genkey > privatekey

You should now find a file named privatekey

With the private key, we can derive the public key. This time we use the pubkey subcommand, feed it the private key (<) and pipe the result to (>) a file named publickey:

server:/etc/wireguard $ wg pubkey < privatekey > publickey

You now have two files that identify this server peer. Later on you'll have to generate a second key pair to identify the client.

As a side note, we could have achieved the same using a single command by using tee. A small but powerful tool to "read from standard input and write to standard output and files")

# No need to run this
server:/etc/wireguard $ wg genkey | tee privatekey | wg pubkey > publickey

See explainshell.com if you want to understand the command better.

Setup the key for the client

For the client we've got instructions for Ubuntu and for Windows (further below)

Ubuntu client

We can go through this rather quickly as you are already familiar with the commands:

  1. Become root

    client:/ $ sudo -i
  2. Install WireGuard

    client:/ $ sudo apt install wireguard
  3. Create /etc/wireguard if needed:

    client:/ $ mkdir /etc/wireguard
  4. Set the file permissions

    client:/ $ cd /etc/wireguard && umask 077
  5. Generate the client's key pair (we'll use the single command)

    client:/etc/wireguard $ wg genkey | tee privatekey | wg pubkey > publickey

Windows client

For windows, you'll need to install "WireGuard for Windows". You can download the installer here: https://www.wireguard.com/install/

After installation, launch WireGuard and select Add empty tunnel to add the client configuration.

Then provide a name, and leave this dialog open (the values for the keys will be different for you)

We'll revisit this dialog later.

Configure WireGuard on the Server

We'll create a new file /etc/wireguard/wg0.conf to configure the server. Later on you'll be configuring the client peer using the same syntax.

In its most basic form the configuration consist of the following ini file sections:

  • a interface section: to specify the IP-address for the network interface, the listen port and the peer's identity in the form of its private key.
  • one ore more peer section: contains the public key of a peer (the client) and we can add an allow-list to block other IPs-addresses

For the server we'll use 192.168.2.1/24 with port UDP 51820 (address 192.168.2.1 with netmask 24, which you might also know as 255.255.255.0). For the client we'll use 192.168.2.2/32.

You need to have the client-peer's public key and server's private key available.

To get the client public key from linux, use the following command on the client's public key (assuming you are root):

client:/ $ cat /etc/wireguard/publickey

For Windows, copy the Public key from the WireGuard dialog.

For the server's private key use (assuming you are root)

server:/ $ cat /etc/wireguard/privatekey

Now define the Server's configuration.

Using an editor create and edit the /etc/wireguard/wg0.conf

server:/ $ vim /etc/wireguard/wg0.conf

Add the following content (replace the keys with your own values and don't close the file yet)

[Interface]
Address = 192.168.2.1/24
ListenPort = 51820
PrivateKey = <SERVER PRIVATE KEY>

[Peer]
PublicKey = <CLIENT PUBLIC KEY>
AllowedIPs = 192.168.2.2/32

Notice the value of the AllowedIPs. Any traffic from this peer to 192.168.2.2/32 will be send over this WireGuard interface (wg0). More importantly (for our use case), when this peer (which is the server) received traffic on wg0 in will drop packets not matching the AllowedIPs

We are almost ready to start the server's endpoint, but we'll need to configure the firewall/IP Tables (Assuming you are running one)

Configure the Server's firewall (IP Tables)

We need to open port 51820. If you have UFW (the "uncomplicated firewall") installed, then we can easily do that using a command as simple as sudo ufw allow 51820/udp. You could just run that command and leave the port open even when your vpn is not running. However it is better if we open the port only when the WireGuard endpoint server is running. This can be accomplished using PostUp/PostDown hooks inside our configuration. These hooks allow us to run bash scripts snippets that are executed before/after setting up/tearing down the interface ( The string %i gets expanded to the name of the interface, but with ufw, we won't need that)

Add the following two lines to the Interface section of your server's /etc/wireguard/wg0.conf configuration file:

PostUp = ufw allow 51820/udp
PostDown = ufw delete allow 51820/udp

Start and Enable the WireGuard Service (server)

To bring up the VPN tunnel we'll be using wg-quick (which was part of the WireGuard installation earlier).

Let's start the service to test our configuration:

server:/ $ systemctl start wg-quick@wg0

And check the status:

server:/ $ systemctl status wg-quick@wg0

You should see active (exited). Awesome! If you are getting an error, then check the steps above again (Make you are using the correct keys, the syntax of the init-file is correct etc.)

With no errors, enable the service to automatically run at boot time (if your want of course)

server:/ $ systemctl enable wg-quick@wg0

Configure WireGuard on the Client

Again, we've provided instructions for both Ubuntu and Windows.

Ubuntu client

A few notes about the client configuration. It is very similar to your peer's (the server):

  • Interface: You will obviously not open a port, but will have an interface with your IP address (recall that's 192.168.2.2/32 for this client). You'll also have to specify your identity on this interface using the client's private key you generated earlier
  • Peer for the client the server is the peer. You need to tell the client the IP address of your server. Also you'll need to provide the server's public key in order to establish a proof of the server's identity.

ToObtain the server's public key:

server:/ $ cat /etc/wireguard/publickey

And your private key

client:/ $ cat /etc/wireguard/privatekey

Then on your client create a WireGuard configuration file named /etc/wireguard/wg0.conf:

client:/ $ vim /etc/wireguard/wg0.conf

Use the following as a template (as before, replace the placeholders with the correct key values)

[Interface]
PrivateKey = <CLIENT PRIVATE KEY>
Address = 192.168.2.2/32

[Peer]
PublicKey = <SERVER PUBLIC KEY>
AllowedIPs = 192.168.2.0/24
Endpoint = <SERVER PUBLIC IP>:51820
PersistentKeepalive = 30

Once again, observe the value of the AllowedIPs. Any traffic from this peer to 192.168.2.0/24 (iow 192.168.2.x) will be routed over the WireGuard interface wg0. If you wanted all traffic from this peer to be routed to wg0 you could use a value such as 0.0.0.0/0 (or rather 0.0.0.0/0, ::/0 in order to also route ip6 traffic). As a reminder. Recall from our explanation earlier, when we configured the server, incoming traffic is dropped when it not matches AllowedIPs.

The PersistentKeepalive is used when you are behind a NAT/Firewall. Normally the WireGuard protocol goes to sleep when not needed. This means it is not sending any packets. However NAT/Firewalls keep track of connections and you need to periodically send (keep-alive) packets. That's what we are doing with PersistentKeepalive This will ensure a keep-alive packet is send every 30 seconds.

Now, start the client and test your connection

Start the VPN service on the client

client:/ $ systemctl start wg-quick@wg0

Then try and ping using your server's VPN IP Address

client:/ $ ping 192.168.2.1

You can also ping your client form your server;

server:/ $ ping 192.168.2.2

Windows

Provide the following configuration:

Once again, observe the value of the AllowedIPs. Any traffic from this peer to 192.168.2.0/24 (iow 192.168.2.x) will be routed over the WireGuard interface wg0. If you wanted all traffic from this peer to be routed to wg0 you could use a value such as 0.0.0.0/0 (or rather 0.0.0.0/0, ::/0 in order to also route ip6 traffic). As a reminder. Recall from our explanation earlier, when we configured the server, incoming traffic is dropped when it not matches AllowedIPs.

The PersistentKeepalive is used when you are behind a NAT/Firewall. Normally the WireGuard protocol goes to sleep when not needed. This means it is not sending any packets. However NAT/Firewalls keep track of connections and you need to periodically send (keep-alive) packets. That's what we are doing with PersistentKeepalive This will ensure a keep-alive packet is send every 30 seconds.

To activate the tunnel, double click the icon in the list on the left, or click the activate button.

Then open a command window and ping your server:

c:\Users\windowsUser> ping 192.168.2.1

In the next article we'll access a container (Docker) over the VPN. The next post will be available early July 2020.