ctx->guides->ipsec

Securing services with IPsec tunnels

This short document will get you started on how to configure simple IPsec tunnels between your remote hosts. All peers need to run the key management daemon and have IPsec enabled and allowed through PF.

/etc/rc.conf.local:

isakmpd_flags="-K"
ipsec=YES

/etc/pf.conf:

# Allow incoming IPsec traffic
pass quick on egress proto {esp, ah}
pass in quick on egress proto udp to port {isakmp, ipsec-nat-t}

The next part shows how we run mail message submission and POP version 3 services through IPsec tunnels.

Server configuration

/etc/ipsec.conf:

peers="{hosta.example.org, hostb.example.org, hostc.example.org}"
ike passive esp proto tcp from egress to $peers \
    port submission \
    psk "hackme"
ike passive esp proto tcp from egress to $peers \
    port pop3 \
    psk "hackmemore"

Client configuration

/etc/ipsec.conf:

peer=ipsec.example.org
ike esp proto tcp from egress to $peer \
    port submission \
    psk "hackme"
ike esp proto tcp from egress to $peer \
    port pop3 \
    psk "hackmemore"

The isakmpd server uses UDP port 500 for key management; UDP port 4500 is used for IPsec NAT features. You should forward those ports on your router if needed. The ports can be found with:

$ grep ipsec /etc/services
ipsec-nat-t     4500/tcp        ipsec-msft      # IPsec NAT-Traversal
ipsec-nat-t     4500/udp        ipsec-msft      # IPsec NAT-Traversal
$ grep isakmp /etc/services
isakmp          500/udp                         # ISAKMP key management

Furthermore, here is a little trick to bypass some nasty firewalls that block low ports. This makes port 500 to effectively appear as port 5000 to other peers.

/etc/pf.conf:

# Rewrite outgoing src port isakmp to src port 5000 and back
pass out on egress proto udp from any port isakmp to any port isakmp \
    nat-to (egress) port 5000

Testing the setup

If all went OK, from a client you should see something like the following, when the server's IP address is 1.2.3.4 while the client's IP address is 5.6.7.8.

$ ipsecctl -s flow
FLOWS:
flow esp in proto tcp from 1.2.3.4 port pop3 to 5.6.7.8 peer 1.2.3.4 srcid 5.6.7.8/32 dstid 1.2.3.4/32 type use
flow esp out proto tcp from 5.6.7.8 to 1.2.3.4 port pop3 peer 1.2.3.4 srcid 5.6.7.8/32 dstid 1.2.3.4/32 type require
flow esp in proto tcp from 1.2.3.4 port submission to 5.6.7.8 peer 1.2.3.4 srcid 5.6.7.8/32 dstid 1.2.3.4/32 type use
flow esp out proto tcp from 5.6.7.8 to 1.2.3.4 port submission peer 1.2.3.4 srcid 5.6.7.8/32 dstid 1.2.3.4/32 type require

$ route -n show -encap
Routing tables

Encap:
Source      Port  Destination   Port  Proto  SA(Address/Proto/Type/Direction)
1.2.3.4/32   110   5.6.7.8/32      0      6     1.2.3.4/esp/use/in
5.6.7.8/32     0   1.2.3.4/32    110      6     1.2.3.4/esp/require/out
1.2.3.4/32   587   5.6.7.8/32      0      6     1.2.3.4/esp/use/in
5.6.7.8/32     0   1.2.3.4/32    587      6     1.2.3.4/esp/require/out

$ nc -v server.example.org 587
Connection to server.example.org 587 port [tcp/submission] succeeded!
$ nc -v server.example.org 110
Connection to server.example.org 110 port [tcp/pop3] succeeded!
+OK

Notes

There is no actual server and client in the protocol, however in this setup the server knows all other hosts, and they only know the server. The server is configured as passive while others start negotiation immediately. The server needs to know all peers in order to correctly setup the Encap routes. A config with peers=any will create a default route pointing to the peer negotiated last. Such a setup is broken!

That is certainly not all folks!

Cheers!

lostd@