Route-based VPN between OPNSense and StrongSwan

Hello there!

Today I’ve spent a little bit of my time to figure out how to move away from policy-based VPN in favour of a route-based one instead. I was eyeing the concept for a while now and wanted to use it in my home lab to solve a couple of problems I was trying to turn a blind eye to. Without further ado, please follow the guide below to set up a route-based VPN between a StrongSwan-based peer (on RPi 3+) and an OPNSense appliance.

OPNSense

OPNSense will be easier to sort out – Head to VPN > IPsec > Tunnel Settings and add a new phase-1 entry following template below (description will be provided below the screenshot):

  • Connection method – leave as “Default” unless your situation requires it to be different. It might be a good idea to set it to “Responder only” if the StrongSwan client (e.g. Raspberry Pi) sits behind a router that does NAT.
  • Key Exchange version – V2
  • Internet Protocol – IPv4
  • Interface – WAN
  • Remote Gateway – This has to be either an IP address or hostname of the remote peer. If “Dynamic gateway” option is ticket, this doesn’t matter as OPNSense will be waiting for the 3rd party (StrongSwan) to initiate the connection
  • Description – I will go with “Routed VPN”
  • Phase 1 (Authentication):
    • Authentication method: Mutual PSK
    • My Identifier:
      • IP address
      • ID_OPNSense – Fill it out with some sort of IP that will uniquely identify the machine. Now it does not have to be OPNSense’s IP. Just make sure you write it down as I will refer to this later
    • Peer Identifier:
      • IP address
      • ID_StrongSwan – Again, you can either use external or internal IP on your network. Just write it down as I will refer to this by “ID_StrongSwan” later
    • Pre-Shared Key: Some-pre-shared-key-example-123
  • Phase 1 (Algorithms):
    • Encryption algorithm:
      • AES
      • 256
    • Hash algorithm – SHA256
    • DH key group – 24 (2048(sub 256) bits)
    • Lifetime – 86400
  • All remaining options should be unticked except NAT Traversal – it should be set to Enable.

After that’s been done, we now move to set up a single phase 2 entry. Because Route-Based VPN uses 0.0.0.0/0 == 0.0.0.0/0 SPIs to operate (essentially allowing everything through), we have to add it as follows:

Now fill out the form like shown below. I will cover some things beneath the screenshot:

  • Mode – Select Route-based. It’s pretty self-explanatory, it’s essentially what we want
  • Description – w/e
  • Tunnel network
    • Local address: 192.168.255.1 – This will be the IP address of OPNSense’s VTI. Basically after we press “Save”, OPNSense will create an interface called ipsecxxxx (where xxxx is some number) with two IPs in it: local and peer. “Local” is referring to itself (as in, the OPNSense box) and “Peer” will be the IP address of the RPi’s VTI. This will give us a functionality similar to traditional networking, allowing us to put routes in place pointing at RPi from OPNSense, and vice versa
    • Remote address: 192.168.255.2 – IP of RPi’s VTI
  • Phase 2 proposal (SA/Key Exchange)
    • Protocol – ESP
    • Encryption algorithms – AES 256 bits – We basically pick the strongest encryption available
    • Hash algorithms – SHA512 – Same as above. In case of performance problems you can lower the hash algorithm down to SHA1 and Enc Algorithm to AES128.
    • PFS key group – 24 (2048(sub 256) bits) – This ensures that in case of tunnel compromise, the attacker won’t be able to hijack the tunnels once they re-establish (in case of a timer timeout or manual SPI deletion)
    • Lifetime – As a CheckPoint guy, I pick 3600 (1 hour) for phase 2 and 1440 minutes for phase 1. Please ignore the 86400 in screenshot, it should say 3600.
  • Advanced Options – Leave blank

Press Save to save.

Now that’s been done, there’s one more thing we need to do in OPNSense – Head to System > Gateways > Single and Add a new gateway:

Remember to select “Far Gateway” and “Disable Gateway Monitoring” options. Without “Far Gateway” option, attempts to save the gateway will fail as it will be “outside of the subnet”. It’s because VTIs use /32 IPs for local and remote values.

Now, it’s worth to mention that even with “Far Gateway” and “Disable Gateway Monitoring” options enabled, there will be problems with the routes. So far I’ve noticed that every time the VPN goes down (due to power or ISP outage), any route added using this gateway as next hop will disappear and won’t reappear until we disable and re-enable the route. I’m yet to find a solution to this problem.

Let’s head to Routes > Configuration now to add any routes we’d like to have in place. In my scenario, RPi will have a 192.168.1.0/24 network and therefore, to get to 192.168.1.0/24, I need to point at RPi’s VTI as the next hop:

After we press “Save” and “Apply” we can minimize the browser as we will now be configuring RPi.

Raspberry Pi (StrongSwan)

Let’s install StrongSwan:

sudo apt -y install strongswan

Let’s head to /etc/ipsec.conf and replace the contents with following configuration, swapping out parts that will be unique in your environment:

# ipsec.conf - strongSwan IPsec configuration file

