Set Up OpenVPN To Get Access To Your Local Home Network

Sometimes when I experiment with something and need to forward some ports on router to test it - I wish I was at home to do so. But Igor, remote management exist in 2019? Yes, but no way I am exposing my router configuration GUI to the world. World is a scary place and if my Wi-Fi password is 20 symbols long - my management GUI credentials are a mess (#wontfix). So I came up with idea of OpenVPN server in Docker just for home local network access. I plan to set it up on my PC server at home and document it here while I am riding to Narva in train. Let's start :)


Requirements

I made a feature for our devices at work some time ago - now we can set them up as OpenVPN server. And I know that basic things that you need for this on embedded Linux system is:

  • Easy-RSA
  • OpenVPN

In our case we also need Docker. Make sure you have it installed.

Dockerfile

I researched if those packages exist for Alpine Linux and yes, they do. Also I found where Easy-RSA stores its files on Alpine. So we will start with basic Dockerfile:

FROM alpine:3.8

RUN apk add --no-cache openvpn easy-rsa \
    && mkdir -p /etc/openvpn/data

To run OpenVPN server from configuration all we need is openvpn --config server.conf command. Add this section to Dockerfile (some files and their purpose will be revealed later):

COPY server.conf /etc/openvpn/server.conf
COPY client.conf /client.conf
COPY scripts/setup_server /setup_server
COPY scripts/create_client /create_client
WORKDIR /etc/openvpn

CMD mkdir -p /dev/net && \
    if [ ! -c /dev/net/tun ] ; then mknod /dev/net/tun c 10 200 ; fi \
    && openvpn --config server.conf

When running container you can override CMD. This way we don't need another Dockerfile for running scripts below.

Scripts

Generating CA and server certificate/key

Create vars file from vars.example (copy and edit it). This file will be used to create all certificates and keys without need for your input. It should look like this:

if [ -z "$EASYRSA_CALLER" ]; then
        return 1
fi

set_var EASYRSA_DN              "org"
set_var EASYRSA_REQ_COUNTRY     "EE"
set_var EASYRSA_REQ_PROVINCE    "Harjumaa"
set_var EASYRSA_REQ_CITY        "Tallinn"
set_var EASYRSA_REQ_ORG         "Company"
set_var EASYRSA_REQ_EMAIL       "example@example.com"
set_var EASYRSA_REQ_OU          "Example"
set_var EASYRSA_REQ_CN          "vpn.example.com"
set_var EASYRSA_BATCH           "yes"

The only variable you should not touch is EASYRSA_BATCH. It makes creation non-interactive.

To generate CA and server certificate/key we will use this script inside container (scripts/setup_server we COPY-ed into container):

#!/bin/sh
set -e

mkdir -p /etc/openvpn/data/ccd

rm -rf /etc/openvpn/data/easyrsa || true
cp -r /usr/share/easy-rsa/* /etc/openvpn/data/easyrsa
cd /etc/openvpn/data/easyrsa

# Building CA
./easyrsa init-pki
./easyrsa build-ca nopass

# Building server cert & key
./easyrsa build-server-full server nopass
./easyrsa gen-dh
openvpn --genkey --secret pki/private/ta.key

Run sudo ./setup_server $PORT $SUBNET $DOMAIN_OR_IP which is in repository root folder. Example: sudo ./setup_server 1194 10.8.0.0 vpn.example.com. It will take some time. You should have CA and certificate/key for server saved in data/easyrsa/pki folder on host after script finished.

Now you can add something like push "route 192.168.1.0 255.255.255.0 10.8.0.1 1000" to server.conf to enable access to LAN. After that just run sudo ./run and it should start OpenVPN server.

Generating .ovpn files for clients

To generate client's .ovpn file with embedded certificates and keys we will use this script inside container (scripts/create_client we COPY-ed into container):

#!/bin/sh
set -e

if [ -z "$2" ] ; then
    if [ -z "$1" ] ; then
        echo "Client name should be supplied as first argument"
        exit 1
    fi
    echo "No static IP provided for client!"
fi

cd /etc/openvpn/data/easyrsa
./easyrsa build-client-full ${1} nopass

CA_CERT=pki/ca.crt
CERT_DIR=pki/issued
KEY_DIR=pki/private
OUTPUT_DIR=/etc/openvpn/data/clients
BASE_CONFIG=/client.conf

mkdir -p ${OUTPUT_DIR}

(cat ${BASE_CONFIG}; \
    echo -e "<ca>\n$(cat ${CA_CERT})\n</ca>\n"; \
    echo -e "<cert>\n$(cat ${CERT_DIR}/${1}.crt)\n</cert>\n"; \
    echo -e "<key>\n$(cat ${KEY_DIR}/${1}.key)\n</key>\n"; \
    echo -e "<tls-auth>\n$(cat ${KEY_DIR}/ta.key)\n</tls-auth>") \
    > ${OUTPUT_DIR}/${1}.ovpn

if [ ! -z "$2" ] ; then
    mkdir -p /etc/openvpn/data/ccd
    echo "ifconfig-push ${2} 255.255.255.0" > /etc/openvpn/data/ccd/${1}
fi

Run sudo ./create_client $CLIENT_NAME $STATIC_IP which is in repository root folder. Example: sudo ./create_client myclient 10.8.0.4, NB! $STATIC_IP is optional. Make sure you have running container (see command run from above)! You should have client's .ovpn file saved in data/clients folder on host after script finished.

Linux Tweaks

Now you need to make some tweaks in Linux to make it work. First one is setting net.ipv4.ip_forward=1 in /etc/sysctl.conf to make Linux forward packets from OpenVPN tunnel. Next you need to set up iptables masking, since devices in your local network don't know who is the host from OpenVPN network that tries to reach them:

# Install iptables persistent but do not save rules when they ask
sudo apt install iptables-persistent

# Create file with rule for masking tunnel IP with server IP (192.168.1.2)
sudo nano /etc/iptables/rules.v4

*nat
-A POSTROUTING -o eth0 -j SNAT --to-source 192.168.1.2
COMMIT

# From Ubuntu 16.04 service is named netfilter-persistent
sudo service netfilter-persistent start

Reboot your server just in case. And it should work.

Test Connection On Client Side

Take .ovpn file you created earlier to another device and test your connection. It should work.

And I forgot to forward port to PC server's OpenVPN server before I left home :)

Happy Experimenting!

comments powered by Disqus