Reverse VPN: turn any private device into public cloud server

3 Apr

NOTICE there might still be some bugs and sparsely commented snippets in this document, but if you follow it, it should work.

Video Demo https://youtu.be/O7D7UkA3IKQ by ravallblog

Typically when you think of a VPN, you think of a private office network behind a firewall that you want to securely access from home or another office.

But what about when your home server hosting your blog and your business site is stuck behind an apartment firewall?

Or what if you want to be able to take a device with you, attach it to any network, and begin using it’s web services immediately?

Effectively, we’re turning the VPN inside out. A reverse VPN, if you will.

Use a public VPS as the VPN server

Digital Ocean, ChunkHost, AWS, or a server (or Raspberry Pi) in your office, home, or a friend’s home – anything that you can get root access to and give a public network access (even if it’s with a dynamic dns service).

Install some tools

NOTE: You must be using bash (the default shell / terminal) — not zsh or fish or anything fancy — legit bash.

Log into your VPS (ssh user@example.com) and then continue:

Install the Tools

bash
sudo apt-get install --yes openvpn

Switch to the root user

Be the root you know in your heart. You’re going to have to do pretty much everything as root from this point on, so it makes sense just to switch (if you aren’t root already).

sudo su -

Get the easy-rsa 2.x tools from github

The new line of tools is 3.x, but I’m not familiar with those yet and these work, so these are what we will use. 😀

# Install git
sudo apt-get install --yes git
# Copy all the files from the internet to your computer
git clone https://github.com/OpenVPN/easy-rsa.git ~/easy-rsa
pushd ~/easy-rsa/
git checkout 'release/2.x'
popd

# Copy all the files into the right place
rsync -av ~/easy-rsa/easy-rsa/2.0/ /etc/openvpn/easy-rsa/

Next we update the config

# Push into the easy-rsa directory
pushd /etc/openvpn/easy-rsa/
# Then open the ./vars file, which is a config file
# You'll need to take a look around and season to taste
# It's nothing fancy, just names and preferences
vim ./vars

Note a few things that you probably should change:

/etc/openvpn/easy-rsa/vars:

# change all of the things down at the bottom that look like these
# (these are given as an example for me, yours should be different)
export KEY_COUNTRY="US"
export KEY_PROVINCE="Utah"
export KEY_CITY="Provo"
export KEY_ORG="AJ ONeal Tech LLC"
export KEY_EMAIL="awesome@coolaj86.com"
export KEY_CN="*.coolaj86.com"
export KEY_NAME="AJ ONeal"
export KEY_OU="Department of Docs & Blogging"

Now we’ll get to work

Then load all of the vars into your environment

# load the variables
source ./vars

# delete any and all previous keys.
./clean-all

mkdir -p ./keys
ls -lah ./keys

# TODO (note to self) show the plain openssl commands (it's not that hard, y'know?)

# Build a certificate authority for your organization
./build-ca
# NOTE: answer the questions

sudo apt-get install --yes tree
tree ./keys/
# The out put will look like this:
# ./keys/
# ├── ca.crt
# ├── ca.key
# ├── index.txt
# └── serial

# Inpsect the keys, just for fun
openssl x509 -text -noout -in ./keys/ca.crt
openssl x509 -text -noout -in ./keys/ca.key

# When prompted, enter the same CN as above - coolaj86.com in my case
# No Password
./build-key-server rvpn.coolaj86.com

tree ./keys/
# ./keys/
# ├── 01.pem
# ├── ca.crt
# ├── ca.key
# ├── index.txt
# ├── index.txt.attr
# ├── index.txt.old
# ├── rvpn.coolaj86.com.crt
# ├── rvpn.coolaj86.com.csr
# ├── rvpn.coolaj86.com.key
# ├── serial
# └── serial.old
# The less you're doing with your computer, the longer this will take.
# If you're downloading a big file or something or https, it should only take a minute.
# Otherwise it could take several minutes
./build-dh

ProTipTM:

If you use https, you can download a file over and over again (such as a large image) with curl and you’ll get a different set of bits each time because the random seed for the https connetion will be different. This will give you tons of entropy if you run it while generating keys.

