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.
I initially thought I would be clever and launch my VPN as part of a docker stack so I wouldn't need to launch it independently on reboot, but swarm does not support the extra capabilities the VPN needs to work (cap_add). And in retrospect this wouldn't even make sense. Subsequently I was going to rewrite the upstart scripts in the tutorials as systemd, but I found a better one buried in the docs. (Ubuntu stopped supporting upstart in 2015). In the end, however, I opted to launch the VPN using docker-compose with a restart directive. No need for swarms on the bastion or systemd or upstart.
If you chose to block your SSH port(s) so you can only access them via VPN, then you are therefore dependent on the VPN being properly up and running before you can SSH in. If something goes wrong with your setup, you'll be locked out. Be sure you have an emergency path back into your system - if you are using Digital Ocean, then you can use the console with a root password, but I'd recommend making absolutely sure the VPN is running properly before you start blocking things.
# 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
# Run this *locally* to grab the config file rsync -avzr $USER@$OVPN_FQDN:/$USER/$OVPN_NAME.ovpn ~/Desktop
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
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
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 126.96.36.199 -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.