Debian IPsec Micro‐Howto

Updated for Debian 7 (wheezy) on 2014‑06‑23. The old texts for Debian 4.0 (etch) and Debian 3.1 (sarge) are still available.

This aims to be a simple how‐to for setting up IPsec in Debian.

Why Another Howto?

So what’s wrong with other howtos about how to setup IPsec? Well, they usually don’t cover Debian specifically, but that’s not my chief problem. The main thing which makes other IPsec texts unsuitable for me is that they all assume I’m using IPsec to do a VPN. Well, I’m not, I’m using it to get IP security for otherwise completely insecure thing like NIS and NFS. And once you’ve got it running you can enable things like FTP and even telnet, and you can then run X over the network like it was meant to be run and not inside some ugly SSH tunnel.

Authentication Methods

Another thing which put me off other texts about IPsec was that they all used PSKs in their examples and then, maybe, covered X.509 certificates at the end, and only as a list of things to do differently. This irks me because I prefer to use OpenPGP keys, and don’t want to be forced to learn how to set up IPsec into a configuration I won’t be using in order to only then learn how to do what I really want to do.

Well, enough ranting.

What Implementation?

Debian has contained many implementations of IPsec, and still does, but the prevailing one seems to be strongSwan.

Anyway, on to the installation.

Installation

Simply install the strongswan package.

The startup init scripts

The default strongswan-starter package is not configured to start up or shut down in the right sequence on system reboot or shutdown, so we need to edit some files and add some links. First, we patch the header in the init.d startup script:

# patch --backup --version-control=numbered --strip=0 <<EOF
--- /etc/init.d/ipsec.~1~	2012-06-15 16:21:24.000000000 +0200
+++ /etc/init.d/ipsec	2014-06-22 17:34:58.000000000 +0200
@@ -1,9 +1,10 @@
 #! /bin/sh
 ### BEGIN INIT INFO
 # Provides:          ipsec
-# Required-Start:    \$network \$remote_fs
-# Required-Stop:     \$network \$remote_fs
-# Default-Start:     2 3 4 5
+# Required-Start:    \$network \$local_fs
+# Required-Stop:     \$network \$local_fs
+# X-Start-Before:    rpcbind mountnfs
+# Default-Start:     S 2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: Strongswan IPsec services
 ### END INIT INFO
EOF
patching file /etc/init.d/ipsec
# 

Then we remove and add some links:

# update-rc.d -f ipsec remove
update-rc.d: using dependency based boot sequencing
# update-rc.d -f ipsec start 41 S 2 3 4 5 . stop 91 1 . start 34 0 6 .
update-rc.d: using dependency based boot sequencing
# 

Now we can safely start and stop IPsec. But it won’t do anything without configuration.

Configuration files

First, you need to create your OpenPGP public and private keys. Using plain gpg, this is a bit of work, but I have previously written a script to generate OpenPGP keys for Mandos, so I prefer to just use that. (You can download the script directly, or just install the mandos-client package.) Anyway, we first need to create our keys. We do this in the /etc/ipsec.d/private directory:

# mandos-keygen --dir /etc/ipsec.d/private
Note: Due to entropy requirements, key generation could take
anything from a few minutes to SEVERAL HOURS.  Please be
patient and/or supply the system with more entropy if needed.
Started: Mon Jun 23 03:20:53 CEST 2014
.........+++++
................+++++
......+++++
..........+++++
Finished: Mon Jun 23 03:23:25 CEST 2014
# 

Then rename the key files and move them to their correct locations:

# cd /etc/ipsec.d/private
# mv seckey.txt $(hostname)-key.asc
# mv pubkey.txt ../certs/$(hostname).asc
# 

We need to make sure strongSwan knows about our private key, so edit /etc/ipsec.secrets and add one line:

# cat >> /etc/ipsec.secrets <<EOF
: RSA $(hostname)-key.asc
EOF
# 

If you are using IPv4 addresses as well as IPv6, you need to create separate IPsec configurations for the IPv4 addresses, and also generate separate keys for those connections, otherwise it won’t work. (I’m guessing that strongSwan gets confused by multiple connections to the same host with the same key, but I really don’t know.)

