Routing Traffic Through OpenVPN Multiple Hops

This was something I always wanted to do:
Using a VPN (public or private) to add a layer of security & privacy is really easy today – you can use any one of the plentiful services for VPN, and most of them a reasonably priced. But, what can you do if you want to add an extra level of security — what if you want to make a double or triple hop VPN connection? so the end point to the internet will not have your real IP?

At first, it sounds like a fairly simple task – install two clients of public VPN supplier and you are done – but I’ve found that in many cases, this would simply won’t work, either because the VPN clients interfere with one another (using same TAP etc) or because of troubles in routing the connection of the second client through the existing VPN tunnel of the first client.

So I’ve decided to try and do it on my own, and practicing my IP routing and UFW rules knowledge that I haven’t been using for a long time…

For this tutorial, we will be using the following terminology:

  • EP = End point, our computer we want to connect to the VPN
  • A = server A, the server 1 hop from EP, also known as “NEAR”
  • B = server B, the server 2 hops from EP, also known as “FAR”

although this tutorial will work with only 2 hops, the process should work the same with higher number of hops as well.

prerequisites:

  • 2 Ubuntu servers, we will be using Ubuntu 14.04 LTS.
  • 1 Ubuntu desktop
  • root access (or sudo privilege) to all 3 computers

How it will work

  • to make sure no data is accidently sent from EP to the internet, EP will boot with no internet access at all. this will be done with a ufw rule.
  • EP will then establish a VPN tunnel to server A , which we will name tun0.
  • Using tun0, EP will then establish a VPN tunnel to server B, which we will name as tun1.
  • at each step we will:
    • update ufw to allow communication to the specific servers only.
    • change our routing to set the gateway to forward all of our traffic through.

Step 1 – installing needed software

On EP we will create the needed security keys, certificates and CA for this whole process to work, so we will use easy-rsa and not only openvpn.

on EP:

sudo apt-get update
sudo apt-get install openvpn easy-rsa
sudo update-rc.d openvpn disable # we dont want openvpn to start automatically.

make sure EP is using Google’s DNS servers. To do so, edit /etc/network/interfaces and remove any existing dns-nameservers statements.
Add the following

dns-nameservers 8.8.8.8 8.8.4.4

and reboot.

on A & B:


sudo apt-get update
sudo apt-get install openvpn

 

step 2 – create CA, certificate and Deffie-Helman keys

we will be using easy-rsa‘s shell script to create the needed keys.

sudo -i
cd /usr/share/easy-rsa
. ./vars
./clean-all
./build-ca --batch
./build-dh
./build-key-server --batch serverA
./build-key-server --batch serverB
./build-key --batch client

This should have created the following files in /usr/share/easy-rsa/keys folder:

Filename Needed By Purpose Secret
ca.crt All Root CA certificate No
ca.key key signing machine only (in our case, EP) Root CA key Yes
client.crt EP only EP Certificate No
client.key EP only EP Key Yes
dh2048.pem Server A and Server B Diffie Hellman parameters No
serverA.crt server A only Server A certificate No
serverA.key Server A only Server A key Yes
serverB.crt server B only Server B certificate No
serverB.key Server B only Server B key Yes

 

step 3 – configure openVPN on servers

The vpn servers (A and B) in our scenario will accept connection from our EP, the authentication would be using the public key created on the EP in step 2. we will go through the following 3 steps:

  1. make a config file for openvpn
  2. enable ip forwarding on the server
  3. push the openvpn keys to the server

first, make sure the /etc/openvpn directory exists, and create a directory to keep our keys:

sudo mkdir /etc/openvpn
sudo mkdir /etc/openvpn/keys
sudo chown ${USER} /etc/openvpn/keys

then, create a file /etc/openvpn/server.conf and insert the following configuration:

port 1194
proto udp
dev tun0
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/server.crt
key /etc/openvpn/keys/server.key
dh /etc/openvpn/keys/dh2048.pem
server 10.8.0.0 255.255.255.0   # make sure the net is different for every hop
ifconfig-pool-persist ipp.txt
keepalive 10 120
comp-lzo
user nobody
group nogroup
persist-key
persist-tun
status openvpn-status.log
log-append /var/log/openvpn.log
verb 3

