IPsec on Linux – Strongswan Configuration (IKEv2, Policy-Based, PSK)

Background

The idea behind a VPN is to create a tunnel, that is to say, packets that have IP packets embedded inside IP packets. The private part means the this tunnel is encrypted so that an attacker listening along the path between the two tunnel endpoints will not be able to steal any meaningful data.

Some info about Strongswan – it’s an implementation of the IKE protocol (Internet Key Exchange) which is designed to secure exchange cryptographic key information across an untrusted network, usually the Internet, but not always. The actual encrypting of packets and sending them using a protocol called ESP (Encapsulating Security Payload) is already built into Linux, but Strongswan is needed to give ESP the correct key information to build the tunnel.

There are other IKE implementations, but I find Strongswan the be the best and most actively updated as of this writing in January 2020. You can find the Strongswan website and their documentation at https://www.strongswan.org/, as well as their Github page with the most recent code at https://github.com/strongswan/strongswan.

There are lots of ways to configure IPsec, but for this lab we’ll be using IKE version 2 with a policy-based configuration. The policy-based part means a policy negotiated by the two tunnel endpoints (routers) determines what traffic is allowed or not allowed into the tunnel. Another way to do this is route-based, where the routing tables determine tunneled traffic, allowing for more flexibility. But that’s a story for another day.

Installation

Strongswan can be installed with relative ease on a number of Linux distributions that have package managers, on Ubuntu this is pretty simple:

Ubuntu 18.04 Server

apt-get install strongswan

You can also build from source to get a more recent version, although you’ll probably want to get the instructions from their Github page, as building from source is more involved.

Topology

The idea here is to get UbuntuServer18.04-1 on the bottom left to be able to reach UbuntuServer18.04-2 on the bottom right. You can think of the U_18Router and CiscoIOSv15.6(2)T-3 as being connected to an Internet-like network, for this lab’s purposes. Needless to say, the subnet 192.168.0.0/24 on the left can’t reach 172.16.0.0/24 on the right without some sort of tunnel mechanism, and traffic needs to be encrypted as someone is capturing packets in the middle. A man in the middle, if you will 😛

I’ll be configuring Strongswan on an Ubuntu18.04 Server (U18_Router) on one end and a Cisco IOSv router on the other end. That way we can see that it actually works with other vendors.

Configuration

U18_Router (Ubuntu18.04)

First edit the text file /etc/ipsec.conf using your favorite text editor, I use vim. It should look something like this:

config setup
 strictctlpolicy=yes
 uniqueids=no

conn &default
 ikelifetime=1440m
 keylife=60m

conn james_tunnel
 left=1.1.1.1  #Outside interface of this router
 leftsubnet=192.168.0.0/24  #Inside network behind this router
 right=3.3.3.2  #Outside interface of the cisco router
 rightsubnet=172.16.0.0/24  #Inside network of the cisco router
 ike=aes256-sha2_256-modp1024!  #Corresponds to cisco ikev2 proposal
 esp=aes-sha2_256!  #Corresponds to cisco transform set
 keyingtries=0
 ikelifetime=1h
 lifetime=8h
 authby=secret
 auto=start
 keyexchange=ikev2
 type=tunnel  #Corresponds to transform set "mode"

Then edit /etc/ipsec.secrets, and put your pre-shared key in there along with tunnel endpoint IP addresses:

1.1.1.1 3.3.3.2 : PSK '12345'

Then restart strongswan to load your configuration

ipsec restart

CiscoIOSv15.6(2)T-3

This is an abbreviated version of the Cisco IOS router configuration, as they tend to include a lot of info that’s not relevant here:

crypto ikev2 proposal james-proposal 
 encryption aes-cbc-256
 integrity sha256
 group 2
!
crypto ikev2 policy james-policy 
 proposal james-proposal
!
crypto ikev2 keyring james-ring
 peer remote-router-james
  address 1.1.1.1
  pre-shared-key 12345
 !
