Securing Docker with OpenVPN

category:

all posts
culture
design
engineering
project

Tags:

1445
aarhus
academia
art
art and technology
artificial intelligence
artistic research
artwork
blog
borders
certificate
client side
collaboration
cultural criticism
culture
design
devops
digital humanities
digital rights
digital scenography
docker
feral
future memory
gitlab
hosting
interdisciplinary
internet
internet of things
ios
jekyll
loss
manifesto
medium
memory
morm
music
notification
openvpn
pdf
performance
react
research
resist
scripting
shell
skeuomorphism
software
ssl
switzerland
tactical media
television
theatre
transdisciplinarity
vpn
December 22, 2018
VPN + Docker = Secure and flexible remote maintenance

Securing Docker with OpenVPN

After I finished setting up a public facing docker stack for hosting, I realized that my solution for securing access (a simple IP Block) was effective but not nearly as flexible as I needed. VPN seemed like a good solution for this, but I found the tutorials a bit out of date and thin on theory of operation.

I had a surprisingly difficult time initially wrapping my head around how this would work - like most people I've used a VPN for years for security, to encrypt and relocate my internet traffic so it's not exposed on whatever free wifi I'm using. That said, I'd never actually set one up myself. How to secure particular services on one machine using a VPN? Is this a point to point? Is this something more exotic? The answer is straightforward: 1. Use the VPN to "relocate" your traffic to a "bastion" server whose job it is to act as a bridge to your swarm. 2. Configure the sensitive services on your swarm to only accept this "local" access. That's it.

You can get a lot more complex than this and there are other configurations, but this will get you started. In particular it is probably helpful to add "split tunneling" so not all of your internet traffic goes through the VPN server (more on that later).

One "gotcha" if you're new to this and using Docker to hold the VPN: Do not forget that you will be connecting to a docker container. You will not be "VPNing" directly into the host machine itself, so, for example, you will not find a tun+ adapter on your host machine. Look in the container hosting the VPN.

Did you find this helpful or interesting? I'm glad! 🦊 💜 ☕️
Buy Me a Coffee at ko-fi.com
Why?

Step-By-Step

Kylemanna/OpenVPN

Kylemanna OpenVPN for Docker is a really nice solution for getting a VPN up and running quickly. In addition to packaging up the VPN itself, this repo provides packaged configuration scripts that make setup relatively painless. Unfortunately the Digital Ocean community tutorial is a bit out of date.

What follows is a synthesis of the two sets of instructions linked above.

Note:

Set up the VPN

# Set these to whatever you like

OVPN_NAME="EXAMPLE"
OVPN_DATA="ovpn-data-\$OVPN_NAME"
OVPN_FQDN="EXAMPLE.com"
USER="DOCKER_USER"

# Create the volume to hold the config data

docker volume create --name \$OVPN_DATA

# Generate the config

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_genconfig -u udp://vpn.$OVPN_FQDN

# Generate Keys

docker run -v \$OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki

# Launch the VPN

docker run -v \$OVPN_DATA:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn

# Generate a client certificate without a passphrase and then dump the client config to a shareable file (allows full access, so treat this like a root password)

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full$OVPN_NAME nopass
docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_getclient$OVPN_NAME > \$OVPN_NAME.ovpn

Test your Configuration

    # Run this *locally* to grab the config file
    rsync -avzr $USER@$OVPN_FQDN:/$USER/$OVPN_NAME.ovpn ~/Desktop

Launch at Startup

Once you confirm the VPN is running and you can connect remotely, set up the container to be launched at boot. Here's a docker_compose.yml file that will do the trick:

docker-compose up -d

version: "3"
services:
  openvpn:
    volumes:
      - "ovpn-data-EXAMPLE:/etc/openvpn"
    ports:
      - "1194:1194/udp"
    image: kylemanna/openvpn
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
volumes:
  ovpn-data-EXAMPLE:
    external: true

Lock Down The Swarms

At this point you should begin to lock down the services on your swarm, making them accessible only via the VPN login. You can chose to proxy connections to this internal network via the bastion or surface public ports directly from the Swarm.

I found it easiest to use a combination of IPtables rules which restrict some ports to the IP of the VPN host. To get this working smoothly with Docker, I recommend this technique otherwise it's quite a struggle.

Something like this will do the trick:

-A FILTERS -m state -s [VPN_HOST_IP] --state NEW -m tcp -p tcp --dport [RESTRICTED_PORT] -j ACCEPT
-A FILTERS -m state --state NEW -m tcp -p tcp --dport [RESTRICTED_PORT] -j REJECT

Bonus: Split Tunneling

While connected to the VPN, all of your internet traffic is being routed through the VPN host you have set up. You probably don't actually want this (also if you're considering using DigitalOcean as a personal proxy for, say, video streaming or torrenting you probably shouldn't, there are network traffic caps to consider). To work around this, we set up a "split tunnel" which only routes certain requests via the VPN and leaves the rest of the traffic to fend for itself.

To do this, we make a change to the configuration, instead of the step above where we invoked ovpn_genconfig we do something like this:

docker run -v $OVPN_DATA:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -N -d -n DNS.SERVER -u udp://vpn.EXAMPLE.COM -p "route <local net range> <netmask>" -p "route <docker net range> <netmask>"

You can also specify only a single IP if you want:

docker run -v $OVPN_DATA:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -N -d -n 8.8.8.8 -u udp://vpn.EXAMPLE.COM -p "route IP"

Note that it's not sufficient to run this command alone - you should stop the currently running VPN, run this and also the subsequent commands to reissue new connection credentials.

Did you find this helpful or interesting? I'm glad! 🦊 💜 ☕️
Buy Me a Coffee at ko-fi.com
Why?