The problem
Start an OpenVPN server so that other users (i.e. workers) can connect to your internal network.
The network architecture looks like this:
graph LR
subgraph Internal Network
subgraph Docker Host<br/>192.168.13.20
GW[OpenVpn gateway <br/> 172.0.0.2 <br/> 192.168.255.1]
end
A[Host A <br/> 192.168.13.1]
B[Host B <br/> 192.168.13.2]
end
Client[Client <br/> 192.168.255.2] --> GW
GW --- A
GW --- B
We’ve got three separate subnets:
192.168.13.0/24
- the target internal network (i.e. company network)172.17.0.0/16
- docker internal bridge network192.168.255.0/24
- openvpn network, from which addresses are assigned to clients
We want to be able to connect through OpenVPN and access any host in the internal network. This will be achieved through routing and NAT (as opposed to bridging, where VPN clients would get IP addreses from internal network).
Docker to the rescue
Let’s start with OpenVPN docker image. The heavy lifting has already been done and there is an image at https://github.com/kylemanna/docker-openvpn/tree/master that not only starts OpenVPN server, but also facilitates registering new clients and generating profiles for them. There’s also a guide on how to use it with docker-compose. Sweet.
The docker-compose.yml
looks like this:
version: '2'
services:
openvpn:
cap_add:
- NET_ADMIN
image: kylemanna/openvpn
container_name: openvpn
ports:
- "1194:1194/udp"
restart: always
volumes:
- ./openvpn-data/conf:/etc/openvpn
Before running openvpn server itself, we need to generate it’s config and certificates, using scripts provided in the image.
> docker-compose run --rm openvpn ovpn_genconfig -u udp://VPN.SERVERNAME.COM
> docker-compose run --rm openvpn ovpn_initpki
You will be asked for private key passphrace and Common Name for server certificate, then DH keypairs will be generated. When it says it’s going to take a long time - it really is. Go make yourself a tea.
Configuration
ovpn_genconfig
script that we used in previous step generates two config files: openvpn.config
and ovpn_env.sh
. It takes a bunch of additional arguments to help you customize the config. You can either play along with it or edit the generated config manually. ovpn_genconfig
also does some basic iptables
configuration, which otherwise you would have to edit by hand, so I’ll stick with the script.
Let’s see what ovpn_genconfig
can do for us:
> docker-compose up -d openvpn
> docker-compose exec openvpn bash
bash-4.3# ovpn_genconfig -?
Invalid option: -?
usage: /usr/local/bin/ovpn_genconfig [-d]
-u SERVER_PUBLIC_URL
[-e EXTRA_SERVER_CONFIG ]
[-E EXTRA_CLIENT_CONFIG ]
[-f FRAGMENT ]
[-n DNS_SERVER ...]
[-p PUSH ...]
[-r ROUTE ...]
[-s SERVER_SUBNET]
optional arguments:
-2 Enable two factor authentication using Google Authenticator.
-a Authenticate packets with HMAC using the given message digest algorithm (auth).
-b Disable 'push block-outside-dns'
-c Enable client-to-client option
-C A list of allowable TLS ciphers delimited by a colon (cipher).
-d Disable default route
-D Do not push dns servers
-k Set keepalive. Default: '10 60'
-m Set client MTU
-N Configure NAT to access external server network
-t Use TAP device (instead of TUN device)
-T Encrypt packets with the given cipher algorithm instead of the default one (tls-cipher).
-z Enable comp-lzo compression.
In this scenario, we want to:
- enable NAT (
-N
) - push (
-p
) routes to the client, so it knows to it should use vpn network to connect to company network - use company’s internal dns for name resolution (
-n
) and set default domain tomycompany.net
(push adequatedhcp-option
to client)
Here’s the command to do that:
# remove old ovpn_env.sh
> docker-compose run --rm openvpn rm /etc/openvpn/ovpn_env.sh
# generate new config files
> docker-compose run --rm openvpn ovpn_genconfig -N -d -n 192.168.13.6 -u udp://vpn.mycompany.net -p "dhcp-option DOMAIN mycompany.net" -p "route 192.168.13.0 255.255.255.0" -p "route 172.17.0.0 255.255.0.0"
The generated config will be in ./openvpn-data/conf
and it should look like this (you may also want to take a look at ovpn_env.sh
):
server 192.168.255.0 255.255.255.0
verb 3
key /etc/openvpn/pki/private/dev.legimi.com.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/dev.legimi.com.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key
key-direction 0
keepalive 10 60
persist-key
persist-tun
proto udp
# Rely on Docker to do port mapping, internally always 1194
port 1194
dev tun0
status /tmp/openvpn-status.log
user nobody
group nogroup
### Push Configurations Below
push "dhcp-option DNS 192.168.13.6"
push "dhcp-option DOMAIN legimi.com"
push "route 192.168.13.0 255.255.255.0"
push "route 172.17.0.0 255.255.0.0"
Client profiles
Now that openvpn server is configured, make sure it is up and running:
> docker-compose up
To connect, we’ll need a client profile.
# Generate a client certificate (you will be asked for a passphrase)
> export CLIENTNAME="your_client_name"
> docker-compose run --rm openvpn easyrsa build-client-full $CLIENTNAME
# Generate client profile for openvpn
> docker-compose run --rm openvpn ovpn_getclient $CLIENTNAME > $CLIENTNAME.ovpn
Finally, connect to openvpn:
> openvpn -c your_client_name.ovpn
If everything goes fine, you should be able to ping the internal network (i.e. 192.168.13.1 host).
Troubleshooting
If you cannot connect to openvpn:
- make sure
openvpn
container starts without errors and port 1194 is exposed at docker host - make sure port 1194 is open at vpn url you specified when configuring (vpn.mycompany.net)
If you can connect to openvpn, but cannot ping internal network:
- check if the client machine has an IP address from openvpn network assigned (
192.168.255.x
) - ping
192.168.255.1
(that’s the default gateway for openvpn connections). If you can’t, there’s probably something wrong with openvpn config. - check
openvpn
container’s internal IP (172.17.0.x
) and ping it. - check routing tables on client machine. There should be routes for networks
172.17.0.0
and192.168.13.0
going through Gateway192.168.255.1
. - check if you can ping internal network from
openvpn
container