HOW-TO: IPsec/IKEv2 Road-Warrior VPN /w strongSwan in 2024

Background

I've setup and been running IPsec/IKEv2 VPN so-called road-warrior scenario with strongSwan for a decade. Firstly setup on Entware. A couple of years later easily migrated the setup to EdgeRouter X (i.e. Debian).

Recent years' update in strongSwan such swanctl & xfrm interface, the UCI middleware and firewall4/nftables in OpenWrt (all new to me) made the migration of my old setup more challenging than anticipated this time around.

Available information is a bit messy, inaccurate and confusing. I've yet to see a comprehensive HOW-TO that works and sticks to best practice of latest technologies.

I spent a day on & off to pull this working setup together. Hope it will save others some time. And more and more users will deploy IPsec VPN. As far as I'm aware IPsec is the only solution with widely built-in clients on various OS platforms and will take advantage of crypto acceleration ASICs already supported by the platforms.

A few Conceptis First

strongSwan comes a long way. The old 'ipsec CLI' way of doing configuration is set for deprecation. The new 'swanctl' is the way forward.

The 'VTI interface' in strongSwan for setting up routes and firewall on the host is set for deprecation too. The replacement and better feature is known as 'xfrm interface'. The good old 'iptables policy based' routes and firewall seems still supported but I would not recommend for firewall already migrated to nftables.

So the chosen technologies in this guide is 'swanctl' and 'xfrm interface'. The latter makes so easy to work with nftables-based firewall such as OpenWrt's firewall4.

Pitfalls in OpenWrt

  • Past information on the forum & documentation in OpenWrt are confusing and sometimes conflicting each other due to changing technologies. So cherrypick carefully.
  • Package 'strongswan-minimal' (as of last check in Jul 2024) is for the old strongswan's 'ipsec CLI' way of doing the configuration. Unless you know that's specifically what you want, don't install. This will save you lot of trouble later on.
  • If you want to try yourself from scratch, install package 'strongswan-full' instead. This will fulfil most pre-requisites but still some missing packages that you'll have to figure out. I've gone through this process. So you might not want to spend hours to repeat the journey.
  • Don't install package 'ip-tiny'. Install package 'ip-full' instead. The installed-size of the full version is only 100KiB bigger. Why OpenWrt defaults to 'ip-tiny' or having two versions available is beyond my comprehension. It creates lot of confusion. At the minimum effort of change, I think package strongswan-full should be defined explicitly to depend on 'ip-full' instead. Package 'ip-tiny' provides the 'ip' utility that doesn't support
    1. 'ip xfrm' sub-command
    2. graceful reporting of empty routing table. Instead, show you a puzzling error (Error: ipv4: FIB table does not exist. Dump terminated). A common question on this forum but seems nobody had addressed the 'root cause' of the error from my brief search. Now you have it.
  • package 'xfrm' is required to enable UCI to support 'xfrm interface' in /etc/config/network. None of the storngSwan package depends on package 'xfrm'. You have to manually install it.

Now we can start configuring our minimalistic setup of strongSwan...

Required Packages

  • ip-full xfrm
  • strongswan strongswan-charon strongswan-swanctl
  • strongswan-mod-kernel-netlink strongswan-mod-kdf strongswan-mod-gmp strongswan-mod-random strongswan-mod-socket-default strongswan-mod-uci strongswan-mod-vici
  • strongswan-mod-aes strongswan-mod-hmac strongswan-mod-pem strongswan-mod-pkcs1 strongswan-mod-pkcs7 strongswan-mod-sha1 strongswan-mod-sha2 strongswan-mod-x509

The second group of modules meet my requirement of crypto strength (as shown in config below). If you choose stronger or other ciphers, then some of the packages might not be required and additional packages will be required.

Here is the one liner version of the same list:

$ opkg install ip-full xfrm strongswan strongswan-charon strongswan-swanctl strongswan-mod-aes strongswan-mod-kdf strongswan-mod-gmp strongswan-mod-hmac strongswan-mod-kernel-netlink strongswan-mod-pem strongswan-mod-pkcs1 strongswan-mod-pkcs7 strongswan-mod-random strongswan-mod-sha1 strongswan-mod-sha2 strongswan-mod-socket-default strongswan-mod-x509 strongswan-mod-uci strongswan-mod-vici

Create the 'xfrm' Interface
Append the following code snippet to

/etc/config/network
# XFRM interface for IKEv2 IPsec VPN (strongSwan)
config interface 'xfrm0'
  option proto   'xfrm'
  option ifid    '357'
  # 'tunlink' must be an 'interface' defined in this config
  # not the 'device' attached to the 'interface'
  # e.g. 'wan' works but 'br-wan' doesn't on Banana Pi BPI-R4
  option tunlink 'wan'
  option mtu     '1438'
  option zone    'vpn'

# Purpose of this interface is to assign a static address to the host end of the 'xfrm0' tunnel
# This dummy interface, otherwise, is not used anywhere else whatsoever
config interface 'xfrm0_dummy'
  option proto  'static'
  option ifname '@xfrm0'
  # last address in the subnet. Hope we won't have that many road-warriors to reach 254
  # Adapt it to your own chosen private IP subnet
  option ipaddr '172.31.0.254/24'

Note that the only linkage between this interface and our IPsec config is the "ifid". Nothing else. You can choose your own unique integer, need not be '357'.

