Published on

IPSec Site to Site VPN with Dynamic IPs with Openswan

Authors

In this tutorial we will setup a site to site ipsec vpn with strongswan and we will enable each server to discover the other vpn server via dynamic dns. We will also append to our config the ability of roadwarriors so that you will be able to connect to your homelab from any mobile or laptop device from any remote source.

Some background

Me and one of my friends decided to build a site to site vpn with strongswan so that our homelabs could be reachable to each other over private networks.

One challenge that I thought of is that both of our internet providers don't support static ip addressing, so each vpn server needs to know where to connect to whenever the ip address changes.

What we will be doing

We will setup strongswan vpn on both servers and allow the private LAN ranges to be reachable for both sides. As I have a domain hosted on cloudflare, I will be using cloudflare's api to update the A record of each sides dns whenever the IP changes.

Environment

On my side, which I will be referring to as Side-A:

Public DNS Name: side-a.example.com
Private Range: 192.168.0.0/24
VPN Server IP: 192.168.0.2

On my friend's side, which I will be referring to as Side-B:

Public DNS Name: side-b.example.com
Private Range: 192.168.1.0/24
VPN Server IP: 192.168.1.2

Cloudflare Dynamic DNS

You don't need to use Cloudflare, theres services such as dyndns.com, no-ip.com. But for this tutorial I will be using cloudflare to utilize my own domain.

I will be using the cloudflare-ddns-client

First we need to create a API Token, head over to your dashboard: dash.cloudflare.com, head over to "my profile", select "API Tokens", then allow "Read Zones" and "Edit DNS", then select "Create Token". Keep the returned token value in a safe place.

Install the pre-requirements:

$ apt install python python-dev python-pip make curl build-essential -y

Get the source and install:

$ git clone https://github.com/LINKIWI/cloudflare-ddns-client.git
$ cd cloudflare-ddns-client
$ make install

We will now configure the cloudflare dynamic dns client, this will be done on both sides, but will only demonstrate for side-a:

$ cloudflare-ddns --configure
Use API token or API key to authenticate?
Choose [T]oken or [K]ey: T
Enter the API token you created at https://dash.cloudflare.com/profile/api-tokens.
Required permissions are READ Account.Access: Organizations, Identity Providers, and Groups; READ Zone.Zone; EDIT Zone.DNS
CloudFlare API token: [redacted]
Enter the domains for which you would like to automatically update the DNS records, delimited by a single comma.
Comma-delimited domains: side-a.example.com

Testing it out to ensure the A record can be updated:

$ cloudflare-ddns --update-now
Found external IPv4: "1.x.x.x"
Listing all zones.
Finding all DNS records.
Updating the A record (ID x) of (sub)domain side-a.example.com (ID x) to 1.x.x.x.
DNS record updated successfully!

We can run this command from above in a cron, but I will use a bash script to only run when the public ip changed: /opt/scripts/detect_ip_change.sh:

#!/bin/bash
set -ex
MY_DDNS_HOST="side-a.example.com"

if [ $(dig ${MY_DDNS_HOST} +short) == $(curl -s icanhazip.com) ];
  then exit 0;
  else /usr/local/bin/cloudflare-ddns --update-now;
fi

Make the file executable: chmod +x /opt/scripts/detect_ip_change.sh then edit your cronjobs: crontab -e and add the script:

* * * * * /opt/scripts/detect_ip_change.sh

This will keep your DNS updated, this needs to be done on both sides, if you want to use dynamic dns.

Port Forwarding

We will need to forward UDP traffic from the router to the VPN server, on both sides:

Port: UDP/500 
Target: VPN-Server-IP:500

Port: UDP/4500
Target: VPN-Server-IP:4500

Create a Pre-Shared Key

Create a preshared key that will be used on both sides to authenticate:

$ openssl rand -base64 36
pgDU4eKZaQNL7GNRWJPvZbaSYFn2PAFjK9vDOvxAQ85p7qc4

This value will be used on both sides, which we will need later.

Install Strongswan on Side-A

Install strongswan and enable the service on boot:

$ apt install strongswan -y
$ systemctl enable strongswan

The left side will be the side we are configuring and the right side will be the remote side.

Create the config: /etc/ipsec.conf and provide the following config:

config setup
    charondebug="all"
    uniqueids=yes
    virtual_private=
    cachecrls=no

conn vpn-to-side-b
    type=tunnel
    authby=secret
    left=%defaultroute
    leftid=side-a.example.com
    leftsubnet=192.168.0.0/24
    right=%side-b.example.com
    rightid=side-b.example.com
    rightsubnet=192.168.1.0/24
    ike=aes256-sha2_256-modp1024!
    esp=aes256-sha2_256!
    keyingtries=0
    ikelifetime=1h
    lifetime=8h
    dpddelay=30
    dpdtimeout=120
    dpdaction=restart
    auto=start

