strongSwan VPN Between Two VMs

A Pleasant Discovery

In the course of technological events arising from the labor of one's job, frequently the occasion occurs that one finds oneself either forestalled by uncertainty and variability in setting up an unfamiliar system/tool or confused by the complexity of such system/tool and its unwholesome documentation or both. (The appreciable frequency of this scenario being one of the reasons why filtering on flat task lists for job candidates often proves so foolhardy even as it is defended as the safe choice. Rather, Aristotle I think would advise not to look upon a person's resume, but at what that person will do next.)

Not as frequently as one would desire, the new system/tool purporting to fix/fulfill one's professional requirement proves pleasantly well-designed. VPN technology is naturally complex as it must interact with multiple functional areas within both networking and cryptography. This is all the more reason to enjoy using strongSwan, which handles the native complexity of the problem space with competence and composure and without arbitrary constructs that obscure intent and inflict intellectual discontent. Much like git with the version control of files, strongSwan seems to anticipate the next level of your thought progress by providing the right features in the right timeplace that behave exactly how you would expect them.

Not being yet enough awesome, strongSwan also provides excellent config documentation and an expansive suite of fully-documented test cases. Even the page on analyzing traffic is loaded with excellent information, which rewards close reading.

So many tutorials amongst the Greater Intarwebz I found consisted mostly of manically crunching together a pile of configs until the author's ends were produced, the resulting glut of info dumped into a blog post marking the achievement of the bonus goal of communal erudition making no distinction or cognition of which configs were effective and which were deceptive.

While dutifully working through the RTFM procedure (obligatory XKCD), I was not only pleased at the virtue signaled by DuckDuckGo being hardcoded in as their site-specific search tool, but also found this gem while searching the mailing list:

You're having no success because you're trying ramdom[sic] shit from the Internet. About 99,999% of the strongSwan related information on third party sites is wither well ng or of questinable quality[sic]. Don't get your information from any place but the project's website.

Kind regards

Noel

Great tonic for erstwhile tedium with hipstergrammer cargo cult explanations. Let's hope this post doesn't fall into the bottom \(\frac{99999}{100000}\) (yes, gratuitous MathJax) of strongSwan info.

Setup

I considered writing this post in SaltStack rather than plain command line, since if you're serious about what you're doing, you would not be deploying new systems without some kind of audit/control like salt or one of its inferior peers. I decided to favor the salt-illiterate.

Note

The following assumes you are going to run two Ubuntu 16.04 VMs and will install the strongSwan version packaged for that platform (currently 5.3.5-1ubuntu3.5).

Note

This is a worked reproduction of strongSwan's IKEv1 PSK testcase. I chose it because I need to site-to-site connect to a 3rd party, who uses this config profile. I chose to setup two strongSwan VMs so that I can more easily learn VPN technology generally and strongSwan specifically while controlling all the variables.

Note

