IKEv2 VPN with Ubuntu & Apple

An IKEv2 Tunnel is a safe way to protect your internet traffic and only requires a configuration file. No additional software. We’re assuming you are running as root. If you’re currently a user you can enter sudo su and become root.

Before we begin, you must know your external network interface name, you need to come up with a private IPv4 address, and an IPv6 Unique Local Address

Finding your External Network Interface

Typically you only have a single interface. But if you’re building a VPN server you may have an internal network. Here’s something you can run to figure out what the name of your external network interface is. 

┌[ star@vpn ] ~
└➤ ip route | awk '/default/ {print $5}'
eth0

Generating a Private IPv4 Network

All you want to avoid is the super common ones. 192.168.1.0/24, 10.0.0.0/24. Private IPv4 addresses are the addresses that don’t route on the internet. RFC 1918 has it all listed out in dry detail. But let’s keep this simple. 

It’s four parts and in this case we’ll start with a 10. Every number ranges from 0 to 255. The last number will be a zero and you will add /24 at the end. These all work:

  • 10.1.2.0/24
  • 10.255.0.0/24
  • 10.84.67.0/24

Generating a ULA IPv6 Network

IPv6 uses hexadecimal and is longer. That’s the hard part. a ULA starts with fdand has three parts we’ll create with a ::/64 at the end. 

  • fd:1:2:3::/64
  • fd:ffff:ffff:ffff::/64
  • fd:abcd:ef:1::/64

Generating a Universally Unique Identifier

This is a simple command.

uuidgen

This will output a long randomly generated identifier like af732f13-4368-4450-a671-0e70c18035cc. You will need that later. 

Install Packages

Install the required packages.

apt install -y certbot strongswan libstrongswan-standard-plugins strongswan-libcharon libcharon-extra-plugins libcharon-extauth-plugins

Certificate

We’re going to use a free Let’s Encrypt certificate with modern encryption. 

nano /etc/letsencrypt/cli.ini

Enter these contents:

standalone = true
agree-tos = true
non-interactive = true
preferred-challenges = http
key-type = ecdsa
elliptic-curve = secp384r1
email = [YOUR EMAIL]
pre-hook = /sbin/ufw allow from any to any port 80 proto tcp
post-hook = /sbin/ufw delete allow from any to any port 80 proto tcp
renew-hook = /usr/sbin/ipsec reload && /usr/sbin/ipsec secrets

The purpose of the pre-hook and post-hook is so you don’t keep 80/TCP open all the time. Just open it for the request & renewal then shut it afterwards.

Now we generate the certificate.

certbot certonly --key-type ecdsa -d [YOUR DOMAIN]

Link the certificate where IPSec can find it. 

ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/cert.pem"    /etc/ipsec.d/certs/cert.pem
ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/privkey.pem" /etc/ipsec.d/private/privkey.pem
ln -f -s "/etc/letsencrypt/live/[YOUR DOMAIN]/chain.pem"   /etc/ipsec.d/cacerts/chain.pem

AppArmor Configuration

Create the configuration file

nano /etc/apparmor.d/local/usr.lib.ipsec.charon

Enter this one line in the configuration file.

