OpenVPN with Docker Containers

We create open-source because we love it, and we share our finding so everyone else can benefit as well.

OpenVPN with Docker Containers

Docker is becoming more and more commonplace with custom instance. I recently found myself needing a IDS node container for internal use that would automatically connect to a centralized logging server. The difference from other containers, was the need for an automatically active OpenVPN connection that would start with the instance. At first glance this would seem like a simple process, but the reality is that it requires a bit more effort to create. Let’s look at how to create this container using Alpine Linux, and how to use the power of supervisor to run multiple services.

OpenVPN with Docker

The idea behind the container build was out of need. We needed our Nginx reverse-proxy to pass analytical data to a centralized database server. Because of additional features provided on the host, our company uses a external host for the proxy. So we needed a custom OpenVPN tunnel to securely pass sensitive data to remote logging servers.

Dockerfile Setup

As usual, we add the required packages for OpenVPN as well as supervisor for our process control. Then we replace the configuration files with a custom local copy. We place all of the OpenVPN files into a local folder named vpn. The vpn folder holds the client.conf file, as well as the required keys and certificates needed for OpenVPN. By doing this, we only need one command to copy everything to the /etc/openvpn folder.

ADD vpn/ /etc/openvpn/

If you are looking to setup lots of OpenVPN clients, be sure to check out our OpenVPN Client Management Script.

Since we are wanting to use OpenVPN, we will need access to a tun device for the tunnel. Depending on the host, this device may already be available. In case it doesn’t exist, we create a placeholder during the build process:

RUN mkdir -p /dev/net && \
    mknod /dev/net/tun c 10 200 && \
    chmod 600 /dev/net/tun

Even if it is, we create it so we can build the container image. When we test our build locally, we can provide the device from our own local system like so:

$ docker run --cap-add=NET_ADMIN --device /dev/net/tun:/dev/net/tun -i -t openvpn-test /bin/sh

Once we can establish a connection with OpenVPN, we need to verify it can go through the tunnel. In order to forward our traffic to the tunnel, we need to enable forwarding through sysctl. We do this by placing the network.conf file, which enabled forwarding, in the sysctl.d folder. To make this automatic, we copy the network.conf file to the container in the Dockerfile. Now that we have this file in place, network forwarding is automatically applied every time the container starts.

/etc/sysctl.d/network.conf

net.ipv4.ip_forward=1

All that’s we have left to do is to set supervisor as our command which starts with the container. Below we see the Dockerfile used for this setup. This file shows multiple services, but keep in mind that snmpd, rsyslog, and nginx are not required for OpenVPN to work. These are present within a real-world docker build, and showcases how a container can run multiple services.

Dockerfile

FROM nginx:alpine

RUN mkdir -p /dev/net && \
    mknod /dev/net/tun c 10 200 && \
    chmod 600 /dev/net/tun

RUN apk update && \
    apk add openvpn rsyslog net-snmp net-snmp-tools iproute2 bash supervisor curl

ADD nginx.conf /etc/nginx/conf.d/default.conf
ADD network.conf /etc/sysctl.d/network.conf
ADD vpn/ /etc/openvpn/
ADD supervisor.conf /app/
ADD rsyslog.conf /etc/rsyslog.conf
ADD snmpd.conf /etc/snmp/snmpd.conf

ENV NGINX_PORT=8080

CMD /usr/bin/supervisord -c /app/supervisor.conf

On the very last line, we are using supervisor as an entry point using CMD, which causes supervisor to become the foreground process to keep the container alive.

Supervisord

Supervisor is a great complement to Docker, giving anyone the ability to run multiple light services within a single container. When you use supervisor, you set a base configuration, then set the configuration for each program you want to run. If a service goes down, supervisor will then restart it so the service is always running.

When working with Docker, we need to make sure none of our programs are running in daemon mode. This means they need to be running in the foreground. If we don’t, we’ll see errors in our deploy, showing the process ran for “< 1 second”, meaning it went right into the background, which supervisor sees as an error. So as we setup our configuration, we set each service to run in the foreground one way or another.

We set supervisor to run in nodaemon mode, as root, and log output to our log folder. Continuing our setup, we add a supervisor HTTP server, and then configure the programs to be run on startup.

We also need to make sure OpenVPN is set to run in the /etc/openvpn folder. This way it has the correct relative path for the configuration and the keys provided. Next we set our logs to output to the stdout and stderr. By doing this, we can see OpenVPN logs within the console logs. Most importantly, set the programs to autorestart. When the service stops, supervisor will immediately start that service again.

[supervisord]
logfile=/var/log/supervisord
logfile_maxbytes=0
loglevel=info
pidfile=/tmp/supervisord.pid
nodaemon=true
user=root

[unix_http_server]
file=/tmp/supervisor.sock

[program:openvpn]
directory=/etc/openvpn
command=/usr/sbin/openvpn --config /etc/openvpn/client.conf
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
stdout_logfile_maxbytes=0
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
autorestart=true
startsec=0
stopwaitsec=0
stopasgroup=true
killasgroup=true
stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr
stdout_logfile_maxbytes=0
stderr_logfile_maxbytes=0

[program:rsyslog]
command=/usr/sbin/rsyslogd -n
autorestart=true

[program:snmpd]
command=/usr/sbin/snmpd -f
autorestart=true

While looking at the commands set for each process, you’ll find each one is using a command flag. These services are explicitly run in the foreground so supervisor can directly monitor the output. If you don’t do this, you will get errors of processes running for “< 1 second”, meaning it went in the background. Since some services do not offer a foreground mode, so you may have to use a debug mode instead. As long as you keep in mind everything needs to be in the foreground, you won’t have issues with supervisor builds.

 

No Comments

Add your comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.