config setup
        # strictcrlpolicy=yes
        # uniqueids = no

conn %default
        keyexchange=ikev1
        authby=secret

conn TecDen-routed
        keyexchange=ikev2
        ike=aes256-sha256-modp2048s256
        esp=aes256-sha2_512-modp2048s256
        keyingtries=%forever
        ikelifetime=1440m
        lifetime=60m
        leftauth=psk
        left=ID_StrongSwan # Replace this with IP used in ID_StrongSwan
        leftsubnet=0.0.0.0/0
        rightauth=psk
        right=ID_OPNSense # Replace this with IP used in ID_OPNSense 
        auto=start
        rightsubnet=0.0.0.0/0
        mark=12

include /var/lib/strongswan/ipsec.conf.inc

Now head to /etc/ipsec.secrets and replace whole file with following config:

# REPLACE ID_StrongSwan AND ID_OPNSense WITH ACTUAL VALUES (IPs)!!!
ID_StrongSwan ID_OPNSense : PSK "some-psk-you-used-when-configuring-opnsense"
# include /var/lib/strongswan/ipsec.secrets.inc

Let’s configure our VTI now:

Run following commands in Bash, replacing relevant stuff like WAN_RPI and WAN_OPNSense with actual IPs. If your RPI is behind NAT, you can just use the IP of your router-facing interface (in my case – eth0):

ip tunnel add vti0 local WAN_RPI remote WAN_OPNSense mode vti key 12                                                        
ip link set up dev vti0                                                                                                           
ip addr add 192.168.255.2/30 remote 192.168.255.1/30 dev vti0                                                                     
ip route add 10.0.255.0/24 # This is an example of a route to a network hidden behind OPNSense

Let’s now open /etc/sysctl.conf and add following lines somewhere:

net.ipv4.ip_forward = 1
net.ipv4.conf.vti0.disable_policy = 1
net.ipv4.conf.vti0.rp_filter = 0

And load all changes we made with following command:

sudo sysctl -p /etc/sysctl.conf
sudo systemctl enable ipsec
sudo systemctl start ipsec
# and check if the tunnel establishes:
sudo ipsec status
# above should produce similar output:
#Security Associations (1 up, 0 connecting):                                                                                             
#TecDen-routed[1]: ESTABLISHED 28 minutes ago, WAN_RPI[ID_StrongSwan]...WAN_OPNSense[ID_OPNSense]                                
#TecDen-routed{1}:  INSTALLED, TUNNEL, reqid 1, ESP in UDP SPIs: c9c3bf6c_i c435fa70_o                                                  
#TecDen-routed{1}:   0.0.0.0/0 === 0.0.0.0/0

Let’s test our pings now:

strelok@raspberrypi:~ $ ping 192.168.255.1                                                                                              
PING 192.168.255.1 (192.168.255.1) 56(84) bytes of data.                                                                                
64 bytes from 192.168.255.1: icmp_seq=1 ttl=64 time=36.2 ms                                                                             
64 bytes from 192.168.255.1: icmp_seq=2 ttl=64 time=36.6 ms                                                                             
64 bytes from 192.168.255.1: icmp_seq=3 ttl=64 time=36.4 ms                                                                             
64 bytes from 192.168.255.1: icmp_seq=4 ttl=64 time=36.3 ms 

Yeehaw! If pings don’t work for you, don’t give up just yet. On OPNSense, head to Firewall > Log Files > Live View and filter for 192.168.255.2 or the external IP of the RPi. If you don’t know what it is, you can run following command on the RPi: curl -s http://ifconfig.me and it should return the public IP. If you see the traffic being blocked or dropped by OPNSense, add relevant rules to Floating or IPSec ruleset.

Okay, but how do we make those changes permanent now? OPNSense is all set, but RPi will lose the VTI settings after we reboot. Now, we could either do it through /etc/network/interfaces.d (I didn’t look too much into that) or by a simple script!

Run following command to create a directory for our script to sit in and open a text editor to create our script:

sudo mkdir -p /opt/scripts
sudo vim.tiny /opt/scripts/set_up_vtis.sh

and paste following content:

#!/bin/bash

/sbin/ip tunnel add vti0 local WAN_RPi remote WAN_OPNSense mode vti key 12 # Replace WAN_RPi and WAN_OPNSense with same values as we did last time we added the interface manually
/sbin/ip link set up dev vti0
/sbin/ip addr add 192.168.255.2/30 remote 192.168.255.1/30 dev vti0
/sbin/ip route add 10.0.255.0/24 # This is an example of a route to a network hidden behind OPNSense

Save and give the file appropriate permissions:

chmod +x /opt/scripts/set_up_vtis.sh

And add the script to startup (/etc/rc.local). Open the /etc/rc.local file with your favourite editor and put path to your script somewhere. Use below as an example:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

/bin/bash /opt/scripts/set_up_vtis.sh # I've put it just above the "exit 0" line. I suggest you do that too.

exit 0

And save. That’s it! You should be good to go now 🙂 Add more routes with ip route add command every time you’re missing a route!

Leave a Reply

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

Navigation