while true; do sleep 1; curl 'https://pbs.twimg.com/media/A7hdDEnCYAA8oky.jpg:large' > /dev/null; done
openvpn --genkey --secret keys/ta.key
curl -fssL https://gist.githubusercontent.com/coolaj86/18b92ee350b38f18fca6/raw/server.conf \
  -o /etc/openvpn/server.conf
vim /etc/openvpn/server.conf 

Configure the Server’s Network and Firewall settings

vim /etc/sysctl.conf

/etc/sysctl.conf:

# uncomment the following
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

TODO: the router may need to be setup to forward 1194 to the pi

sysctl -p

ufw status
ufw allow 1194/udp

# IMPORTANT change the 192.168.1.4 to your server's IP
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j SNAT --to-source 192.168.1.4

touch /etc/firewall-openvpn-rules.sh
chmod 700 /etc/firewall-openvpn-rules.sh

# this will be open, you'll add the stuff below
vim /etc/firewall-openvpn-rules.sh

/etc/firewall-openvpn-rules.sh:

#!/bin/bash
# IMPORTANT change the 192.168.1.4 to your server's IP
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j SNAT --to-source 192.168.1.4

Now we’ll make sure that this firewall rule is added on each boot

vim /etc/network/interfaces

# You'll see a line that looks like this
iface eth0 inet dhcp

# underneath it you'll need to add this line
# (it shouldn't matter whether you use a tab or spaces)
        pre-up /etc/firewall-openvpn-rules.sh

And finally we’re ready to restart the openvpn service

/etc/init.d/openvpn stop
/etc/init.d/openvpn start

Use a private device as a Web Server

I must note that this should be the process:

  • create your client key on the client,
  • create a client.CSR.PEM (certificate signing request)
  • send the client.CSR.PEM to the server
  • the server creates a client.CRT.PEM from the client.CSR.PEM

However, if the server is compromised then the server private key and certificate are compromised which means that every certificate the server has signed are now invalid.

On the SERVER

We’re going to create the client’s keys and OVPN file.

# For example
./build-key client-xyz.example.com

Generating Client Keys

Each device will need its own keys.

Use ./build-key if the device is meant to be always-on and not require user interaction, like a raspberry pi home server.

# Example
# this will NOT require a passphrase
./build-key homeserver.coolaj86.com

# just showing the new files (omitting *.old backups)
tree ./keys/
# ./keys/
# ├── 02.pem
# ├── homeserver.coolaj86.com.crt
# ├── homeserver.coolaj86.com.csr
# └── homeserver.coolaj86.com.key

Use ./build-key-pass if the device is something you log into, such as your laptop.

# Example
# this WILL require a passphrase
./build-key-pass macbook.coolaj86.com

# just showing the new files (omitting *.old backups)
tree ./keys/
# ./keys/
# ├── 03.pem
# ├── macbook.coolaj86.com.crt
# ├── macbook.coolaj86.com.csr
# └── macbook.coolaj86.com.key

Some devices (iOS, Android, Macbook(?)) need a des3 version of the key, might as well do that now:

# this will require your previous passphrase, and to create a passphrase (it can be the same)
openssl rsa -in ./keys/macbook.coolaj86.com.key -des3 -out ./keys/macbook.coolaj86.com.3des.key
curl http://checkip.dyndns.org
curl https://coolaj86.com/services/whatsmyip

vim /etc/openvpn/easy-rsa/keys/Default.txt

/etc/openvpn/easy-rsa/keys/Default.txt:

client 
dev tun 
proto udp 
remote coolaj86.com 1194 
resolv-retry infinite 
nobind 
persist-key 
persist-tun 
mute-replay-warnings 
ns-cert-type server 
key-direction 1 
cipher AES-128-CBC 
comp-lzo 
verb 1 
mute 20 

MakeOVPN.sh:

#!/bin/bash 
 
# Default Variable Declarations 
DEFAULT="Default.txt" 
FILEEXT=".ovpn" 
CRT=".crt" 
KEY=".3des.key" 
CA="ca.crt" 
TA="ta.key" 
NAME="${1}"
 