Another trivia: inconsistently enough, "/etc/config/network" decided to use "ifid" as the keyword and "/etc/config/ipsec" (as you'll see in a moment) decide to use "if_id" as the keyword for the same thing...

Configure the Firewall (FW4)
Append the following code snippet to

/etc/config/firewall
config zone
  option name 'vpn'
  option input   'ACCEPT'
  option output  'ACCEPT'
  option forward 'ACCEPT'
  option mtu_fix '1'
  list network 'xfrm0'

config forwarding
  option src  'vpn'
  option dest 'wan'

config forwarding
  option src  'vpn'
  option dest 'lan'

config forwarding
  option src  'wan'
  option dest 'vpn'

config forwarding
  option src  'lan'
  option dest 'vpn'

Configure IKEv2 IPsec
Create /etc/config/ipsec with the following content

/etc/config/ipsec
config ipsec
  list   interface 'wan'
  option zone   'vpn'
  option debug  '2'

config remote 'phaeo'
  option enabled '1'
  option gateway 'any'
  option authentication_method 'pubkey'
  option local_identifier      'the CN value given to MyVPNServerCert.pem'
  option local_cert 'MyVPNServerCert.pem'
  option local_key  'PrivateKeyToMyVPNServerCert.pem'
  option ca_cert    'MySelfSignedCACert.pem'
  option local_sendcert        'always' # swanctl init script needs to be patched to support this
  option dpddelay   '300s' # dpdaction must be set to non-default value in below tunnel section
  option rekeytime  '3h'
  option keyingretries '0'
  option mobike '1'
  option fragmentation '1'
  list   crypto_proposal  'ike_proposal'
  list   tunnel           'roadwarrior'
  list   pools  'ipv4pool'

config crypto_proposal 'ike_proposal'
  option encryption_algorithm 'aes256'
  option hash_algorithm 'sha256'
  option dh_group      'modp3072'

config tunnel 'roadwarrior'
  list   local_subnet   '0.0.0.0/0'
  list   remote_subnet  '172.31.0.0/24' # for decoration and easier human identification purpose only
  option if_id      '357'
  option rekeytime  '1h'
  option startaction 'none'
  option closeaction 'none'
  option dpdaction   'clear' # must be set in order for above IKEv2 "remote" block to honor dpddelay setting
  list   crypto_proposal 'esp_proposal'

config crypto_proposal 'esp_proposal'
  option encryption_algorithm 'aes128'
  option hash_algorithm 'sha1'

# Undocumented in OpenWrt wiki, added by git commit 08a0f7
# https://github.com/openwrt/packages/commit/08a0f7bb94f5577d5c04dcd8e3691e8ee69f76f9
config pools 'ipv4pool'
  option addrs '172.31.0.0/24' # actual IPv4 will be giving out to road-warriors
  list netmask '255.255.255.0'
  list dns     '192.168.1.1' # the DNS server on your LAN

The swanctl.patch

This patch is not mine. It seems to be contributed by @aleks-mariusz as far as I'm aware. The finding in the patch is tremendous contribution. Why hasn't OpenWrt merged the patch btw ?

For the above '/etc/config/ipsec' to work, you will have to apply the patch. Otherwise, 'local_sendcert' won't be recognised as it's not supported in the stock OpenWrt version (as of check in OpenWrt snapshot in July 2024).

For preserving the exact formatting of the patch, and hence work 'out of the box', please get it from my GitHub with

$ cd /root
$ wget https://raw.githubusercontent.com/kvic-z/goodies-OpenWrt/main/swanctl.patch

To apply the patch

$ cd /
$ patch -p 1 < /root/swanctl.patch

A note on the crypto ciphers that I chose

They're from about 10 years ago. Some might suggest to choose stronger ciphers. IMHO, for the purpose of SOHO users, I think it's still good enough for today. It's very strong & compute intensive on the key exchange phase (that happens every 3 hours as configured above). And very light compute on actually encrypting the encapsulation. The idea is that by the time a less-than-state actor broke the symmetric key, my VPN tunnel already hops to a new key (that happens every hour as configured above). LOL

Generation of self-signed CA certificate and server certificate is covered in many tutorials. Hence, we'll skip here.

Conclusion

I'll have documented my 'difficult struggle' anyway. Instead of writing in Microsoft Word and keeping it private for enjoyment in a closed circle, I thought why not let me do it in Markdown and publish it on Discourse. Because I found concise & correct info/documentation is very lacking on OpenWrt wiki & forum. Ultimately I hope more people will use IPsec and find it easy to setup and joyful to use.

So this HOW-TO provides a setup that fits into latest OpenWrt release that uses

  • strongSwan, swanctl & xfrm interface
  • nftables & OpenWrt's firewall4
  • UCI middleware in OpenWrt

References that provided valuable input to my research

1 Like

excellent write up, and i'm glad my patch was useful to folks. I went through the same thing last september and it surely took me several days (maybe even a week or so) of on-off time, so that you got this working in even just one-day is pretty impressive :slight_smile: i have a ton of linux and networking experience, but my ipsec knowledge was quite limited since previously i just took a shortcut when i was doing a diy-linux-vm-routing and used algovpn on that VM (don't think it supports openwrt, but back then i hadn't used openwrt then).

again, bravo! your write up is the kind of thing i wish i had available to me last autumn and i'm sure it'll help a lot more folks. i totally agree that i would much rather use ipsec as it's built-in and doesn't require extra packages for clients.

In case it helps, here's a set of commands i have saved (i had it only in my version of the "private word doc", actually it's in a private git repo i keep where i keep track of how i custom-build and config my openwrt deployment):

$ STRONGSWAN_CLIENT_NAME=myMacBookPro  # will have hostname of the IPsec client

# CA stuff (for best security, keep offline, only needed during cert-generation steps here)

$ openssl genrsa -out ca.key 4096
$ openssl req -x509 -new -nodes -config $ openssl-$STRONGSWAN_CLIENT_NAME.cnf -extensions ca -key ca.key -subj "/C=UK/O=your.domain.com self-hosted strongSwan VPN/CN=external-hostname-you-vpn-to.domain.com" -days 3650 -out ca.crt

# certs for the ipsec service (deploy these on openwrt host)