!         
crypto ikev2 profile james-profile
 match identity remote address 1.1.1.1 255.255.255.255 
 authentication local pre-share
 authentication remote pre-share
 keyring local james-ring
!
crypto ipsec transform-set james-trans esp-aes esp-sha256-hmac 
 mode tunnel
!
crypto map james-map 1 ipsec-isakmp 
 set peer 1.1.1.1
 set transform-set james-trans 
 set ikev2-profile james-profile
 match address james-vpn
!         
interface GigabitEthernet0/0
 ip address 3.3.3.2 255.255.255.0
 duplex auto
 speed auto
 media-type rj45
 crypto map james-map
!
interface GigabitEthernet0/1
 ip address 172.16.0.1 255.255.255.0
 duplex auto
 speed auto
 media-type rj45
!
ip route 0.0.0.0 0.0.0.0 3.3.3.1
!
ip access-list extended james-vpn
 permit ip 172.16.0.0 0.0.0.255 192.168.0.0 0.0.0.255

Verification

U18_Router (Ubuntu18.04)

Verifying the status of your tunnel is fairly simple, just issue the command ‘ipsec statusall’. Below is an example of a tunnel that’s up an running:

root@uvm1804:/var/log# ipsec statusall
Status of IKE charon daemon (strongSwan 5.6.2, Linux 4.15.0-20-generic, x86_64):
  uptime: 19 hours, since Jan 15 21:48:59 2020
  malloc: sbrk 1626112, mmap 0, used 861840, free 764272
  worker threads: 11 of 16 idle, 5/0/0/0 working, job queue: 0/0/0/0, scheduled: 5
  loaded plugins: charon aes rc2 sha2 sha1 md4 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters
Listening IP addresses:
  1.1.1.1
  192.168.0.1
Connections:
      tunnel:  1.1.1.1...3.3.3.2  IKEv2
      tunnel:   local:  [1.1.1.1] uses pre-shared key authentication
      tunnel:   remote: [3.3.3.2] uses pre-shared key authentication
      tunnel:   child:  192.168.0.0/24 === 172.16.0.0/24 TUNNEL
Security Associations (1 up, 0 connecting):
      tunnel[26]: ESTABLISHED 6 minutes ago, 1.1.1.1[1.1.1.1]...3.3.3.2[3.3.3.2]
      tunnel[26]: IKEv2 SPIs: 8c847c9fa74fd6f6_i 5158e0ac789bebb4_r*, pre-shared key reauthentication in 40 minutes
      tunnel[26]: IKE proposal: AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_1024
      tunnel{42}:  INSTALLED, TUNNEL, reqid 25, ESP SPIs: cda62364_i bb016c85_o
      tunnel{42}:  AES_CBC_128/HMAC_SHA2_256_128, 8400 bytes_i (100 pkts, 271s ago), 8400 bytes_o (100 pkts, 271s ago), rekeying in 36 minutes
      tunnel{42}:   192.168.0.0/24 === 172.16.0.0/24

CiscoIOSv15.6(2)T-3

On an Cisco IOS device, there are a few different show commands you can use to verify tunnel status however I find ‘show crypto session’ to quickly get you the info you need. You can get more info by adding ‘detail’ on the end:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Profile: james-profile
Session status: UP-ACTIVE     
Peer: 1.1.1.1 port 500 
  Session ID: 95  
  IKEv2 SA: local 3.3.3.2/500 remote 1.1.1.1/500 Active 
  IPSEC FLOW: permit ip 172.16.0.0/255.255.255.0 192.168.0.0/255.255.255.0 
        Active SAs: 2, origin: crypto map

Router#

Packet Capture

A packet capture can really help you figure out how far you have progressed and what next steps should be taken to solve a configuration issue or troubleshoot a problem. After you configure Strongswan on Linux and Crypto map on your Cisco, you should be seeing ISAKMP (an extension of IKE) protocol messages in the packet capture that are negotiating tunnel parameters:

Finally, when you have a tunnel established, you should see ESP packets carrying the actual data when UbuntuServer18.04-1 and UbuntuServer18.04-2 ping each other. You should NOT see ICMP (ping) packets in your capture.

Troubleshooting

U18_Router (Ubuntu18.04)

The Strongswan IKE daemon is called “charon”, and it deposits its log messages in the system syslog file. On different distributions this is in different locations or uses a different name, in Ubuntu 18.04 it’s in /var/log/syslog. You can search this file for clues as to what’s going wrong, I suggest using grep to find what you’re looking for. A useful starting point would be to issue ‘cat /var/log/syslog | grep charon’ to show only charon messages. The following example is an example of a typo in the Strongswan configuration resulting in the charon exiting and not attempting to bring up the tunnel. You would not see any ISAKMP packets in your packet capture:

Jan 16 18:00:22 uvm1804 charon: 00[DMN] Starting IKE charon daemon (strongSwan 5.6.2, Linux 4.15.0-20-generic, x86_64)
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading ca certificates from '/etc/ipsec.d/cacerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading aa certificates from '/etc/ipsec.d/aacerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading ocsp signer certificates from '/etc/ipsec.d/ocspcerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading attribute certificates from '/etc/ipsec.d/acerts'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading crls from '/etc/ipsec.d/crls'
Jan 16 18:00:22 uvm1804 charon: 00[CFG] loading secrets from '/etc/ipsec.secrets'
Jan 16 18:00:22 uvm1804 charon: 00[CFG]   loaded IKE secret for 1.1.1.1 3.3.3.2
Jan 16 18:00:22 uvm1804 charon: 00[LIB] loaded plugins: charon aes rc2 sha2 sha1 md4 md5 mgf1 random nonce x509 revocation constraints pubkey pkcs1 pkcs7 pkcs8 pkcs12 pgp dnskey sshkey pem openssl fips-prf gmp agent xcbc hmac gcm attr kernel-netlink resolve socket-default connmark stroke updown eap-mschapv2 xauth-generic counters
Jan 16 18:00:22 uvm1804 charon: 00[LIB] dropped capabilities, running as uid 0, gid 0
Jan 16 18:00:22 uvm1804 charon: 00[JOB] spawning 16 worker threads
Jan 16 18:00:22 uvm1804 charon: 05[CFG] received stroke: add connection 'tunnel'
Jan 16 18:00:22 uvm1804 charon: 05[CFG] algorithm 'aes265' not recognized
Jan 16 18:00:22 uvm1804 charon: 05[CFG] skipped invalid proposal string: aes265-sha2_256-modp1024
Jan 16 18:00:22 uvm1804 charon: 07[CFG] received stroke: initiate 'tunnel'
Jan 16 18:00:22 uvm1804 charon: 07[CFG] no config named 'tunnel'
root@uvm1804:/var/log# 

You’ll notice on line 13 it says “algorithm ‘aes265’ not recognized”. That’s because AES265 isn’t a thing. It’s AES256.

Another example that may be harder to detect and requires some good “stare and compare” skills, would be mismatched encryption/authentication parameters. Your configurations may be valid but if parameters don’t match up, your tunnel won’t come up. The following is an example of when ESP parameters aren’t matching. On the Ubuntu side I’ve specified AES256 for ESP but the Cisco’s transform set calls for AES, which is short for AES128:

Jan 16 18:08:38 uvm1804 charon: 05[CFG] added configuration 'tunnel'
Jan 16 18:08:38 uvm1804 charon: 07[CFG] received stroke: initiate 'tunnel'
Jan 16 18:08:38 uvm1804 charon: 07[IKE] initiating IKE_SA tunnel[1] to 3.3.3.2
Jan 16 18:08:38 uvm1804 charon: 07[ENC] generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(HASH_ALG) N(REDIR_SUP) ]
Jan 16 18:08:38 uvm1804 charon: 07[NET] sending packet: from 1.1.1.1[500] to 3.3.3.2[500] (334 bytes)
Jan 16 18:08:38 uvm1804 charon: 09[NET] received packet: from 3.3.3.2[500] to 1.1.1.1[500] (348 bytes)
Jan 16 18:08:38 uvm1804 charon: 09[ENC] parsed IKE_SA_INIT response 0 [ SA KE No V V N(NATD_S_IP) N(NATD_D_IP) ]
Jan 16 18:08:38 uvm1804 charon: 09[IKE] received Cisco Delete Reason vendor ID
Jan 16 18:08:38 uvm1804 charon: 09[IKE] received Cisco FlexVPN Supported vendor ID
Jan 16 18:08:38 uvm1804 charon: 09[IKE] authentication of '1.1.1.1' (myself) with pre-shared key
Jan 16 18:08:38 uvm1804 charon: 09[IKE] establishing CHILD_SA tunnel{1}
Jan 16 18:08:38 uvm1804 charon: 09[ENC] generating IKE_AUTH request 1 [ IDi N(INIT_CONTACT) IDr AUTH SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(EAP_ONLY) N(MSG_ID_SYN_SUP) ]
Jan 16 18:08:38 uvm1804 charon: 09[NET] sending packet: from 1.1.1.1[4500] to 3.3.3.2[4500] (272 bytes)
Jan 16 18:08:38 uvm1804 charon: 10[NET] received packet: from 3.3.3.2[4500] to 1.1.1.1[4500] (160 bytes)
Jan 16 18:08:38 uvm1804 charon: 10[ENC] parsed IKE_AUTH response 1 [ V IDr AUTH N(NO_PROP) ]
Jan 16 18:08:38 uvm1804 charon: 10[IKE] authentication of '3.3.3.2' with pre-shared key successful
Jan 16 18:08:38 uvm1804 charon: 10[IKE] IKE_SA tunnel[1] established between 1.1.1.1[1.1.1.1]...3.3.3.2[3.3.3.2]
Jan 16 18:08:38 uvm1804 charon: 10[IKE] scheduling reauthentication in 2848s
Jan 16 18:08:38 uvm1804 charon: 10[IKE] maximum IKE_SA lifetime 3388s
Jan 16 18:08:38 uvm1804 charon: 10[IKE] received NO_PROPOSAL_CHOSEN notify, no CHILD_SA built
Jan 16 18:08:38 uvm1804 charon: 10[IKE] failed to establish CHILD_SA, keeping IKE_SA
root@uvm1804:/var/log# 

You’ll notice that the last message states “failed to establish CHILD_SA” which generally means your ESP parameters don’t match, on the Cisco side you’ll need to check your transform set. If your IKE proposal doesn’t match, you’ll get a different message about no proposal being chosen. On the Cisco side you’ll need to check your IKE proposal as well.

CiscoIOSv15.6(2)T-3

You’ll notice that when the Ubuntu Strongswan router isn’t trying to bring up it’s tunnel due to an invalid configuration, the Cisco IOS device says it’s tunnel is DOWN:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Session status: DOWN
Peer: 1.1.1.1 port 500 
  IPSEC FLOW: permit ip 172.16.0.0/255.255.255.0 192.168.0.0/255.255.255.0 
        Active SAs: 0, origin: crypto map

Router#

When parameters mismatch, you’ll see an UP-IDLE session status:

Router#show crypto session
Crypto session current status

Interface: GigabitEthernet0/0
Profile: james-profile
Session status: UP-IDLE
Peer: 1.1.1.1 port 4500 
  Session ID: 101  
  IKEv2 SA: local 3.3.3.2/4500 remote 1.1.1.1/4500 Active 

Router#

IPsec is a compex protocol, but when it works it’s pretty cool. Shoot me a message or post a comment if you’re having trouble with a VPN tunnel, I’m always happy to help if I can.