if [ -z "${NAME}" ]; then
  #Ask for a Client name 
  echo "Please enter an existing Client Name:"
  read NAME 
fi
 
 
#1st Verify that client’s Public Key Exists 
if [ ! -f $NAME$CRT ]; then 
 echo "[ERROR]: Client Public Key Certificate not found: $NAME$CRT" 
 exit 
fi 
echo "Client’s cert found: $NAME$CR" 
 
 
#Then, verify that there is a private key for that client 
if [ ! -f $NAME$KEY ]; then 
 echo "[ERROR]: Client 3des Private Key not found: $NAME$KEY" 
 exit 
fi 
echo "Client’s Private Key found: $NAME$KEY"
 
#Confirm the CA public key exists 
if [ ! -f $CA ]; then 
 echo "[ERROR]: CA Public Key not found: $CA" 
 exit 
fi 
echo "CA public Key found: $CA" 
 
#Confirm the tls-auth ta key file exists 
if [ ! -f $TA ]; then 
 echo "[ERROR]: tls-auth Key not found: $TA" 
 exit 
fi 
echo "tls-auth Private Key found: $TA" 
 
#Ready to make a new .opvn file - Start by populating with the default file 
cat $DEFAULT > $NAME$FILEEXT 
 
#Now, append the CA Public Cert 
echo "<ca>" >> $NAME$FILEEXT 
cat $CA >> $NAME$FILEEXT 
echo "</ca>" >> $NAME$FILEEXT
 
#Next append the client Public Cert 
echo "<cert>" >> $NAME$FILEEXT 
cat $NAME$CRT | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >> $NAME$FILEEXT 
echo "</cert>" >> $NAME$FILEEXT 
 
#Then, append the client Private Key 
echo "<key>" >> $NAME$FILEEXT 
cat $NAME$KEY >> $NAME$FILEEXT 
echo "</key>" >> $NAME$FILEEXT 
 
#Finally, append the TA Private Key 
echo "<tls-auth>" >> $NAME$FILEEXT 
cat $TA >> $NAME$FILEEXT 
echo "</tls-auth>" >> $NAME$FILEEXT 
 
echo "Done! $NAME$FILEEXT Successfully Created."
 
#Script written by Eric Jodoin
pushd /etc/openvpn/easy-rsa/keys
./MakeOVPN.sh client-xyz.example.com


# copy this and paste it on the client
cat client-xyz.example.com.ovpn

On the Client

You’ve just got to paste that config onto the client and start the connection.

sudo apt-get install --yes openvpn
# note it's important to use '&' rather than ';' or '&&', even though it's a daemon
OVPN_CLIENT="client-xyz"
sudo kill --signal TERM $(cat /var/run/ovpn."${OVPN_CLIENT}".pid)
sleep 0.5
sudo kill --signal KILL $(cat /var/run/ovpn."${OVPN_CLIENT}".pid)
sudo openvpn --config "${OVPN_CLIENT}".ovpn --daemon --writepid /var/run/ovpn."${OVPN_CLIENT}".pid
# you can later kill the process with
# sudo bash -c 'kill $(cat /var/run/client-xyz.pid)'

Check if you were successful.

ifconfig
ping -c 1 10.8.0.1

If you were successful you’ll see a tun0 device with an IP address and you should be able to ping the VPN server.

Connect on Startup

If you’re lucky the openvpn service was already configured to start on boot and all you have to do is move your config file to the correct directory and change the config file to say that it will be loaded

sudo mv client-xyz.example.com.ovpn /etc/openvpn/client-xyz.conf
sudo vim /etc/default/openvpn
# uncomment the following
# AUTOSTART="client-xyz"

upstart

vim /etc/init/myopenvpn
# OpenVPN autostart on boot upstart job

start on runlevel [2345]
stop on runlevel [!2345]

respawn

exec /usr/sbin/openvpn --status /var/run/openvpn.client.status 10 --cd /etc/openvpn --config /etc/openvpn/client.conf --syslog openvpn

By AJ ONeal

If you loved this and want more like it, sign up!