Depending on how you configure strongSwan, you and/or strongSwan could be editing iptables rules.

  1. Instantiate two Ubuntu 16.04 VMs on your favorite cloud/hypervisor. Name one moon and the other sun.

  2. Install the package on both VMs.

    (moon/sun) # apt update ; apt install strongswan
    
  3. Setup main config ipsec.conf.

    (moon) # mv /etc/ipsec.conf /etc/ipsec.conf.original
    (moon) # cat > /etc/ipsec.conf
    config setup
    
    conn %default
        ikelifetime=60m
        keylife=20m
        rekeymargin=3m
        keyingtries=1
        authby=secret
        keyexchange=ikev1
    
    conn MyVPN
        left=192.168.0.1  # moon's normal IP addr
        leftsubnet=10.1.0.0/16
        leftid=moon
        leftfirewall=yes    # These two configs trigger the necessary iptables rules
        lefthostaccess=yes  # for moon<-->sun traffic from strongSwan's up/down script
        right=192.168.0.2  # sun's normal IP addr
        rightsubnet=10.2.0.0/16
        rightid=sun
        auto=add
    
    (sun) # mv /etc/ipsec.conf /etc/ipsec.conf.original
    (sun) # cat > /etc/ipsec.conf
    config setup
    
    conn %default
        ikelifetime=60m
        keylife=20m
        rekeymargin=3m
        keyingtries=1
        authby=secret
        keyexchange=ikev1
    
    conn MyVPN
        left=192.168.0.2  # Swap left/right
        leftsubnet=10.2.0.0/16
        leftid=sun
        leftfirewall=yes
        lefthostaccess=yes
        right=192.168.0.1
        rightsubnet=10.1.0.0/16
        rightid=moon
        auto=add
    
  4. Setup secret config ipsec.secrets.

    (moon/sun) # mv /etc/ipsec.secrets /etc/ipsec.secrets.original
    (moon/sun) # cat > /etc/ipsec.secrets
    : PSK TBTBidVXSOQ1bTF8R81gtjQb0T97KfDJ3D4avvM32RZ744d6miaAAFiazJPQgWu8Fw9rduirfy7nIzLxN4loxqrqrLwAJ5JodQbWuEk3koQ0DbeoOLRr1zIVcdJHJWZB
    
  5. Add the VPN IP addresses to moon and sun. Make sure the subnet (/16) matches what you've configured in ipsec.conf.

    (moon) # ip address add 10.1.0.1/16 dev eth0
    
    (sun) # ip address add 10.2.0.1/16 dev eth0
    
  6. Add iptables rules for esp, ah, and isakmp.

    (moon/sun) # iptables -A INPUT -i eth0 -p esp -j ACCEPT
    (moon/sun) # iptables -A INPUT -i eth0 -p ah -j ACCEPT
    (moon/sun) # iptables -A INPUT -i eth0 -p udp -m udp --dport isakmp -j ACCEPT
    (moon/sun) # iptables -A OUTPUT -o eth0 -p udp -m udp --dport isakmp -j ACCEPT
    
  7. Start the connection.

    (moon) # systemctl restart strongswan.service
    (moon) # ipsec statusall  # Ensure MyVPN is listed
    (moon) # ipsec up MyVPN
    
  8. Profit. An exercise for the reader: repeat the following from sun and replace 10.2.0.1 with 10.1.0.1.

    (moon) # ping -c 1 10.2.0.1
    PING 10.2.0.1 (10.2.0.1) 56(84) bytes of data.
    64 bytes from 10.2.0.1: icmp_seq=1 ttl=64 time=180 ms
    
    --- 10.2.0.1 ping statistics ---
    1 packets transmitted, 1 received, 0% packet loss, time 0ms
    rtt min/avg/max/mdev = 180.300/180.300/180.300/0.000 ms
    (moon) # telnet 10.2.0.1 22
    Trying 10.2.0.1...
    Connected to 10.2.0.1.
    Escape character is '^]'.
    SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.4
    ^]
    telnet> Connection closed.
    

Comments

This is obviously not a finished example: the IP addresses, iptables rules, and VPN connection are not saved across reboots; two random VMs connected by a VPN is not very useful for real work; the systems are flagrantly not salted. The purpose is to illustrate the minimum necessary to get from nothing to a successful VPN connection, or at least imbue enough comprehension to make reading strongSwan's test cases productive and provide a working starting place.

Getting Help

strongSwan has what might appear to be an overly punitive RTFM policy, but given the complexity of the problem space a comprehensively detailed report seems like a reasonable context prerequisite for assistance. The one minor annoyance with this situation is that strongSwan does not provide a tool to gather most/all of this info for you, which is why I wrote the following script. It assumes you're running with the deprecated stroke plugin on Ubuntu with Systemd and have charon logging setup.

Note

If your Ubuntu VM has AppArmor enabled, you may need to place the following AppArmor config for charon logging to work:

(moon/sun) # cat > /etc/apparmor.d/local/usr.lib.ipsec.charon
/var/log/charon_debug.log w,
#!/usr/bin/env python3
from subprocess import run, Popen, PIPE
from datetime import datetime
import time
import shlex


def log(stmt):
    print('=== {} ==='.format(stmt))

conn = 'MyVPN'
pre_cmds = [
    'ipsec down {}'.format(conn),
    'rm -f /var/log/charon_debug.log',
    'systemctl restart strongswan.service',
]
for pre_cmd in pre_cmds:
    log(pre_cmd)
    run(pre_cmd.split())

time.sleep(1)
start = datetime.utcnow()
cmds = [
    'ipsec up {}'.format(conn),
    'journalctl --all --priority 7 --since "{}" --unit strongswan.service'.format(start.isoformat(sep=' ')[:19]),
    'cat /var/log/charon_debug.log',
    'cat /etc/ipsec.conf',
    'ipsec statusall',
    'iptables-save',
    'ip6tables-save',
    'ip route show table all',
    'ip address show',
    'ping -c 5 -I 10.1.0.1 10.2.0.1',
]
with open('strongswan_report.md', 'w') as report:
    for cmd in cmds:
        time.sleep(1)
        log(cmd)
        proc = Popen(shlex.split(cmd), stdout=PIPE)
        report.write('\n### `{}`\n'.format(cmd))
        report.write('```\n')
        report.write(proc.stdout.read().decode())
        report.write('```\n')

The resulting strongswan_report.md file should give you all you need to ask for help on the mailing list or in the IRC channel.

Remember to paste responsibly.

Posted: | Source