$ openssl genrsa -out server.key 4096
$ openssl req -new -config openssl-$STRONGSWAN_CLIENT_NAME.cnf -extensions server -key server.key -subj "/C=UK/O=your.domain.com self-hosted strongSwan VPN/CN=external-hostname-you-vpn-to.domain.com" -out server.csr
$ openssl x509 -req -extfile openssl-$STRONGSWAN_CLIENT_NAME.cnf -extensions server -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt

# client cert generation: myMacBookPro (the output of these commands will be packaged and sent to client)

$ openssl genrsa -out client-$STRONGSWAN_CLIENT_NAME.key 4096
$ openssl req -new -config openssl-$STRONGSWAN_CLIENT_NAME.cnf -extensions client -key client-$STRONGSWAN_CLIENT_NAME.key -subj "/C=UK/O=your.domain.com self-hosted strongSwan VPN/CN=$STRONGSWAN_CLIENT_NAME@external-hostname-you-vpn-to.domain.com" -out client-$STRONGSWAN_CLIENT_NAME.csr
$ openssl x509 -req -extfile openssl-$STRONGSWAN_CLIENT_NAME.cnf -extensions client -in client-$STRONGSWAN_CLIENT_NAME.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out client-$STRONGSWAN_CLIENT_NAME.crt

# here we generate a password we will use for packing the client certs
$ openssl rand -base64 24
$ cat > client-$STRONGSWAN_CLIENT_NAME-export-pw
$ openssl pkcs12 -in client-$STRONGSWAN_CLIENT_NAME.crt -inkey client-$STRONGSWAN_CLIENT_NAME.key -certfile ca.crt -export -out client-$STRONGSWAN_CLIENT_NAME.p12

# now secure the critical stuff
$ chmod 600 *.key
$ chmod 600 *-pw
$ chmod 600 *.p12

for apple-based clients (macOS hosts, iphones, ipads, etc), you can bundle all this in a .mobileconfig and airdrop or email it to the device and voila, all is configured.

The details for that macos/ios clients i got from this site, with the related video timestamp @21:47 here.

i'm sure there's a procedure out there for configing windows/linux clients as well, but i too will pass on providing that, as that's not what i had the need to do, so can't provide guidance on what i haven't done myself.

1 Like

The swanctl.patch is a marvellous achievement that should get merged into OpenWrt source code. I can help to create a pull request in the coming days, and get the ball rolling. If you want to do it yourself, by all means go ahead as you deserve every credit of it.

Another piece of detail I forgot to highlight in the original post. Your discovery of the undocumented 'config pools' is also critical. As my test showed, without the pools fragment, IP assignment to road-warriors seems not working.

So kudos to your many hours of effort last autumn and generously sharing with the community. Interesting folks like you make OpenWrt a great ecosystem.

1 Like

Oh yes, please. If you would be so kind, that would be terrific. I was thinking of doing a PR to this issue (which was about another stumbling point i came across with the stock config) at some point soon after, but, life got in the way (and other priorities came up, as it goes). Also i was a bit put off that the maintainer of the package wanted separate PRs for each issue/enhancement (and that inevitably creates multiple of the work, which I had too limited bandwidth and exhausted my brain capacity by then to take on).

Not trying to make excuses, i should have seen it fully through, yes. But after the effort i felt i already put into writing about the experience on the forum in multiple posts, having to also do multiple PR didn't seem super palatable when the maintainers barely had a handful of words in response. :slight_smile: Guess they've got busy lives too i suppose.

I should really get around to fixing up the official openwrt doc on this topic, as it looks incredibly in need of an entire rewrite. But maybe lets get the fixes merged into upstream so the doc can not rely on my hacks :laughing:

That's probably the kindest thing I've been told on a forum :slight_smile: I appreciate that very much!

1 Like

Sorry for the belated response. Spent sometime on a short vacation.

I'll make a pull request for the swanctl patch shortly. Will keep the thread posted on its progress :smiley:

1 Like