To enable IP forwarding, we need to allow it in the kernel <==make sure> and to enable NATing in the iptables of the machine

sudo -i # change to root

# enable ip forwarding immediatly, and after reboot.
echo 1 > /proc/sys/net/ipv4/ip_forward
sed -i.bkup 's/^#net.ipv4.ip_forward=1$/net.ipv4.ip_forward=1/' /etc/sysctl.conf

# enable FORWARD policy using ufw
sed -i.bkup 's/^DEFAULT_FORWARD_POLICY="DROP"$/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw

# make ufw add routing rules to iptables, upon startup
EXTERNAL_INTERFACE=(`ip route show | grep "default via" | grep -oE "dev [^ ]+" | grep -oE "[^ ]+$"`)
echo >> /etc/ufw/before.rules
echo \# allow NAT >> /etc/ufw/before.rules
echo *nat >> /etc/ufw/before.rules
echo :POSTROUTING ACCEPT [0:0] >> /etc/ufw/before.rules
#make sure you change 10.8.0.0/8 to whatever you set in this server
echo -A POSTROUTING -s 10.8.0.0/8 -o ${EXTERNAL_INTERFACE} -j MASQUERADE >> /etc/ufw/before.rules
echo COMMIT >> /etc/ufw/before.rules

# make sure the ufw won't block you out of your server
ufw allow ssh

# allow UDP port 1194 to allow for the VPN traffic to pass the firewall
ufw allow proto udp from any to any port 1194

# enable ufw (now & on startup)
ufw enable 

the configuration described, must be run on both server A and server B.

now, move the following files from EP to the servers: ca.crt, dh2048.pem, server.crt, server.key using a secure method

scp /usr/share/easy-rsa/keys/ca.crt <server A user>@<server A ip>:/etc/openvpn/keys/
scp /usr/share/easy-rsa/keys/ca.crt <server B user>@<server B ip>:/etc/openvpn/keys/
scp /usr/share/easy-rsa/keys/dh2048.pem <server A user>@<server A ip>:/etc/openvpn/keys/
scp /usr/share/easy-rsa/keys/dh2048.pem <server B user>@<server B ip>:/etc/openvpn/keys/
scp /usr/share/easy-rsa/keys/serverA.crt <server A user>@<server A ip>:/etc/openvpn/keys/server.crt
scp /usr/share/easy-rsa/keys/serverB.crt <server B user>@<server B ip>:/etc/openvpn/keys/server.crt
scp /usr/share/easy-rsa/keys/serverA.key <server A user>@<server A ip>:/etc/openvpn/keys/server.key
scp /usr/share/easy-rsa/keys/serverB.key <server B user>@<server B ip>:/etc/openvpn/keys/server.key

finally, reboot the servers, to allow for the OpenVPN service to load the keys & OS changes to take place.

reboot

you can check if the server is listening by running:

netstat --listening

you should see: “udp        0      0 *:openvpn               *:*

 

step 4 – configure openVPN on client

 

we will use 2 configuration files on the client, the first will create the VPN tunnel to server A, and the second will create the tunnel to server B.

first, create a file /etc/openvpn/first.conf and insert the following configuration:

client
dev tun0
dev-type tun
proto udp
port 1194
remote <SERVER A IP> 1194
resolv-retry infinite
nobind
user nobody
group nogroup 
persist-key
persist-tun 
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/client.crt
key /etc/openvpn/keys/client.key

remote-cert-tls server 
comp-lzo
status openvpn_A-status.log
log-append /var/log/openvpn_A.log
verb 3

secondly, create a file /etc/openvpn/second.conf and insert the following configuration:

client
dev tun1
dev-type tun
proto udp
port 1194
remote <SERVER B IP> 1194
resolv-retry infinite
nobind
user nobody
group nogroup 
persist-key
persist-tun 
ca /etc/openvpn/keys/ca.crt
cert /etc/openvpn/keys/client.crt
key /etc/openvpn/keys/client.key

remote-cert-tls server 
comp-lzo
status openvpn_B-status.log
log-append /var/log/openvpn_B.log
verb 3

now, lets create the needed /etc/openvpn/keys directory:

sudo mkdir /etc/openvpn
sudo mkdir /etc/openvpn/keys
sudo chown ${USER} /etc/openvpn/keys