# mandos-keygen --name $(hostname)-v4 --dir /etc/ipsec.d/private
Note: Due to entropy requirements, key generation could take
anything from a few minutes to SEVERAL HOURS.  Please be
patient and/or supply the system with more entropy if needed.
Started: Mon Jun 23 03:26:27 CEST 2014
..+++++
.....................................................+++++
..+++++
......+++++
Finished: Mon Jun 23 03:29:04 CEST 2014
# mv seckey.txt $(hostname)-v4-key.asc
# mv pubkey.txt ../certs/$(hostname)-v4.asc

And add your IPv4 private key to /etc/ipsec.secrets:

# cat >> /etc/ipsec.secrets <<EOF
: RSA $(hostname)-v4-key.asc
EOF
# 

Firewall

Before we get IPsec configured and running, we need some preparations. After an IPsec SA is established to a specific IP address, an entry will automatically be added to the IPsec SPD, which will allow only IPsec traffic, and non‑encrypted traffic will not be allowed, which sounds good, except for the fact that this includes the IKEv2 protocol needed to set up the IPsec SA’s in the first place. So, while the connection could be established once, it can not be rekeyed, and will only work until re‑keying is required, and will then fail until both ends restart their IPsec daemons. Therefore, we need to add policies to the IPsec SPD to allow all necessary traffic which can not be IPsec encrypted. Also, while all possible traffic is now encrypted, unencrypted traffic is still accepted until an IPsec SA is negotiated, so if, e.g. the IPsec system never starts on one host for some reason, you will be using unencrypted traffic and never know it. This is not acceptable; we need assurances that IPsec is always used for those IP addresses we have configured.

For these things, we need a firewall and the iptables package. (Historical note: It used to be that the IPsec Security Policy Database could be used as a de‐facto firewall, but strongSwan insists on managing and changing the SPD itself – most notably erasing it on exit – so this is currently not possible.) Here is a script I have written to set up a firewall based on what is in the /etc/ipsec.conf file created above:

Download

#!/bin/sh

## Add exceptions to IPsec SPD
# Accept IKE and IKEv2
for ipaddr in ::/0 0.0.0.0/0; do
    for port in 500 4500; do
	for dir in in out; do
	    ip xfrm policy update src $ipaddr dst $ipaddr proto udp \
		sport $port dport $port dir $dir ptype main
	done
    done
done
# Accept ICMPv6 neighbour-*
for dir in in out; do
    for type in 135 136; do
	ip xfrm policy update src ::/0 dst ::/0 proto ipv6-icmp type \
	    $type code 0 dir $dir ptype main
    done
done

## Add exceptions to firewall
for chain in INPUT OUTPUT; do
    for iptables in iptables ip6tables; do
	$iptables --flush $chain
	# Accept encrypted packets
	$iptables --append $chain --protocol esp --jump ACCEPT
	# Accept IKE and IKEv2
	for port in 500 4500; do
	    $iptables --append $chain --proto udp \
		--source-port $port --destination-port $port \
		--jump ACCEPT
	done
    done
    if [ "$chain" = INPUT ]; then
	ip6tables --append INPUT --protocol udp --match frag \
	    --fragid 0 --fraglast --jump ACCEPT
    fi
    # Accept ICMPv6 neighbour-*
    ip6tables --append $chain --protocol icmpv6 --match icmp6 \
	--icmpv6-type neighbour-solicitation --jump ACCEPT
    ip6tables --append $chain --protocol icmpv6 --match icmp6 \
	--icmpv6-type neighbour-advertisement --jump ACCEPT
done

for iptables in iptables ip6tables; do
    if ! $iptables --new-chain reject_ipsec_hosts 2>/dev/null; then
	$iptables --flush reject_ipsec_hosts
    fi
done

for iptables in iptables ip6tables; do
    # Accept packets which will match an IPsec policy
    $iptables --append INPUT --match policy --dir in --pol ipsec \
	--mode transport --jump ACCEPT
    # Reject packets from IPsec-only hosts
    $iptables --append INPUT --jump reject_ipsec_hosts
    
    # Accept packets which will be encrypted
    $iptables --append OUTPUT --match policy --dir out --pol ipsec \
	--mode transport --jump ACCEPT
    # Reject packets for IPsec-only hosts
    $iptables --append OUTPUT --jump reject_ipsec_hosts
done