/etc/letsencrypt/archive/[YOUR DOMAIN]/* r,

Now enable app armor with the configuration.

aa-status --enabled && invoke-rc.d apparmor reload

Sysctl Configuration

First step is adding onto the sysctl.conf file. 

nano /etc/sysctl.conf

Enter this at the end of the configuration profile.

net.ipv4.ip_forward = 1
net.ipv4.ip_no_pmtu_disc = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.forwarding = 1

Now implement the changes.

sysctl -p

UFW Forwarding

You will need to edit the forwarding policy of UFW.

nano /etc/default/ufw

Look for the line that says DEFAULT_FORWARD_POLICY="DROP" and change it to DEFAULT_FORWARD_POLICY="ACCEPT".

UFW IPv4 NAT

We’re going to add onto an existing configuration file. 

nano /etc/ufw/before.rules

Enter this at the end. 

# NAT for IPSec
*nat
:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -s [IPv4 VPN NETWORK] -o [INTERFACE] -j MASQUERADE

COMMIT

UFW IPv6 NAT

Again, we’re adding onto an existing configuration file. 

nano /etc/ufw/before6.rules

Enter this at the end.

# NAT for IPSec
*nat
:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -s [IPv6 VPN NETWORK] -o [INTERFACE] -j MASQUERADE

COMMIT

UFW Firewall Configuration

Enter these commands in terminal.

ufw allow from any to any port 500 proto udp
ufw allow from any to any port 4500 proto udp
ufw allow from [IPv4 VPN NETWORK] to any
ufw allow from [IPv6 VPN NETWORK] to any
ufw limit from [YOUR HOME IPV4 ADDRESS] to any port 22 proto tcp
ufw limit from [YOUR HOME IPV6 ADDRESS] to any port 22 proto tcp 

Now implement the changes. 

ufw enable

IPSec Configuration

You will need to edit the configuration file. It’s ok to remove it and start fresh. 

rm /etc/ipsec.conf
nano /etc/ipsec.conf

Enter this into the configuration file.

config setup
  strictcrlpolicy=yes
  uniqueids=never

conn roadwarrior
  auto=add
  compress=no
  type=tunnel
  keyexchange=ikev2
  fragmentation=yes
  forceencaps=yes
  ike=aes256gcm16-prfsha384-ecp384,aes256gcm16-prfsha256-ecp256!
  esp=aes256gcm16-ecp384!
  dpdaction=clear
  dpddelay=900s
  rekey=no
  left=%any
  leftid=@[YOUR DOMAIN]
  leftcert=cert.pem
  leftsendcert=always
  leftsubnet=::/0,0.0.0.0/0
  right=%any
  rightid=%any
  rightauth=eap-mschapv2
  eap_identity=%any
  rightdns=2a01:4f8:c17:2c61::213,2a01:4f8:c013:5ec0::154,49.12.222.213,88.198.122.154
  rightsourceip=[IPv6 VPN NETWORK],[IPv4 VPN NETWORK]
  rightsendcert=never

Configure IPSec Secrets

These are similar to your login password. You will be authenticating using a username and password. 

nano /etc/ipsec.secrets

Obviously modify this as needed. No square brackets. 

[YOUR DOMAIN] : ECDSA "privkey.pem"
[USER1] : EAP "[PASSWORD]"
[USER2] : EAP "[PASSWORD]"

Now implement the changes.

ipsec enable
ipsec restart
ipsec status

Create the Configuration Profile for macOS, iPadOS, and iOS

Read through this. You will need to enter your domain, provide a different universally unique identifier for each time it says [UNIQUE ID]. When you save it, save it with the file extension .mobileconfig.

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC '-//Apple//DTD PLIST 1.0//EN' 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'>
<plist version='1.0'>
<dict>
  <key>PayloadContent</key>
  <array>
    <dict>
      <key>IKEv2</key>
      <dict>
        <key>AuthenticationMethod</key>
        <string>None</string>
        <key>ChildSecurityAssociationParameters</key>
        <dict>
          <key>EncryptionAlgorithm</key>
          <string>AES-256-GCM</string>
          <key>IntegrityAlgorithm</key>
          <string>SHA2-384</string>
          <key>DiffieHellmanGroup</key>
          <integer>20</integer>
          <key>LifeTimeInMinutes</key>
          <integer>1440</integer>
        </dict>
        <key>DeadPeerDetectionRate</key>
        <string>Medium</string>
        <key>DisableMOBIKE</key>
        <integer>0</integer>
        <key>DisableRedirect</key>
        <integer>0</integer>
        <key>EnableCertificateRevocationCheck</key>
        <integer>0</integer>
        <key>EnablePFS</key>
        <true/>
        <key>ExtendedAuthEnabled</key>
        <true/>
        <key>IKESecurityAssociationParameters</key>
        <dict>
          <key>EncryptionAlgorithm</key>
          <string>AES-256-GCM</string>
          <key>IntegrityAlgorithm</key>
          <string>SHA2-384</string>
          <key>DiffieHellmanGroup</key>
          <integer>20</integer>
          <key>LifeTimeInMinutes</key>
          <integer>1440</integer>
        </dict>
        <key>RemoteAddress</key>
        <key>OnDemandEnabled</key>
        <integer>1</integer>
        <key>OnDemandRules</key>
        <array>
          <dict>
            <key>Action</key>
            <string>Connect</string>
          </dict>
        </array>
        <string>[YOUR DOMAIN]</string>
        <key>RemoteIdentifier</key>
        <string>[YOUR DOMAIN]</string>
        <key>UseConfigurationAttributeInternalIPSubnet</key>
        <integer>0</integer>
      </dict>
      <key>IPv4</key>
      <dict>
        <key>OverridePrimary</key>
        <integer>1</integer>
      </dict>
      <key>PayloadDescription</key>
      <string>Configures VPN settings</string>
      <key>PayloadDisplayName</key>
      <string>[YOUR DOMAIN]</string>
      <key>PayloadIdentifier</key>
      <string>com.apple.vpn.managed.[UNIQUE ID]</string>
      <key>PayloadType</key>
      <string>com.apple.vpn.managed</string>
      <key>PayloadUUID</key>
      <string>[UNIQUE ID]</string>
      <key>PayloadVersion</key>
      <integer>1</integer>
      <key>Proxies</key>
      <dict>
        <key>HTTPEnable</key>
        <integer>0</integer>
        <key>HTTPSEnable</key>
        <integer>0</integer>
      </dict>
      <key>UserDefinedName</key>
      <string>[YOUR DOMAIN]</string>
      <key>VPNType</key>
      <string>IKEv2</string>
    </dict>
  </array>
  <key>PayloadDisplayName</key>
  <string>IKEv2 VPN configuration ([YOUR DOMAIN])</string>
  <key>PayloadIdentifier</key>
<string>[UNIQUE ID]</string>
  <key>PayloadRemovalDisallowed</key>
  <false/>
  <key>PayloadType</key>
  <string>Configuration</string>
  <key>PayloadUUID</key>
  <string>[UNIQUE ID]</string>
  <key>PayloadVersion</key>
  <integer>1</integer>
</dict>
</plist>

Now that you have the .mobileconfig file created, you can get it setup on any apple device.

For macOS, I just double click on it. Then I go to Settings and click on “Profile Downloaded” to install it. Double click on the configuration profile and click “Install“.

After installing it on macOS, you will have to define the login. Go to Settings VPN > and click on the little “i” for your connection. Under “Authentication” click on “Certificate” and change it to “Username“. Enter your username and password you created in the ipsec.secrets configuration file.

Now you can use it. In macOS you can also enable it in the menu bar (control center) by going to Settings > Control Center and going to “Menu Bar Only” section and changing VPN to “Show in Menu Bar.”

For iOS or iPadOS, you may AirDrop it or email it to yourself. You can also now open it from your iCloud Drive directly in the Files app. You’ll be prompted to go to Settings. You’ll see where it says “Profile Downloaded” near the top of settings. Choose to install it and follow the prompts. You will be asked for your device passcode, your username, and your password for the VPN.

It will enable and you will be able to use it.

If you want to make it not turn on automatically, you can go to Settings > GeneralVPN & Device Management VPN and set “Connect on demand” to off.