and of course, copy the needed keys to that directory

cp /usr/share/easy-rsa/keys/ca.crt /etc/openvpn/keys/ca.crt
cp /usr/share/easy-rsa/keys/client.crt  /etc/openvpn/keys/client.crt
cp /usr/share/easy-rsa/keys/client.key /etc/openvpn/keys/client.key

 

step 5 – starting the tunnels and routing

At this point we have all nodes ready to connect. We have to configure the route with the following points in mind:

  • Make sure the second tunnel is routed through the first tunnel
  • Make sure that all of our traffic is routed through the second tunnel
  • Make sure that the VPN traffic to A is routed through our starting default gateway
  • Make sure that the VPN traffic to B is routed through the first tunnel

We would have to do this in steps:
First step – connect to A

sudo -i 
cd /etc/openvpn

NEARIP=<SERVER A>
FARIP=<SERVER B>

NEARINNERIP=<server ip at hop 1> #should be 10.8.0.1, if you used the default value in section you set at step3> 
FARINNERIP=<server ip at hop 2>

REALGW=`ip route show | grep "default via" | cut -d' ' -f3 | head -n 1`
LOCALIFACE=`ip route show | grep "default via" | cut -d' ' -f5 | head -n 1`
LOCALIP=`ifconfig ${LOCALIFACE} | grep -o "inet addr:[0-9\.]*" | cut -d':' -f2`
LOCALMASK=`ifconfig ${LOCALIFACE} | grep -o 'Mask:[0-9\.]*' | cut -d':' -f2`

IFS=. read -r i1 i2 i3 i4 <<< ${LOCALIP}
IFS=. read -r m1 m2 m3 m4 <<< ${LOCALMASK}
LOCALNET=$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).$((i4 & m4))
LOCALNETPERFIX=`ip addr show ${LOCALIFACE} | grep -o 'inet [0-9\./]*' | cut -d'/' -f2`

# cleaning everything and starting new:
ip route flush table main
ip route add ${LOCALNET}/${LOCALNETPERFIX} dev ${LOCALIFACE}
ip route add 0.0.0.0/0 via ${REALGW}

#make sure that Server A is routed through our current GW, and not through our tunnels.
ip rule add to ${NEARIP} table main
ip route add to ${NEARIP} via ${REALGW} table main

# connect to A
openvpn --config first.conf --daemon first

# check for "Initialization Sequence Completed" in /var/log/openvpn_A.log
tail -n1   /var/log/openvpn_A.log | grep "Initialization Sequence Completed"

Second step – route all traffic through tun0

ip rule add to ${FARIP} table main   
ip rule add to  ${NEARINNERIP} table main
ip route add to ${FARIP} dev tun0     # make sure we tunnel the access to server B IP

ip route del default via ${REALGW} dev ${LOCALIFACE}  # remove the current GW
ip route add default dev tun0                         # make tun0 the default GW


 
This is a good stop to check your external ip (by running curl https://canhazip.com, you should get the IP of server A

Third step – Connect to B

# connect to B
openvpn --config second.conf --daemon second

# check for "Initialization Sequence Completed" in /var/log/openvpn_B.log
tail -n1   /var/log/openvpn_B.log | grep "Initialization Sequence Completed"

Forth step – Route everything through tun1

ip route del default dev tun0
ip route add default dev tun1
 

Run curl https://canhazip.com again, now you should get the IP of server B.

 

And finally – some clean ups

The EP has no need to keep the servers private and public keys, so decide rather you want to keep all of the keys in the /usr/share/easy-rsa/keys/ directory.

 

Going forward

One could many other means for protecting this connection:

  • Use ufw to deny any communication not done through the routes, specifically allow only udp/1194 from your local interface and tun0.
  • instead of stopping the startup of OpenVPN, we can change the location we’ve put the conf files, and run them with a bash script.
  • Use UFW to deny any access to out server A and server B that is not from out ssh. allow server B to be connected to port 1194/udp only from server A. Allow access to port 1194/udp on server A from everywhere.
  • create a bash script to simplify the whole process described here, we only need a few parameters from the user, and everything else could be done automatically.

Leave a Reply

Your email address will not be published. Required fields are marked *