Hybrid cloud from home using DigitalOcean

This is something I had been wanting to do for awhile and the concepts were always floating around in my head. At a high level, I wanted to join a network at my home to a cloud provider. Why? Mostly just because I wanted to see if I could do it and how it could be done – this weekend I had a reason and I did it. More specifically, I wanted to see how I could “get” my own static IP from a cloud provider and forward connections on ports to the DigitalOcean VM back to my house

Disclaimer:
What I am doing below is not the absolutely most secure thing. You should understand the risks involved with this and know that what I am providing below is a “to get going” kind of tutorial

 — — —

I had recently been doing a lot of tinkering with OcServ (http://www.infradead.org/ocserv/) and OpenConnect. These are the open source equivalents of Cisco AnyConnect SSL VPN client and server. BIG shoutout to this software suite…completely dead easy to use and the developer is very active, responsive, and helpful on his gitlab (https://gitlab.com/ocserv/ocserv). Internally in my home network I have a Juniper SRX firewall that I wanted to test this out on and forward the IKE ports back to my house. My home network is 172.16.2.0/24

Ultimately I will be documenting what you need and trying to keep my Juniper bits out of it. But I will cover that too so you know what you need if you ever want to tinker like this

Heres what you need

  1. A DigitalOcean VM with private networking enabled
  2. A Raspberry PI 3 Model B – or an equivalent device with two network connections. A laptop works too. You just need one a device with two NIC’s so one can route for the switch and the other (your initial default route) through the internet: 172.16.2.1
  3. ocserv installed on the DigitalOcean VM
  4. openconnect installed on the Raspberry PI 3 (or equivalent device)
  5. An unmanaged switch (managed would probably work too, but unmanaged ensures no VLAN tagging issues / configurations)
  6. A secondary network device (in this case, it would be my Juniper device): 172.16.2.2

I primarily use Fedora and CentOS so adjust accordingly (ex: CentOS disables asymmetrical routing by default, which we need)

tldr;

The “too lazy didnt read” is simply this:

  1. Connect your PI to the switch and configure it to act as a router for your other device (iptables, etc)
  2. On the DigitalOcean VM, install ocserv and configure it. Have it work with your private network assigned on eth1. A small /29 or /28 should suffice for what IPs to hand out
  3. On your PI, run openconnect and connect to your DigitalOcean external IP – this will give you a VPN tunnel to DigitalOcean private network. Note the IP you have been assigned. You can find this by seeing the line “Connected as <IP>”
  4. On the DigitalOcean VM, set a route for 172.16.2.0/24 (or whatever your home network is) to route to your tun0 interface IP on your PI (the IP you noted in step 2)
  5. Ping 172.16.2.1 (your PI, or whatever IP you have on it) and notice you get a response. You should be able to ping the other device you have behind the switch
  6. You can now use iptables to port forward stuff back to your house via the eth0 external IP

Summary

We are going to make our PI act as a router for the home network. Once we confirm we can move traffic to and from the internet with that setup we are going to connect via openconnect to our DigitalOcean external IP. The DigitalOcean OCServ software will be responsible for handing out a small /29 set of 10.x.x.x/x IPs from the /16 that DigitalOcean gave us. OCServ will tell connecting clients (our PI) that it will take one of those IPs with its gateway being the first IP in the range. Then, OCServ will tell the connecting client that their new default route is now it (OCServ internal IP on DigitalOcean VM).

What this achieves is it makes our PI have its next-hop as the DigitalOcean VM via a private SSL VPN tunnel and thus, any traffic coming in to the PI will be routed out up through DigitalOcean through this VPN and then egress as our external IP in DigitalOcean. Likewise, you can also port forward traffic from the external IP on DigitalOcean VM or on the internal private network assigned to you by DigitalOcean back to your house

This can probably be achieve by something else like OpenSwan or the similar, however, an SSL VPN like this to me is more lightweight and easier to configure; plus, I am going to be setting up an IPSEC VPN from my Juniper devices behind the switch, so the less in the way the better (although I don’t really care about performance). I had already setup OCServ a few times as it was so I went this route

Details

First we’re going to make sure we have what we need at the house side. Substitute PI and Juniper for what you’ve selected

Here is how this looks at a high level from the house side

          Raspberry Pi (or equivalent)
                     |
                   Switch
                     |
                   Juniper

On the PI

We need to enable it as a router which means iptables and sysctl. First install iptables and its utilities. Since this is CentOS 7 for me, I needed to install them

yum install -y iptables iptables-services iptables-utils

And I have prepared a little script to do the “making routing” portion minus one detail. Don’t run this yet, just save it as “/root/mkrouter”

#!/usr/bin/env bash

# Which port is connected to the switch
SWITCH_INTERFACE="eth0"

# Which port is your default route / upstream to the internet
EGRESS_INTERFACE="wlan0"

ip link set up ${SWITCH_INTERFACE}
ip addr add $1 dev ${SWITCH_INTERFACE}
sysctl -w net.ipv4.ip_forward=1
service iptables start
iptables -t nat -A POSTROUTING -o ${EGRESS_INTERFACE} -j MASQUERADE
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i ${SWITCH_INTERFACE} -o ${EGRESS_INTERFACE} -j ACCEPT

I have noticed that iptables includes reject rules by default, so we are going to delete those

Open /etc/sysconfig/iptables and delete the two lines that contain REJECT

Now we need to enable asymmetrical routing. The below command is a pretty big hammer but somewhat necessary. I have seen net.ipv4.conf.all.rp_filter not always work sometimes unless its persisted and setup to configure it at reboot

for i in $(sysctl -a 2> /dev/null | grep -i rp_filter | grep -iv arp_filter | awk '{ print $1 }'); do sysctl -w $i=2; done

Dont forget that you’ll want to make these settings persistent via /etc/sysctl.d/!

Now save iptables

iptables-save > /etc/sysconfig/iptables

Now lets run our script

/root/mkrouter 172.16.2.1/24

From your other device – in my case the Juniper – set a default route towards 172.16.2.1. For my juniper it would be

set routing-options static route 0.0.0.0/0 next-hop 172.16.2.1

For another Linux device it would be

ip route set default dev <YOUR_DEVICE_CONNECTED_TO_SWITCH> via 172.16.2.1

Obviously substitute <YOUR_DEVICE_CONNECTED_TO_SWITCH>. For me, on my Juniper, it was ge-0/0/0.0 – depending on your OS this could be eth0, eth1, or something as abstract as enp0s2f3 🙂

From this secondary device you should be able to ping something far away like 4.2.2.2. Test it out. Ex from my Juniper:

[edit]
root@home-ro0# run show route 4.2.2.2 

inet.0: 9 destinations, 9 routes (9 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[Static/5] 00:14:42
                    > to 172.16.2.1 via ge-0/0/0.0


[edit]
root@home-ro0# run ping 4.2.2.2    
PING 4.2.2.2 (4.2.2.2): 56 data bytes
64 bytes from 4.2.2.2: icmp_seq=0 ttl=55 time=24.002 ms
64 bytes from 4.2.2.2: icmp_seq=1 ttl=55 time=27.720 ms


So this concludes turning our PI (or equivalent) in to a router. Next, we’re going to connect it up to our DigitalOcean VM

So what we have now is:

                  Raspberry Pi 
             wlan0 = 192.168.0.250/24
              eth0 = 172.16.2.1/24
                       |
                     Switch
                       |
                    Juniper
   ge-0/0/0.0 = 172.16.2.2 - default route via 172.16.2.1

On DigitalOcean VM

This is assuming you have already enabled private networking per the requirements above. For me, I was given 10.136.0.0/16. Also note your eth0 assignment as you’ll need it to substitute these values below

You can install OCServ via EPEL if you are running CentOS. I will not cover that here. Simply navigate to the EPEL site, enable the CentOS 7 repository, and then install ‘ocserv’ package

You will also need to generate a cert. Whether you choose to do a self signed or get a root signed, I will not cover that here. When you connect via openconnect command you have the option of answering that you don’t care about any cert warnings/errors

Pretty much all I will show you here is example configuration for OCServ with the scope I was given. I have 10.136.0.0/16, my VM was assigned 10.136.3.255 so I am going to use 10.136.3.192/29 for my VPN subnet

What this means is, as noted above, when an AnyConnect/openconnect client connects to your DigitalOcean external IP, OCServ will offer an IP in that range with 10.136.3.192 being the gateway for that client (being, the PI)

For your certs, the below configuration assumes you are saving them where I did under /etc/ocserv/ssl. You will notice mine says ‘chained’ because I obtained my cert from DigiCert and I chained the intermediate cert

So after you have installed OCServ you can use the example configuration below

I have highlighted the two most critical lines. The first is the network you want to configure which is based on what you get for eth1 and then the “no-route” part for telling connecting clients that you dont want them to route connections to your DigitalOcean external IP through the tunnel. You only want to send everything else there

auth = "pam[gid-min=1000]"
tcp-port = 443
udp-port = 443
run-as-user = ocserv
run-as-group = ocserv
socket-file = ocserv.sock
chroot-dir = /var/lib/ocserv
isolate-workers = true
max-clients = 16
max-same-clients = 0
keepalive = 32400
dpd = 90
mobile-dpd = 1800
switch-to-tcp-timeout = 60
try-mtu-discovery = false
server-cert = /etc/ocserv/ssl/vpn.mydomain.com.chained.crt
server-key = /etc/ocserv/ssl/vpn.mydomain.com.key
ca-cert = /etc/pki/ocserv/cacerts/ca.crt
cert-user-oid = 0.9.2342.19200300.100.1.1
tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0"
auth-timeout = 240
min-reauth-time = 300
max-ban-score = 50
ban-reset-time = 300
cookie-timeout = 300
deny-roaming = false
rekey-time = 172800
rekey-method = ssl
connect-script = /usr/bin/ocserv-script
use-occtl = true
pid-file = /var/run/ocserv.pid
device = vpns
predictable-ips = true
default-domain = mydomain.com
ipv4-network = 10.136.3.192/27
dns = 8.8.8.8
dns = 4.2.2.2
ping-leases = false
route = default

# Dont route our own gateway for the connection through clients tunnel
#no-route = <your_digitalocean_vm_eth0_ip>/255.255.255.255

cisco-client-compat = true
dtls-legacy = true
user-profile = profile.xml

You’re also going to need to enable rp_filter and ip.forward. So swing the hammer again:

# for i in $(sysctl -a 2> /dev/null | grep -i rp_filter | grep -iv arp_filter | awk '{ print $1 }'); do sysctl -w $i=2; done

# sysctl -w net.ipv4.ip_forward=1

Dont forget that you’ll want to make these settings persistent via /etc/sysctl.d/!

We’re now going to move back to the PI where we are going to configure openconnect

Back on the PI

If you’re not using a PI then congrats! Just enable EPEL on your PI equivalent (laptop,etc) and install openconnect from repo

However….

Something I discovered halfway through writing this is that openconnect does not have any packages for the PI. I found this out because for this exercise I used my laptop the whole way, until I decided to implement it on my PI (as I am writing this). So, this was fun. I will run through the steps but please note they will be quick as they are scratch notes from implementing this today. I also will not delve in to many details, so if you’re unsure then find a laptop and use that (then restart at the steps above)

Be sure to be ready to allocate probably 2-3 hours for this part :-/

First install development tools

yum groupinstall -y 'Development Tools'

Then install dependent packages

yum install -y libgcrypt-devel libgcrypt openssl-devel openssl gnutls gnutls-devel libxml libxml2-devel

Now the long part…we have to install cpan to get an old Perl module for the vpnc configuration

yum install -y perl-CPAN
cpan
<select all the defaults>
<once you're at the CPAN cli type the following>
install Fatal

Now fetch the vpnc dependency. More can be found here https://www.unix-ag.uni-kl.de/~massar/vpnc/

curl https://www.unix-ag.uni-kl.de/~massar/vpnc/vpnc-0.5.3.tar.gz -o vpnc.tar.gz
tar -xvzf vpnc.tar.gz
cd vpnc-*
make install clean

This should go in fairly straight forward. Its a pretty lightweight thing

And now install openconnect

curl ftp://ftp.infradead.org/pub/openconnect/openconnect-7.08.tar.gz -o openconnect.tar.gz
tar -xvzf openconnect.tar.gz
cd openconnect-*
./configure<br>make install clean

And now connect it to your DigitalOcean external IP. Below is an example of what you should see:

# openconnect ${YOUR_DIGITALOCEAN_EXTERNAL_IP}
POST https://${YOUR_DIGITALOCEAN_EXTERNAL_IP}/
Connected to ${YOUR_DIGITALOCEAN_EXTERNAL_IP}:443
SSL negotiation with ${YOUR_DIGITALOCEAN_EXTERNAL_IP}
Server certificate verify failed: certificate does not match hostname

Certificate from VPN server "${YOUR_DIGITALOCEAN_EXTERNAL_IP}" failed verification.
Reason: certificate does not match hostname
To trust this server in future, perhaps add this to your command line:
    --servercert sha256:<snip>
Enter 'yes' to accept, 'no' to abort; anything else to view: yes
Connected to HTTPS on ${YOUR_DIGITALOCEAN_EXTERNAL_IP}
XML POST enabled
Please enter your username.
Username:root
POST https://${YOUR_DIGITALOCEAN_EXTERNAL_IP}/auth
Please enter your password.
Password:
POST https://${YOUR_DIGITALOCEAN_EXTERNAL_IP}/auth
Got CONNECT response: HTTP/1.1 200 CONNECTED
CSTP connected. DPD 90, Keepalive 32400
Connected as 10.136.3.202, using SSL
Established DTLS connection (using GnuTLS). Ciphersuite (DTLS1.2)-(PSK)-(AES-128-GCM).

From the above you’ll notice the following things

  1. I have accepted the invalid cert. For this it was because the hostname I connected to did not match (because I used IP). I demonstrated this here so you can see that you can accept insecure certs
  2. I logged on as root with the root password of the server (THIS IS BAD: I am only showing for concepts). OCServ uses PAM to authenticate a user, so your options are pretty much endless there (RADIUS, LDAP, AD, Kerberos, NIS, etc)
  3. I was given the IP 10.136.3.202. And what you dont see is what I am going to show you below. Note this IP that you see because we need it later back on the DigitalOcean VM
[root@ck-centos-rpi3 ~]#  ip route
default dev tun0  scope link 
10.136.3.192/27 dev tun0  scope link 
${YOUR_DIGITALOCEAN_EXTERNAL_IP} via 192.168.0.1 dev wlan0  src 192.168.0.250 
172.16.2.0/24 dev eth0  proto kernel  scope link  src 172.16.2.1 
192.168.0.0/24 dev wlan0  proto kernel  scope link  src 192.168.0.250

[root@ck-centos-rpi3 ~]#  ip route get 4.2.2.2
4.2.2.2 dev tun0 src 10.136.3.202 
 cache 

[root@ck-centos-rpi3 ~]#  ping 4.2.2.2
PING 4.2.2.2 (4.2.2.2) 56(84) bytes of data.
64 bytes from 4.2.2.2: icmp_seq=1 ttl=59 time=24.6 ms
64 bytes from 4.2.2.2: icmp_seq=2 ttl=59 time=21.6 ms


[root@ck-centos-rpi3 ~]# curl http://ifconfig.io
67.205.x.x
^ My DigitalOcean VM external IP

So what does this show us? We now have the following in place:

  1. A default route out of tun0
  2. Route the entire 10.136.3.192/27 through tun0
  3. Default route through tun0
  4. And then keep everything else intact

Now, from your secondary device (in my case, the juniper) you wont be able to do anything yet. We have to jump back up to the DigitalOcean VM before we can move traffic

Jump to the DigitalOcean VM

Now, since we’re all setup and connected, we just need to tell our DigitalOcean VM that traffic for 172.16.2.0/24 needs to be sent down the tunnel interface of vpns0 via the gateway IP 10.136.3.202 which is what my PI was assigned when openconnect connected successfully

[root@do-gw-proxy ~]# ip route add 172.16.2.0/24 dev vpns0 via 10.136.3.202

And now you will see we can reach back to our house!

[root@do-gw-proxy ~]# ping 172.16.2.1
PING 172.16.2.1 (172.16.2.1) 56(84) bytes of data.
64 bytes from 172.16.2.1: icmp_seq=1 ttl=64 time=23.4 ms
^C

And now I will SSH to my Juniper to show you that you can now reach back home from your DigitalOcean VM

[root@do-gw-proxy ~]# ssh 172.16.2.2
Password:
Last login: Tue Jun 13 22:23:51 2017 from 10.80.0.252
--- JUNOS 15.1X49-D90.7 built 2017-04-29 06:16:43 UTC
root@home-ro0%


[root@do-gw-proxy ~]# ssh 172.16.2.1
root@172.16.2.1's password: 
Last login: Tue Jun 13 18:44:17 2017 from 10.136.3.193
[root@ck-centos-rpi3 ~]# netstat -tnap | grep -i established
Active Internet connections (servers and established)
tcp        0      0 172.16.2.1:22           10.136.3.193:41826      ESTABLISHED 16855/sshd: root@pt 
tcp        0      0 192.168.0.250:34780     67.205.x.x:443      ESTABLISHED 1734/openconnect   

And as you can see, I SSH’ed to my RPI and my SSH is connected from 10.136.3.193 which is my IP on my DigitalOcean VM. You can see I have an persistent connection to my DigitalOcean VM via openconnect but I am connected from 136.3.193, which is the IP of the DigitalOcean VM vpns0 interface

[root@do-gw-proxy ~]# ip addr show eth1 | grep -i 10.136
    inet 10.136.3.225/16 brd 10.136.255.255 scope global eth1