Create the secrets file: /etc/ipsec.secrets:

side-b.example.com : PSK "pgDU4eKZaQNL7GNRWJPvZbaSYFn2PAFjK9vDOvxAQ85p7qc4"

Append the following kernel parameters to /etc/sysctl.conf:

net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

Save:

$ sysctl -p

We now want to add a POSTROUTING and FORWARD rule using iptables:

$ iptables -t nat -A POSTROUTING -s 192.168.1.0/24  -d 192.168.0.0/24 -j MASQUERADE
$ iptables -A FORWARD -s 192.168.1.0/24 -d 192.168.0.0/24 -j ACCEPT

Now we need to route back:

$ ip route add 192.168.1.0/24 via 192.168.0.2 dev eth0

We want to persist the iptables and static route across reboots, so edit the /etc/rc.local file, if it's not there create it with the following values:

#!/bin/bash
iptables -t nat -A POSTROUTING -s 192.168.1.0/24  -d 192.168.0.0/24 -j MASQUERADE
iptables -A FORWARD -s 192.168.1.0/24 -d 192.168.0.0/24 -j ACCEPT
ip route add 192.168.1.0/24 via 192.168.0.2 dev eth0
exit 0

If you created the file, make sure to apply executable permissions:

$ chmod +x /etc/rc.local

Read the secrets and restart strongswan:

$ ipsec rereadsecrets
$ systemctl restart strongswan

Install Strongswan on Side-B

Install strongswan and enable the service on boot:

$ apt install strongswan -y
$ systemctl enable strongswan

The left side will be the side we are configuring and the right side will be the remote side.

Create the config: /etc/ipsec.conf and provide the following config:

config setup
    charondebug="all"
    uniqueids=yes
    virtual_private=
    cachecrls=no

conn vpn-to-side-a
    type=tunnel
    authby=secret
    left=%defaultroute
    leftid=side-b.example.com
    leftsubnet=192.168.1.0/24
    right=%side-a.example.com
    rightid=side-a.example.com
    rightsubnet=192.168.0.0/24
    ike=aes256-sha2_256-modp1024!
    esp=aes256-sha2_256!
    keyingtries=0
    ikelifetime=1h
    lifetime=8h
    dpddelay=30
    dpdtimeout=120
    dpdaction=restart
    auto=start

Create the secrets file: /etc/ipsec.secrets:

side-a.example.com : PSK "pgDU4eKZaQNL7GNRWJPvZbaSYFn2PAFjK9vDOvxAQ85p7qc4"

Append the following kernel parameters to /etc/sysctl.conf:

net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

Save:

$ sysctl -p

We now want to add a POSTROUTING and FORWARD rule using iptables:

$ iptables -t nat -A POSTROUTING -s 192.168.0.0/24  -d 192.168.1.0/24 -j MASQUERADE
$ iptables -A FORWARD -s 192.168.0.0/24 -d 192.168.1.0/24 -j ACCEPT

Now we need to route back:

$ ip route add 192.168.0.0/24 via 192.168.1.2 dev eth0

We want to persist the iptables and static route across reboots, so edit the /etc/rc.local file, if it's not there create it with the following values:

#!/bin/bash
iptables -t nat -A POSTROUTING -s 192.168.0.0/24  -d 192.168.1.0/24 -j MASQUERADE
iptables -A FORWARD -s 192.168.0.0/24 -d 192.168.1.0/24 -j ACCEPT
ip route add 192.168.0.0/24 via 192.168.1.2 dev eth0
exit 0

If you created the file, make sure to apply executable permissions:

$ chmod +x /etc/rc.local

Read the secrets and restart strongswan:

$ ipsec rereadsecrets
$ systemctl restart strongswan

Verify Status

Verify that the ipsec tunnel is up on side-a:

$ ipsec statusall

Connections:
  vpn-to-side-b:  %any...side-b.example.com,0.0.0.0/0,::/0  IKEv1/2
  vpn-to-side-b:   local:  [side-a.example.com] uses pre-shared key authentication
  vpn-to-side-b:   remote: [side-b.example.com] uses pre-shared key authentication
  vpn-to-side-b:   child:  192.168.0.0/24 === 192.168.1.0/24 TUNNEL
Security Associations (1 up, 0 connecting):
  vpn-to-side-b[1]: ESTABLISHED 28 minutes ago, 192.168.0.2[side-a.example.com]...4x.x.x.214[side-b.example.com]
  vpn-to-side-b[1]: IKEv2 SPIs: 81996170df1c927d_i e8294946491ddf08_r, pre-shared key reauthentication in 2 hours
  vpn-to-side-b[1]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
  vpn-to-side-b{2}:  INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: cc4504be_i c294cb26_o
  vpn-to-side-b{2}:  AES_CBC_128/HMAC_SHA2_256_128, 0 bytes_i, 240 bytes_o (4 pkts, 7s ago), rekeying in 18 minutes
  vpn-to-side-b{2}:   192.168.0.0/24 === 192.168.1.0/24

