Despite the rise of WireGuard, there is no — or hardly any — way around StrongSwan when it comes to Linux- or Unix-based VPN solutions for its flexibility and compatibility with existing well-established protocols, many of which are supported by modern client operating systems out of the box.
However, flexibility and compatibility comes at a price: (correctly) configuring StrongSwan is anything but intuitive, log messages are detailed, yet not neccessarily helpful in tracing configuration errors, while the official documentation leaves room for improvement. Even though the migration from ipsec.conf to swanctl.conf appears to be an improvment with regard to syntax and naming scheme, it renders most of the publicly available tutorials that are urgently needed to get started with StrongSwan useless.
The official guide from the StrongSwan documentation on how to migrate from the former to the latter configuration is helpful and provides a good starting point, yet is not detailed enough to cover all potentially relevant aspects. A Python script provided by Noel Kuntze allows to automate this migration, even though this does not teach anything about how to write proper configrations in the new format.
To make any sense of the following explanations and configuration snippets, the scenario must be explained. That is answering the question on what our StrongSwan installation is supposed to do or enable.
Simply put, our target state is to have a StrongSwan instance that allows mobile clients to establish IPsec tunnels into the company network using IKEv2. In this setup, the StrongSwan instance serves as a VPN server. Furthermore, the clients are supposed to be authenticated using nothing but x509 certificates that have been signed by specific intermediate certificates known to the server. These intermediate certificates, in turn, have been signed by a root certificate that is also known to the StrongSwan server. Certificate revocation lists (CRLs) for both root and intermediate certificates serve to further discriminate connecting peers.
The idea behind having one or more intermediate certificates to sign and subsequently authenticate client certificates is to grant or deny access to client groups, without touching the relatively long-lived root certificate which may serve other purposes as well. However, this requires StrongSwan to not only verify the certificate chain, but also discriminate among different intermediate certificates to check whether the one used to sign the connecting client's certificate is allowed or not, irrespective of the fact that it may have a valid signature from the certificate authority (CA). Otherwise, without such discrimination, any client with a certificate, that has been signed by any intermediate certificate signed by the same CA would be granted access.
In the following, we will develop a prototypical sample configuration that is capable of implementing the scenario sketched above. The configuration consists of three fundamental blocks: authorities, connections, and pools that will be covered sperately.
The authorities block must contain respective entries for the root CA, as well as all intermediate certificates that have been used to sign the client certificates. While a connecting client may equally send along the intermediate certificate that was used to sign the client certificate so that StrongsSwan can attempt to verify the chain of certificates, specifying the intermediate certificates together with their CRLs in the authorities block is needed to allow for the revocation of single client certificates and have StrongSwan deny access accordingly.
An exemplary authorities block may look as follows:
cacert = <path>/rootCert.crt
crl_uris = file://<path>/rootCert.crl
cacert = <path>/intermediateCert1.crt
crl_uris = file://<path>/intermediateCert1.crl
Please note that CRLs are commonly accessible through publicly available HTTP servers, so that the unified ressource identifier (URI) must specify that we are sourcing the CRL from the local file system. The authority names (e.g. rootCert, intermediateCert1, and intermediateCertX) can be chosen freely, yet must be unique in the scope of a given StrongSwan instance.
The connections block contains one entry per intermediate certificate that we use to authenticate clients. While one single connection profile is in fact capable of using several certificates for authenticating against clients (i.e. in the node local:certs), as well as authenticating clients (i.e. in the node remote:cacerts), this is only possible if common names of certificates that serve as IDs are identical. This refers to the node local where such information is required that is also a paramter in the client-side configuration (i.e. remote-id).
The certificate in the node remote does not specify such information so that different certificates could be included as a comma-seperated list. In principle, this would not keep us from using one specific certificate in the local node that is unrelated to the certificate(s) for authenticating incoming client connections. However, assuming that certificates in local and remote are tied to each other with the intermediate certificate being used to sign the local certificate that may also follow a distinct naming scheme, this will not work, especially not when utilizing the CRLs from the authorities block to revoke one such intermediate certificate.
Other parameters in the connections block are well-described in the official documentation so that they are not addressed here in detail. What should be noted though, is that the value of the key pools within the connection profile's node must match the respective pool's name provided in the pools block that is covered further below.
version = 2
local_addrs = 0.0.0.0/0
remote_addrs = 0.0.0.0/0
proposals = "aes256-sha2_256-modp2048,aes256-sha1-modp1024,aes128-sha1-modp1024,3des-sha1-modp1024"
encap = yes
dpd_delay = 300s
send_cert = always
send_certreq = no
unique = no
pools = remotepool
id = <common name>
certs = <path>/local_cert.crt
auth = eap-tls
cacerts = <path>/intermediateCert1.crt
dpd_action = clear
esp_proposals = "aes256-sha256,aes256-sha1,3des-sha1"
local_ts = 0.0.0.0/0
The block pools serves to specify the pools of addresses to be assigned to connected clients. While the pool's name, in this case remotepool can be freely chosen, it must match the name specified in the connection profile (connections:profile1:pools). Note that the address pool and the DNS resolver's address must match your network configuration. If needed, various pools can be defined for different connections, that are referenced through their names. The following excerpt contains a valid and working configuration for a network in the given address range.
addrs = 10.1.25.192/26
dns = 10.1.25.1