Andrew Kroh
2013-02-18
This document describes how to setup a persistent secure tunnel between two sites known as “VA” and “NY”. The tunnel will be created using Solaris 11 and its built in IP Tunnels and IPsec.
Each site has a single public facing dynamic IP and uses NAT on the internal network. The internal NAT subnet is 192.168.0.0/24 on both sides. Because the address space is the same on both sides of the tunnel we will create a new unique subnet at each site hosted on a separate NIC. Only hosts on this new subnet will be able to route traffic across the VPN.
Figure 1 below shows the network that will be created in this document.
Figure 1: Network Diagram Showing VA (left) and NY (right) |
The table below shows all the IP addresses used throughout the document.
Description | Interface Name | IP |
VA Site Public Address | outside | 111.111.111.111 (dynamic) |
VA Solaris Server Internal Address | net0/v4 | 192.168.0.50/24 |
VA Solaris Server Internal Address (new) | net1/v4 | 172.16.1.1/24 |
VA Solaris Server VPN Endpoint Address | vpn0/v4 | 172.16.0.1/30 |
NY Site Public Address | outside | 222.222.222.222 (dynamic) |
NY Solaris Server Internal Address | net0/v4 | 192.168.0.60/24 |
NY Solaris Server Internal Address (new) | net1/v4 | 172.16.2.1/24 |
NY Solaris Server VPN Endpoint Address | vpn0/v4 | 172.16.0.2/30 |
Strict multi-homing means that any packet received by an interface must be addressed to an IP address assigned to that interface. We need this to be off because our system is going to act as a gateway for packets destined for the VPN at the other site, and these packets will not be destined for the interface’s address. This behavior is controlled by the hostmodel property.
Set the hostmodel property to weak (off) for ipv4 packets.
ipadm set-prop -p hostmodel=weak ipv4 |
This policy file defines the rules that are used in determining which packets require IPsec.
/etc/inet/ipsecinit.conf for VA
# LAN traffic to and from this host can bypass IPsec. {laddr 192.168.0.50 dir both} bypass {} {laddr 172.16.1.1 dir both} bypass {} # VPN traffic uses ESP with AES-256+ and # SHA-2 (with 512 byte block size). {tunnel vpn0 negotiate tunnel} ipsec {encr_algs aes(256..) encr_auth_algs sha512 sa shared} |
/etc/inet/ipsecinit.conf for NY
# LAN traffic to and from this host can bypass IPsec. {laddr 192.168.0.60 dir both} bypass {} {laddr 172.16.2.1 dir both} bypass {} # VPN traffic uses ESP with AES-256+ and # SHA-2 (with 512 byte block size). {tunnel vpn0 negotiate tunnel} ipsec {encr_algs aes(256..) encr_auth_algs sha512 sa shared} |
Verify the syntax of the policy file.
ipsecconf -c /etc/inet/ipsecinit.conf |
Configure the svc:/network/ipsec/policy:default service to use /etc/inet/ipsecinit.conf as its policy file. This does not appear to be configured as the default in Solaris 11 even though the documentation states that it is.
svccfg -s ipsec/policy setprop config/config_file = /etc/inet/ipsecinit.conf |
There are two options for keys -- preshared or public key certificates. Preshared keys are simple but are tied to IP addresses so if either endpoint is behind NAT or your endpoint IPs can change then you need to use certificates.
Generate a self-signed certificate and store it into the ike.privatekeys database.
ikecert certlocal -ks -m 4096 -t rsa-sha1 -D "C=US, O=Foo Inc., OU=Engineering, CN=va" -A DNS=va.foo.com |
Export the certificate so that it can be installed on the remote server.
ikecert certdb -e va.foo.com > ~/va.foo.com.crt |
Send the certificate to the remote server.
scp ~/va.foo.com.crt user@ny.foo.com:~ |
Install the public certificate for home.crowbird.com on the remote system.
ssh user@ny.foo.com sudo /usr/sbin/ikecert certdb -a < ~/va.foo.com.crt |
Repeat those steps from the perspective of the other system.
Now setup configure the IKE service to use the certificates.
/etc/inet/ike/config for VA
# Trust the following certificates (identified by DN): cert_trust "C=US, O=Foo Inc., OU=Engineering, CN=va" cert_trust "C=US, O=Foo Inc., OU=Engineering, CN=ny" { label "va-to-ny" local_id_type dn local_id "C=US, O=Foo Inc., OU=Engineering, CN=va" remote_id "C=US, O=Foo Inc., OU=Engineering, CN=ny" local_addr 192.168.0.50 remote_addr 0.0.0.0/0 # IKE Phase 1 (Main Mode) parameters: # Oakley Diffie-Hellman group 16 uses a 4096-bit key. # You can lookup value 16 with 'ikeadm dump groups'. p1_xform { auth_method rsa_sig oakley_group 16 auth_alg sha512 encr_alg aes(256..) } # IKE Phase 2 (Quick Mode) parameters: # Use Perfect Forward Secrecy for Phase 2 (with Oakley DH group 16). p2_pfs 16 } |
/etc/inet/ike/config for NY
# Trust the following certificates (identified by DN): cert_trust "C=US, O=Foo Inc., OU=Engineering, CN=va" cert_trust "C=US, O=Foo Inc., OU=Engineering, CN=ny" { label "va-to-ny" local_id_type dn local_id "C=US, O=Foo Inc., OU=Engineering, CN=ny" remote_id "C=US, O=Foo Inc., OU=Engineering, CN=va" local_addr 192.168.0.60 remote_addr 0.0.0.0/0
# IKE Phase 1 (Main Mode) parameters: # Oakley Diffie-Hellman group 16 uses a 4096-bit key. # You can lookup value 16 with 'ikeadm dump groups'. p1_xform { auth_method rsa_sig oakley_group 16 auth_alg sha512 encr_alg aes(256..) } # IKE Phase 2 (Quick Mode) parameters: # Use Perfect Forward Secrecy for Phase 2 (with Oakley DH group 16). p2_pfs 16 } |
Verify that the syntax of the config file.
/usr/lib/inet/in.iked -c -f /etc/inet/ike/config |
Restart the IKE service (or enable it if it’s not already running).
svcadm restart ipsec/ike |
If you want to create an IPsec tunnel between two hosts with static addresses and no NAT between them you can use preshared keys. I’ve included this here for only completeness, but it should not be used in the scenario described above because of NAT.
/etc/inet/ike/config for VA
{ label "site-to-site vpn" local_addr 192.168.0.50 remote_addr 192.168.0.60 # IKE Phase 1 (Main Mode) parameters: # Oakley Diffie-Hellman group 16 uses a 4096-bit key. # You can lookup value 16 with 'ikeadm dump groups'. p1_xform { auth_method preshared oakley_group 16 auth_alg sha512 encr_alg aes(256..) } # IKE Phase 2 (Quick Mode) parameters: # Use Perfect Forward Secrecy for Phase 2 (with Oakley DH group 16). p2_pfs 16 } |
Verify that the syntax of the config file.
/usr/lib/inet/in.iked -c -f /etc/inet/ike/config |
/etc/inet/secret/ike.preshared for VA
{ localidtype IP localid 192.168.0.50 remoteidtype IP remoteid 192.168.0.60 key "password123" } |
Restart the IKE service (or enable it if it’s not already running).
svcadm restart ipsec/ike |
Assuming the ipsec/policy service is already running, refresh the config.
svcadm refresh ipsec/policy |
Run these commands on the VA side.
# Create an endpoint address for the VPN. ipadm create-ip net1 ipadm create-addr -T static -a 172.16.1.1/24 net1/v4 # Create the data-link tunnel between the two hosts’ “external” addresses. dladm create-iptun -T ipv4 -a local=192.168.0.50,remote=222.222.222.222 vpn0 ipadm create-ip vpn0 # Create an IPv4 tunnel between these internal addresses. ipadm create-addr -T static -a local=172.16.0.1/30,remote=172.16.0.2 vpn0/v4 # Add a route to the NY subnet through the VPN tunnel gateway address. route -p add 172.16.2.0/24 172.16.0.1 -interface |
Run these commands on the NY side.
# Create an endpoint address for the VPN. ipadm create-ip net1 ipadm create-addr -T static -a 172.16.2.1/24 net1/v4 # Create the data-link tunnel between the two hosts’ “external” addresses. dladm create-iptun -T ipv4 -a local=192.168.0.50,remote=222.222.222.222 vpn0 ipadm create-ip vpn0 # Create an IPv4 tunnel between these internal addresses. ipadm create-addr -T static -a local=172.16.0.2/30,remote=172.16.0.1 vpn0/v4 # Add a route to the NY subnet through the VPN tunnel gateway address. route -p add 172.16.1.0/24 172.16.0.2 -interface |
IPv4 forwarding needs to be enabled to allow packets to pass from the 172.16.1.0/24 subnet to the VPN endpoint and vice versa. For added security you could layer a firewall (see man ipf) on top to control packet input and output.
routeadm -e ipv4-forwarding routeadm -u ipadm set-ifprop -m ipv4 -p forwarding=on net0 ipadm set-ifprop -m ipv4 -p forwarding=on vpn0 |
svcadm restart network/initial |
The snoop command can be used to verify that traffic is encrypted over the net0 interface. You can also use it to see the traffic inside the tunnel (on the vpn0 interface).
To monitor traffic on net0 where you should see ESP packets:
snoop -d net0 -v 222.222.222.222 |
To monitor traffic inside the tunnel:
snoop -d vpn0 -v 172.16.1.2 |
If a tunnel’s endpoint IP address changes (if you have a dynamic IP) you need to update the data link’s remote address. For example if NY’s public site address changes to 222.222.222.223 then you need to update VA’s vpn0 to reflect the new address for NY. There is no need to restart any services when you make this change.
dladm modify-iptun -a remote=[new ip address] vpn0 |
I have written a shell script which can be invoked by cron to automatically updated the tunnel’s endpoint address as needed. Below is a copy of the script. It is followed by instructions how to update root’s crontab to execute the script every minute.
~/update-tunnel-endpoint.sh
#!/bin/sh # File: update-vpn-endpoint.sh # Author: Andrew Kroh usage() { cat << EOF usage: $0 options Use this script to update the remote address of an IP tunnel using a hostname. It compares the resolved IP address to the remote address of the specified tunnel and modifies the tunnel if they are different. This script should be used when the endpoint's IP address is dynamic and the remote host automatically updates DNS when it detects a change. You should not pass a hostname to dladm because it only resolves the address once at boot time which would require an /etc/host entry since DNS may not be available yet. OPTIONS: -t IP tunnel link name (from dladm show-iptun) -h Hostname of tunnel endpoint -v Enable verbose output -? Prints this help message EOF } log_msg() { echo "$(date +"%c"): $1" } tunnel_name= hostname= verbose=0 while getopts ":t:h:v" OPTION do case $OPTION in t) tunnel_name=$OPTARG ;; h) hostname=$OPTARG ;; v) verbose=1 ;; ?) usage exit 1 ;; esac done if [ -z "$tunnel_name" ] then usage exit 1 fi if [ -z "$hostname" ] then usage exit 1 fi ip_address=$(/usr/sbin/dig +short $hostname | tail -1) if [ $? -ne 0 ] then echo "Error running dig command." exit 1 fi tunnel_endpoint_address=$(/usr/sbin/dladm show-iptun \ -p $tunnel_name | cut -d ':' -f 5) if [ $? -ne 0 ] then echo "Error running dladm command." exit 1 fi if [ "$ip_address" != "$tunnel_endpoint_address" ] then [ $verbose == "1" ] && log_msg "$tunnel_name needs updated from" \ "$tunnel_endpoint_address to $ip_address." /usr/sbin/dladm modify-iptun -a remote=$ip_address $tunnel_name if [ $? -eq 0 ] then log_msg "$tunnel_name successfully updated to $ip_address." else echo "Error modifying IP tunnel endpoint." exit 1 fi else [ $verbose == "1" ] && log_msg "$tunnel_name is up-to-date." fi exit 0 |
To update crontab on the VA system use the commands below.
sudo crontab -l > rootcron echo '* * * * * /full/path/to/update-vpn-endpoint.sh -h ny.foo.com -t vpn0 >> /var/log/update-vpn-endpoint.log 2>&1' >> rootcron sudo crontab rootcron rm rootcron |
Once you have connectivity between you may need to debug issues with negotiating keys. For this you can turn on all debug for the IKE service and watch the output. Once you are monitoring IKE output, try sending a ping across the tunnel. See man ikeadm for a list of individual debug levels.
ikeadm set debug all tail -f /var/log/in.iked.log |