Andrew Kroh        

2013-02-18

Creating a Site-to-Site VPN with Solaris 11

Introduction

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)


Assumptions

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

Turn off IP strict multi-homing

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

Add an IPsec Policy File

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

Create Keys for the VPN Endpoints

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.

Public Key Certificates (Self-signed)

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

Preshared Keys

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

Refresh the IPsec Policy

Assuming the ipsec/policy service is already running, refresh the config.

svcadm refresh ipsec/policy

Create an IP Tunnel

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

Turn on Packet Forwarding

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

Restart the Network Services

svcadm restart network/initial

Verify Traffic is Encrypted

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

Modify the Tunnel’s Endpoint Address

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

Debug Problems with IKE

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