My battle with Site-to-Site IPSEC (VTI): A tutorial of sorts



  • I was extremely bored today. Decided to setup an IPv4/v6 VTI to a VPS I purchased. Please see below and ask questions as needed. I have not cleaned up anything or did any proofreading, so if you see something off let me know.

    For informational purposes:
    
    PFSENSE HOST FQDN: fw.foundry.cx
    PFSENSE WAN IP: 77.77.77.77
    PFSENSE WAN NETWORK: 77.77.77.0/24
    PFSENSE LOCAL MANAGEMENT SUBNET: 172.16.0.0/29
    PFSENSE LAN SUBNET: 172.16.0.0/12
    PFSENSE VTI IP: 192.168.254.2
    
    VPS HOST FQDN = sentinel.foundry.cx
    VPS WAN IP    : 88.88.88.88
    PFSENSE VTI IP: 192.168.254.1
    
    

    Create a CA on PFSENSE HOST. I called mine "FOUNDRY-Server-CA"
    Once done, create 2 certificates (I named mine the following):

    • FOUNDRY-Server-LOCALCERT : This will be the PFSENSE IPSEC TUNNEL CERT

    • FOUNDRY-Server-REMOTECERT : This will be the REMOTE VIRTUAL PRIVATE SERVER (VPS) host. I use CentOS 7/8 on any VPS that I run fyi.

    • Once you create the certs above, export the "FOUNDRY-Server-REMOTECERT" certificate, private key, and the "Foundry-Server-CA" certificate and save them on your computer for later.

    • Next, create the following rules to accept incoming IPSEC connections on the PFSENSE host.

    Permit source 88.88.88.88 from any source port to destination 77.77.77.77 on UDP port 500
    Permit source 88.88.88.88 from any source port to destination 77.77.77.77 on UDP port 4500 (<-May not be needed)

    If your PFSENSE host is only acting as an initiator, then no inbound firewall rules need to be configured! This is common.

    • Next up, go into VPN > IPSEC on the PFSENSE host.
    • Click "Add P1" and use the following settings:
    Key Exchange version = IKEv2
    Internet Protocol  = Dual Stack
    Interface = WAN (Or whatever interface you use for a WAN)
    Remote Gateway = 88.88.88.88
    Description = <Whatever you want to call it>
    
    Authentication method = mutual rsa
    My identifier = distinguished name = fw.foundry.cx
    Peer identifier = distinguished name = sentinel.foundry.cx
    My certificate = FOUNDRY-Server-LOCALCERT
    Peer Certificate Authority = FOUNDRY-Server-CA
    
    Encryption Algorith
     - AES256-GCM	=	128 bits	=	SHA384	= Group 16
     - AES192-GCM	=	128 bits	=	SHA384	= Group 16
    
    Lifetime = 28800
    
    Under advanced options, leave everything as is. Instead of SHA384, you can use AES-XCBC. I'm testing it now.
    

    Under advanced options, leave everything as is. Go back to the previous page;
    Under VPN > IPsec > Tunnels, click "Show phase 2 entries", then click " ADD P2" and input the following settings:

    Mode = Routed (VTI)
    Local Network = 192.168.254.2/30
    Remote Network = 192.168.254.1/0
    
    Protocol = ESP
    Encryption Algorithms = 
      AES192-GCM - 128 bits
      AES256-GCM - 128 bits
    Hash Algortihms =
      SHA384
    PFS Key Group = 16
    Lifetime =3600
    
    Automatically Ping Host = 192.168.254.1
    

    Click save and Lets move to the CentOS7 VPS!

    Once you are logged in as root (and you've ran yum -y update [and dnf update if using centos 8]), apply the following commands

    (Install epel)

    yum -y install epel-release
    

    (Upgrade the kernel to latest)

    rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
    rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
    yum --enablerepo=elrepo-kernel install -y kernel-ml
    sed -i "s:GRUB_DEFAULT=saved:GRUB_DEFAULT=0:g" /etc/default/grub
    grub2-mkconfig -o /boot/grub2/grub.cfg
    

    (Install strongswan)

    yum --enablerepo=epel -y install  strongswan
    

    (Erase cert files that are present if any)

    echo "" > /etc/strongswan/ipsec.d/cacerts/cacert.pem
    echo "" > /etc/strongswan/ipsec.d/certs/ipsechost-cert.pem
    echo "" > /etc/strongswan/ipsec.d/private/ipsechost-key.pem
    

    For the file "cacert.pem", paste the contents of the FOUNDRY-Server-CA crt in here.
    For the file "ipsechost-cert.pem", paste the contents of the FOUNDRY-Server-REMOTECERT crt file in here
    For the file "ipsechost-key.pem", paste the contents of the FOUNDRY-Server-REMOTECERT key file in here

    You can use vi or nano to accomplish that, or use the commands below:

    rm -rf /etc/strongswan/ipsec.d/cacerts/cacert.pem
    touch /etc/strongswan/ipsec.d/cacerts/cacert.pem
    echo "
    Please paste the CA CERTIFICATE below :
    "
    python -c 'import sys;print("Type Ctrl + D when finished");f=open("/etc/strongswan/ipsec.d/cacerts/cacert.pem","w");[f.write(l) for l in sys.stdin.readlines()];f.close()'
    
    rm -rf /etc/strongswan/ipsec.d/certs/ipsechost-cert.pem
    touch /etc/strongswan/ipsec.d/certs/ipsechost-cert.pem
    echo "
    Please paste the HOST CERTIFICATE below :
    "
    python -c 'import sys;print("Type Ctrl + D when finished");f=open("/etc/strongswan/ipsec.d/certs/ipsechost-cert.pem","w");[f.write(l) for l in sys.stdin.readlines()];f.close()'
    
    rm -rf /etc/strongswan/ipsec.d/private/ipsechost-key.pem
    touch /etc/strongswan/ipsec.d/private/ipsechost-key.pem
    echo "
    Please paste the PRIVATE KEY CERTIFICATE below :
    "
    python -c 'import sys;print("Type Ctrl + D when finished");f=open("/etc/strongswan/ipsec.d/private/ipsechost-key.pem","w");[f.write(l) for l in sys.stdin.readlines()];f.close()'
    

    (Configure the connection)

    echo "" > /etc/strongswan/ipsec.conf
    cat <<EOF | tee /etc/strongswan/ipsec.conf
    conn A-B
      left=88.88.88.88
      right=77.77.77.77
      leftsubnet = ::/0,0.0.0.0/0
      rightsubnet = ::/0,0.0.0.0/0
      ike=aes256gcm128-sha384-modp4096,aes192gcm128-sha384-modp4096!
      esp=aes192gcm128-sha384-modp4096,aes256gcm128-sha384-modp4096!
      ikelifetime=28800s
      lifetime=14400s
      dpdaction=restart
      dpddelay=10s
      dpdtimeout=60s
      rekey=yes
      keyexchange=ikev2
      reauth=yes
      auto=start
      fragmentation=yes
      leftcert=ipsechost-cert.pem
      leftid=fqdn:sentinel.foundry.cx
      rightauth=pubkey
      leftauth=pubkey
      leftsendcert=always
      leftca="/CN=CN=FOUNDRY-Server-CA/C=US/ST=new jersey/L=newark/O=The Foundry/"
      rightid=fqdn:fw.foundry.cx
      mark=12
    EOF
    

    (Define private key)

    cat <<EOF | tee /etc/strongswan/ipsec.secrets
    : RSA ipsechost-key.pem
    EOF
    

    (Configure CHARON)

    echo "" > /etc/strongswan/strongswan.conf
    cat <<EOF | tee /etc/strongswan/strongswan.conf
    # strongswan.conf - strongSwan configuration file
    #
    # Refer to the strongswan.conf(5) manpage for details
    #
    # Configuration changes should be made in the included files
    
    charon {
            install_routes = 0
            load_modular = yes
            plugins {
                    include strongswan.d/charon/*.conf
            }
    }
    
    include strongswan.d/*.conf
    EOF
    

    STOP the firewall by issuing "systemctl firewalld stop"
    DISABLE the firewall by issuing "systemctl disable firewalld"

    I use the following script to configure my firewall:

    touch /etc/init.d/vpsfw
    cat <<EOF | tee /etc/init.d/vpsfw
    #!/bin/bash
    ### BEGIN INIT INFO
    
    # Provides: vpsfw
    # Required-Start: mountkernfs \$local_fs
    # Required-Stop: \$local_fs
    # Default-Start: 2 3 4 5
    # Default-Stop: 0 1 6
    # Short-Description: Set up iptables rules and read a list of blocked ips
    ### END INIT INFO ###
    
    SS5=/usr/sbin/ss5
    IPTABLES=/sbin/iptables
    SPAMLIST="blockedip"
    SPAMDROPMSG="BLOCKED IP DROP"
    SYSCTL="/sbin/sysctl"
    BLOCKEDIPS="/opt/vdot/blocked.ips.txt"
    
    # LOCAL INTERFACES
    INIF=ens192
    FWDIF=vti0
    REMOTE_HOST_NET=172.16.0.0
    
    # HOMENET1 for SSH ENABLE
    HOMENET1=77.77.77.0/24
    
    # HOMENET2 for SSH ENABLE
    HOMENET2=172.16.0.0/29
    
    # LOCAL GATEWAY IP (ON THIS HOST)
    CURRENTGWIP=
    
    test -x \$IPTABLES || exit 5
    
    case "\$1" in
    start)
    echo -n " Loading packet filters"
    
    #SETUP
    
    #load kernel modules first
    modprobe ip_tables
    
    
    [ -f "\$BLOCKEDIPS" ] && BADIPS=\$(egrep -v -E "^#|^\$" "\${BLOCKEDIPS}")
    
    #Flush old rules
    \$IPTABLES -t nat -F
    \$IPTABLES -t mangle -F
    \$IPTABLES --flush
    \$IPTABLES --delete-chain
    
    #set default-deny policies for all three chains
    \$IPTABLES -P INPUT DROP
    \$IPTABLES -P FORWARD DROP
    \$IPTABLES -P OUTPUT DROP
    
    if [ -f "\${BLOCKEDIPS}" ];
    then
    # create a new iptables list
    \$IPTABLES -N \$SPAMLIST
    
    for ipblock in \$BADIPS
    do
       \$IPTABLES -A \$SPAMLIST -s \$ipblock -j LOG --log-prefix "\$SPAMDROPMSG "
       \$IPTABLES -A \$SPAMLIST -s \$ipblock -j DROP
    done
    
    \$IPTABLES -I INPUT -j \$SPAMLIST
    \$IPTABLES -I OUTPUT -j \$SPAMLIST
    \$IPTABLES -I FORWARD -j \$SPAMLIST
    fi
    
    # Stop certain attacks
    echo "Setting sysctl IPv4 settings..."
    \$SYSCTL net.ipv4.ip_forward=1
    \$SYSCTL net.ipv4.conf.all.send_redirects=0
    \$SYSCTL net.ipv4.conf.default.send_redirects=0
    \$SYSCTL net.ipv4.conf.all.accept_source_route=0
    \$SYSCTL net.ipv4.conf.all.accept_redirects=0
    \$SYSCTL net.ipv4.conf.all.secure_redirects=0
    \$SYSCTL net.ipv4.conf.all.log_martians=1
    \$SYSCTL net.ipv4.conf.default.accept_source_route=0
    \$SYSCTL net.ipv4.conf.default.accept_redirects=0
    \$SYSCTL net.ipv4.conf.default.secure_redirects=0
    \$SYSCTL net.ipv4.icmp_echo_ignore_broadcasts=1
    \$SYSCTL net.ipv4.tcp_syncookies=1
    \$SYSCTL net.ipv4.conf.all.rp_filter=0
    \$SYSCTL net.ipv4.conf.default.rp_filter=0
    \$SYSCTL kernel.randomize_va_space=1
    
    
    #for loopback interfaces
    \$IPTABLES -A INPUT -i lo -j ACCEPT
    \$IPTABLES -A OUTPUT -o lo -j ACCEPT
    
    #anti-IP-spoofing here
    #if the host is in one of these ranges either modify or comment the appropriate range
    \$IPTABLES -A INPUT -s 255.0.0.0/8 -j LOG --log-prefix "Spoofed source IP!"
    \$IPTABLES -A INPUT -s 255.0.0.0/8 -j DROP
    \$IPTABLES -A INPUT -s 0.0.0.0/8 -j LOG --log-prefix "Spoofed source IP!"
    \$IPTABLES -A INPUT -s 0.0.0.0/8 -j DROP
    \$IPTABLES -A INPUT -s 127.0.0.0/8 -j LOG --log-prefix "Spoofed source IP!"
    \$IPTABLES -A INPUT -s 127.0.0.0/8 -j DROP
    
    #inbound policies
    #accept inbound packets that are previously ok'ed
    \$IPTABLES -A INPUT -j ACCEPT -m state --state RELATED,ESTABLISHED
    
    #accept SSH
    \$IPTABLES -A INPUT -p tcp -i \$INIF -s \$HOMENET1 --dport 22 -m state --state NEW -j ACCEPT
    \$IPTABLES -A INPUT -p tcp -i \$INIF -s \$HOMENET2 --dport 22 -m state --state NEW -j ACCEPT
    
    #handle ICMP
    \$IPTABLES -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
    \$IPTABLES -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
    \$IPTABLES -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
    \$IPTABLES -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
    
    #handle NTP
    \$IPTABLES -A INPUT -p udp --sport 123 -j ACCEPT
    \$IPTABLES -A INPUT -m state --state NEW -m udp -p udp --dport 123 -j ACCEPT
    
    #VPN INTERFACE PORTS
    \$IPTABLES -A INPUT -p udp --dport 4500 -s \$HOMENET1 -j ACCEPT
    \$IPTABLES -A INPUT -p udp --dport 500 -s \$HOMENET1 -j ACCEPT
    
    #logging all other inbound traffic
    \$IPTABLES -A INPUT -j LOG --log-prefix "Dropped by default (INPUT):"
    
    #outbound policies
    #logging all other outbound traffic
    \$IPTABLES -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    \$IPTABLES -A OUTPUT -o lo -j ACCEPT
    \$IPTABLES -A OUTPUT -m limit --limit 6/hour -j LOG --log-level 4 --log-prefix 'OutAllow6/h '
    \$IPTABLES -A OUTPUT -j ACCEPT
    
    #forward policies
    #####FORWARDING HAS BEEN DISABLED IN THIS SCRIPT####
    \$IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
    \$IPTABLES -A FORWARD -i \$FWDIF -o \$INIF -j ACCEPT
    
    #mangle policies
    \$IPTABLES -t mangle -A FORWARD -i \$INIF -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
    \$IPTABLES -t mangle -A FORWARD -o \$INIF -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
    \$IPTABLES -t mangle -A FORWARD -i \$INIF -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1386
    \$IPTABLES -t mangle -A FORWARD -o \$INIF -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1386
    
    #prerouting policies
    #PORT FORWARDING TO HOST (DNAT) - EXAMPLE
    \$IPTABLES -t nat -A PREROUTING -i \$INIF -p udp --dport 51413 -j DNAT --to 172.16.200.140:51413
    \$IPTABLES -t nat -A PREROUTING -i \$INIF -p tcp --dport 51413 -j DNAT --to 172.16.200.140:51413
    
    #postrouting policies
    \$IPTABLES -t nat -A POSTROUTING -o \$INIF -j MASQUERADE
    
    #Log any packets which don't fit the rules above...
    #(optional but useful)
    \$IPTABLES -A INPUT -m limit --limit 3/min -j LOG --log-prefix "\$IPTABLES_INPUT_denied: " --log-level 4
    \$IPTABLES -A FORWARD -m limit --limit 3/min -j LOG --log-prefix "\$IPTABLES_FORWARD_denied: " --log-level 4
    \$IPTABLES -A OUTPUT -m limit --limit 3/min -j LOG --log-prefix "\$IPTABLES_OUTPUT_denied: " --log-level 4
    
    ;;
    
    wide_open)
    echo -n "Danger allowing all traffic everywhere"
    \$IPTABLES --flush
    \$IPTABLES -P INPUT ACCEPT
    \$IPTABLES -P FORWARD ACCEPT
    \$IPTABLES -P OUTPUT ACCEPT
    ;;
    
    stop)
    echo -n "stopping ....."
    \$IPTABLES --flush
    ;;
    
    status)
    echo "querying iptables status ..."
    \$IPTABLES --line-number -v --list
    ;;
    
    *)
    echo "Usage: \$0 (start|stop|wide_open|status)"
    exit 0
    ;;
    esac
    EOF
    

    ENSURE YOUR HOMENET ADDRESSES ARE SET PROPERLY OR YOU'LL LOCK YOURSELF OUT OF YOUR VPS!

    (Allow execution)

    chmod +x /etc/init.d/vpsfw
    

    (Execute it)

    /etc/init.d/vpsfw start
    

    (Start strongswan and enable it on boot)

    systemctl enable strongswan
    systemctl start strongswan
    

    (Configure SYSCTL now and permenantly)

    sysctl -w net.ipv4.ip_forward=1
    sysctl -w net.ipv4.conf.ens192.disable_xfrm=1
    sysctl -w net.ipv4.conf.ens192.disable_policy=1
    sysctl -w net.ipv4.conf.vti0.disable_policy=1
    sysctl -w net.ipv4.conf.vti0.rp_filter=0
    
    echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.ens192.disable_xfrm=1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.ens192.disable_policy=1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.vti0.disable_policy=1" >> /etc/sysctl.conf
    echo "net.ipv4.conf.vti0.rp_filter=0" >> /etc/sysctl.conf
    

    (Configure IPv4/IPv6 VTI interface)

    ip link del vti0
    ip link add vti0 type vti local 88.88.88.88 remote 77.77.77.77 ikey 12 okey 12
    ip addr add 192.168.254.1/30 remote 192.168.254.2/30 dev vti0
    ip route add 172.16.0.0/12 via 192.168.254.2 dev vti0
    ip link set dev vti0 mtu 1400
    ip link set up dev vti0
    

    Go back to the pfsense box. Look at the Status of IPSEC. You should see a connection established message by now.
    Go to Interfaces > Assignments.
    Under "Available network ports", select the new ipsec interface (ipsec1xxx) and click "add"
    Go to Interfaces and select the new ipsec interface you just added and make sure its ENABLED.

    Now, go to Firewall > Rules > "IPSEC" and configure the following rule:

    • Permit source network <LAN/MANAGEMENT/ETC. net> from any source port to any destination on any port.

    Now, go to System > Routing. You should see your VTI interface there somewhere (ends in VTIV4). Click edit.
    Make sure that the "Monitor IP" field has the remote ipsec host VTI point-to-point IP input here (192.168.254.1 in this case)
    Save and exit.

    Now, check on your tunnel on both ends and see if you can pass traffic. TCPDump is very useful here. Also note that your VTI should display on the main dashboard the reachability of VTI addresses.


Log in to reply