Verify that the ipsec tunnel is up on side-b:

$ ipsec statusall

Connections:
 vpn-to-side-a:  %any...side-a.example.com,0.0.0.0/0,::/0  IKEv1/2
 vpn-to-side-a:   local:  [side-b.example.com] uses pre-shared key authentication
 vpn-to-side-a:   remote: [side-a.example.com] uses pre-shared key authentication
 vpn-to-side-a:   child:  192.168.1.0/24 === 192.168.0.0/24 TUNNEL
Security Associations (1 up, 0 connecting):
 vpn-to-side-a[2]: ESTABLISHED 20 minutes ago, 192.168.1.2[side-b.example.com]...14x.x.x.x[side-a.example.com]
 vpn-to-side-a[2]: IKEv2 SPIs: 81996170df1c927d_i e8294946491ddf08_r, pre-shared key reauthentication in 2 hours
 vpn-to-side-a[2]: IKE proposal: AES_CBC_128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
 vpn-to-side-a{2}:  INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: c294cb26_i cc4504be_o
 vpn-to-side-a{2}:  AES_CBC_128/HMAC_SHA2_256_128, 0 bytes_i, 0 bytes_o, rekeying in 26 minutes
 vpn-to-side-a{2}:   192.168.1.0/24 === 192.168.0.0/24

From side-a (192.168.0.2) ping the gateway on side-b (192.168.1.1):

$ ping -c2 192.168.1.1
PING 10.3.96.2 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=62 time=11.9 ms

If you want to be able to reach the private range of the other side of the vpn from any device on your network, you should add a static route on your router to inform your default gateway where to route traffic to.

In this case on side-a (192.168.0.0/24) we want to inform our default gateway to route (192.168.1.0/24) to the VPN as it knows to route that destination over the VPN.

On side-a, on your router, add a static route:

Route: 192.168.1.0
Subnet: 255.255.255.0
Gateway: 192.168.0.2

On side-b, on your router, add a static route:

Route: 192.168.0.0
Subnet: 255.255.255.0
Gateway: 192.168.1.2

Optional: Roadwarrior VPN Clients

This step is optional, but since we can access each others homelabs, we thought it would be nice to be able to access the resources from mobile devices or laptops when we are on remote locations.

We made it that each VPN owner will connect to its own endpoint (for roadwarriors), so side-a (which will be me) will connect to its own dns endpoint to connect when away from home..

I will only demonstrate how to append your config to add the ability for a roadwarrion vpn connection, append to the /etc/ipsec.conf:

# ...
conn ikev2-vpn
    auto=add
    type=tunnel
    authby=secret
    left=%any
    leftid=side-a.roadwarrior
    leftsubnet=0.0.0.0/0
    right=%any
    rightid=%any
    rightsourceip=10.10.0.0/24
    rightdns=192.168.0.1,8.8.8.8
    auto=start

Append the secret in /etc/ipsec.secrets:

# ...
side-a.roadwarrior my-laptop : PSK "MySuperSecureSecret123"

Add the vpn ip's that we will assign to the roardwarrior clients to the routing table:

ip route add 10.10.0.0/24 via 192.168.0.2 dev eth0

If you only want the roadwarriors to be able to reach your network, you will only forward to the local network such as:

iptables -A FORWARD -s 10.10.0.0/24 -d 192.168.0.0/24 -j ACCEPT

But we will be forwarding traffic to all destinations:

iptables -A FORWARD -s 10.10.0.0/24 -d 0.0.0.0/0 -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -d 0.0.0.0/0 -j MASQUERADE

Remember to append the routes to /etc/rc.local to persist across reboots.

Reread the secrets and restart strongswan:

ipsec rereadsecrets
systemctl restart strongswan

Connecting your VPN Client, I will be using my Laptop, with the following details:

VPN Type: IKEv2
Description: Home VPN
Server: side-a.example.com
Remote ID: side-a.roadwarrior
Local ID: my-laptop
User Authentication: None
Secret: MySuperSecureSecret123

Thank You

In this tutorial I demonstrated how to setup a site to site ipsec vpn between 2 sides that consists of internet connections that has dynamic ip's and also appending roadwarrior config so that you can connect to your homelab from anywhere in the world.

If you like my content, feel free to check out my website, and subscribe to my newsletter or follow me at @ruanbekker on Twitter.

Buy Me A Coffee