Strongswan swanctl-based VPN setup

08 August 2023
Strongswan deprecated the ipsec.conf format of configuration files quite a long time ago and I felt it was time to bullet with a migration over to the swanctl.conf format. VPN setup is one of those things I did so infrequently that I invariably ended up having to look up at least something, but having had a peek at the new format it seemed to be both laid out better and have more intuitive terminology. The newer swanctl is also much better than the traditional ipsec interface. This was all part of a much wider refresh of all my security arrangements so information that was scattered over several past website articles has been brought together and updated.

Key generation

I no longer use passwords for securing remote access and instead make heavy use of public-key authentication, and here is an overview of how to generate the required files. Note that in this article the terms certificate and key is used but many places use the terms public key and private key instead — a certificate and public key are the same thing. My preference is to maintain my own private authentication hierarchy and for purposes of explaination the following filenames are used:
cacert.pem
This is a self-signed CA (certification authority) certificate which is used to verify all the server and client certificates, so this needs to be coped to every Strongswan installation.
cakey.pem
The private key that compliments the above CA certificate. It is encrypted using “magick” as a placeholder.
server.cert.pem
server.key.pem
The certifiate and key pair for the VPN server.
client.cert.pem
mobile.cert.pem
Client certificates.
client.key.pem
mobile.key.pem
Client private keys.
client.p12
This is a client certificate and key packaged up for deployment to a mobile phone. These packages are always secured with a password and here “sekret” is used as a place-holder.
These files can be generated by the following makefile. The IPSEC parameter at the top may need to be changed if this script is run on distributions other than Slackware. When updating my instructions I decided to make the switch from RSA keys and Elliptic Curve DSA keys.

IPSEC=/usr/sbin/ipsec KEYTYPE=--type ecdsa KEYSIZE=--size 521 all: cacert.pem server.cert.pem client.cert.pem mobile.cert.pem ## Certificate Authority cakey.pem: $(IPSEC) pki --gen $(KEYTYPE) $(KEYSIZE)--outform pem > $@ $(IPSEC) pki --print $(KEYTYPE) --in $@ openssl ec -in $@ -out $@ -aes256 -passout pass:magick cacert.pem: cakey.pem $(IPSEC) pki --self --in $< \ --dn "C=UK, OU=LAN, O=VPN, CN=VPN CA" \ --ca --outform pem > $@ $(IPSEC) pki --print --in $@ ## Server and client keys .SECONDARY: $(%.key.pem) %.key.pem: $(IPSEC) pki --gen $(KEYTYPE) $(KEYSIZE) --outform pem > $@ %.key.pub: %.key.pem $(IPSEC) pki --pub --in $< --outform pem > $@ ## Server cert server.cert.pem: server.key.pub $(IPSEC) pki --issue --cacert cacert.pem --cakey cakey.pem \ --dn "C=UK, OU=LAN, O=VPN, CN=VPN" \ --san "gateway.remy.org.nz" \ --flag serverAuth --flag ikeIntermediate \ --in $< --outform pem > $@ ## Client certs client.cert.pem: client.key.pub $(IPSEC) pki --issue --cacert cacert.pem --cakey cakey.pem \ --dn "C=UK, OU=LAN, O=VPN, CN=Cake VPN Client" \ --in $< --outform pem > $@ mobile.cert.pem: mobile.key.pub $(IPSEC) pki --issue --cacert cacert.pem --cakey cakey.pem \ --dn "C=UK, OU=LAN, O=VPN, CN=S10 VPN Client" \ --in $< --outform pem > $@ mobile.p12: mobile.cert.pem mobile.key.pem openssl pkcs12 -in mobile.cert.pem -inkey mobile.key.pem \ -certfile cacert.pem -export -passout pass:magick -out $@

Server setup

Many of the settings shown here are actually defaults for the parameters but are still specified as I am aware of some defaults changing between the old and new configuration file formats. The address gateway.remy.org.nz is the VPN server address where clients are given IP addresses from the 192.168.96.0/24 class-C block, and use the internal DNS server 192.168.96.3. Note that all these are different from what my own VPN actually uses.

connections { LAN { remote_addrs = %any pools = ip_addrs unique = replace version = 2 local { id = "gateway.remy.org.nz" auth = pubkey certs = server.cert.pem } remote { auth = pubkey revocation = relaxed } children { VPN { start_action = none mode = tunnel } } } } pools { ip_addrs { addrs = 192.168.96.0/24 dns = 192.168.96.1 } } authorities { RemyCA { cacert = cacert.pem } }

Some of the files generated previously need to go into the following locations. As a general rule most files of the correct format in the correct place mostly get picked up automatically.

Once everything is in place the server can be started using the following:

ipsec start swanctl --load-all

..and shut down using:

ipsec stop

Client setup

On the client side these days I normally use NetworkManager rather than Strongswan directly, but at the time I was figuring out the new configuration format I had to use the latter approach so the associated client-side configuration is here for completeness. The secrets section at the bottom is there to show how an encrypted key is automatically unlocked but in practice doing this rather than always manually entering the password defeats the point of encrypting client-side credentials in the first place.

connections { RemyLAN { version=2 local_addrs=%any remote_addrs=gateway.remy.org.nz vips=0.0.0.0 #proposals = aes128-sha256-x25519 children { RemyVPN { start_action=none } } local { auth=pubkey certs=client.cert.pem } remote { auth=pubkey } } } secrets { private-client1 { file = client.key.pem secret="sekret" } }

On the client side files go into the following locations, which conveniently is much the same as on the server side.

Starting up Strongswan on the client, loading the required files, and connecting to the server is done using the following commands:

ipsec start swanctl --load-all swanctl --initiate --child RemyVPN

..and disconnecting from the server followed by shutting down Strongswan:

swanctl --terminate --child RemyVPN ipsec stop

When trying to diagnose faults it is useful to manually run /usr/libexec/ipsec/charon in the foreground as this dumps log output directly to the console, and it is easy to up the level of details for specific sub-systems. This applies to the server side as well as clients.

Certificate revocation

The following makefile snippet shows how to generate a CRL (certificate revocation list) should a specific client certificate needs to be disabled, which thankfully I have so far not needed to do but have put together the command should the eventuality arise. When adding an additional certificate to the revocation list the --lastcrl parameter is also needed — see Strongswan's documentation for details of the commands.

crl.pem: $(IPSEC) pki --signcrl --cacert cacert.pem --cakey cakey.pem \ --reason superseded --outform pem --lifetime 365 \ --cert client.cert.pem > crl.pem

The revocation certificate then goes into the following location:

Slackware boot script

The following is a Slackware-style startup/shutdown script that can be used as an alternative to ipsec start, and if the commented out bits are enabled it can be used as a client-side script. I have not looked into what changes are needed to allow this to be run as a non-root user, so treat this as a starting point rather than off-the-shelf.

#!/bin/bash PIDFILE=/var/run/strongswan-charon.pid case "$1" in 'start') if [ -f ${PIDFILE} ]; then echo "Strongswan already running" exit fi /usr/libexec/ipsec/charon --use-syslog & PID=$! echo ${PID} > ${PIDFILE} sleep 1 swanctl --load-conns swanctl --load-creds #swanctl --initiate --child styx ;; 'stop') #swanctl --terminate --child styx if [ ! -f ${PIDFILE} ]; then echo "charon not running" else PID=$(cat ${PIDFILE}) kill $PID rm ${PIDFILE} fi ;; esac