ipsec_transport(){
    : "ipsec_transport 2001:470:1f15:1a45::4/128 88.80.26.40/32"
    my_ipv6="$1"
    my_ipv4="$2"
    peer_ip="$3"
    case "$peer_ip" in
	*:*)
	    peer_ipv6="${peer_ip}/128"
	    ip6tables --append reject_ipsec_hosts --source \
		"${my_ipv6}"/128 --destination "${peer_ipv6}" --jump \
		REJECT --reject-with icmp6-adm-prohibited
	    ip6tables --append reject_ipsec_hosts --source \
		"${peer_ipv6}" --destination "${my_ipv6}"/128 --jump \
		REJECT --reject-with icmp6-adm-prohibited
	    ;;
	*.*)
	    peer_ipv4="${peer_ip}/32"
	    iptables --append reject_ipsec_hosts --source \
		"${my_ipv4}"/32 --destination "${peer_ipv4}" --jump \
		REJECT --reject-with icmp-admin-prohibited
	    iptables --append reject_ipsec_hosts --source \
		"${peer_ipv4}" --destination "${my_ipv4}"/32 --jump \
		REJECT --reject-with icmp-admin-prohibited
	    ;;
    esac
}

my_ipv6=$(sed --quiet --expression='s/^[^#]*[[:space:]]left=\([^#]*:[^#]*\)#\?.*$/\1/p' /etc/ipsec.conf)
export my_ipv6
my_ipv4=$(sed --quiet --expression='s/^[^#]*[[:space:]]left=\([^#]*\.[^#]*\)#\?.*$/\1/p' /etc/ipsec.conf)
export my_ipv4

sed --quiet --expression='s/^[^#]*[[:space:]]right=\(.*[.:]\)/\1/p' \
    /etc/ipsec.conf | while read peer_ip; do
    ipsec_transport "${my_ipv6}" "${my_ipv4}" "${peer_ip}"
done

(It is a bit long, since it can accept IPv4 addresses as well.) I suggest that you save this script as /usr/local/sbin/firewall and run it as a pre-up command in /etc/network/interfaces, something like this:

auto eth0
iface eth0 inet6 static
	pre-up /usr/local/sbin/firewall || :
	address 2001:db8::2
	netmask 64
	gateway 2001:db8::1	
	dns-nameservers 2001:db8:1::4 2001:db8:1::5
	dns-search example.org

Be sure to modify the pre-up setting to reflect the full name and location of the above firewall script, in case you renamed it and/or placed it somewhere else.

Now, the only actual configuration file, the /etc/ipsec.conf file. I’m assuming a host name of “host1” with IP address 2001:db8::2 and an IPsec connection to another host named “host2” with IP address 2001:db8::3

config setup
        charonstart=yes
	plutostart=no
	nocrsend=yes

conn %default
        keyexchange=ikev2
        type=transport
        auto=ignore
        left=2001:db8::2
        mobike=no
        leftcert=host1.asc
	leftupdown=/usr/local/sbin/firewall

conn to_host2
	auto=start
	right=2001:db8::3
	rightcert=host2.asc

Be sure to modify the conn %default/leftupdown setting to reflect the full name and location of the above firewall script, in case you renamed it and/or placed it somewhere else.

Now, repeat (and rename) that last conn to_* section for each additional host to configure for IPsec.

If using IPv4 addresses as well, you need some more lines. I’m assuming IPv4 addresses 192.0.2.2 and 192.0.2.3 for host1 and host2, respectively:

conn ipv4_default
	left=192.0.2.2
	leftcert=host1-v4.asc

conn to_host2_v4
	also=ipv4_default
	auto=start
	right=192.0.2.3
	rightcert=host2-v4.asc

Again, repeat (and rename) that last conn to_* section for each additional host to configure for IPsec.

Now you can start strongSwan by typing service ipsec start and, once you have configured the other host similarly and started it there as well, you should have IPsec running. You can test it by running the ipsec status command. The IPsec connection (or “Security Association”) should show as “ESTABLISHED”.

With this configuration, we will no longer accept any non‐IPsec packets from host2, and will also send only IPsec packets to that host. Now that’s what I call real network security.

CC0
To the extent possible under law, Teddy Hogeborn has waived all copyright and related or neighboring rights to Debian IPsec Micro‐Howto. This work is published from: Sverige.