In the following article I will explain how to link two hosts through a secure IPsec VPN connection using strongSwan. For this example we will make use of two hosts running Ubuntu Server 16.04 with Docker installed on them. The only difference between them should be that while the first one will have a standard network configuration, while the second one will be using a new subnet: 172.18.0.0/16.
I will assume you already know how to setup Docker, if not you can read my first article and find out. On Ubuntu 16.04 the procedure to change the subnet for Docker is a bit different than the one on 14.04 where you had to just edit the /etc/defaults/docker file: first we stop Docker on docker2 and use the sed command to add the new subnet in the docker.service configuration file:
root@docker2:~# systemctl stop docker root@docker2:~# sed -i 's/fd:\/\//& --bip 172.18.0.1\/16/' /lib/systemd/system/docker.service root@docker2:~# systemctl daemon-reload
Next we will have to destroy the docker0 bridge on docker2, and start Docker again:
root@docker2:~# ip link set dev docker0 down root@docker2:~# brctl delbr docker0 root@docker2:~# systemctl start docker root@docker2:~# ip addr sh dev docker0 33: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:6b:80:a3:a9 brd ff:ff:ff:ff:ff:ff inet 172.18.0.1/16 scope global docker0 valid_lft forever preferred_lft forever
With this last step our initial setup is complete, we can proceed in downloading the images we need; make sure you execute the following commands on both hosts.
root@docker2:~# docker pull houselabs/strongswan Using default tag: latest latest: Pulling from houselabs/strongswan 420890c9e918: Pull complete 5347f08a28c2: Pull complete 4f4495ff7978: Pull complete Digest: sha256:2acb7786573e5d60e8580f42d3ed42227be5b4258e4e5f2cd64f7aae145d55ab Status: Downloaded newer image for houselabs/strongswan:latest root@docker2:~# docker pull alpine Using default tag: latest latest: Pulling from library/alpine 420890c9e918: Already exists Digest: sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0 Status: Downloaded newer image for alpine:latest
After the two images have been downloaded, we need to setup the ipsec.conf and ipsec.secrets files on both hosts. Create a folder named strongswan in /opt and put the files below in it. Do not forget to replace DOCKER1_PUBLIC_IP and DOCKER2_PUBLIC_IP with their corresponding public IPs.
# ipsec.secrets # this file needs to be the same on docker1 and docker2 : PSK "interdocker"
# /etc/ipsec.conf - strongSwan IPsec configuration file for docker1 config setup conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 keyexchange=ikev2 authby=psk conn docker2 left=%defaultroute leftsubnet=172.17.0.0/16 leftid=docker1 leftfirewall=yes rightid=docker2 right=DOCKER_2_PUBLIC_IP rightsubnet=172.18.0.0/16 auto=add
# /etc/ipsec.conf - strongSwan IPsec configuration file for docker2 config setup conn %default ikelifetime=60m keylife=20m rekeymargin=3m keyingtries=1 keyexchange=ikev2 authby=psk conn docker1 left=%defaultroute leftsubnet=172.18.0.0/16 leftid=docker2 leftfirewall=yes rightid=docker1 right=DOCKER1_PUBLIC_IP rightsubnet=172.17.0.0/16 auto=add
Now that we have the configuration files in place, all we need to do is create and run the container on each host with the following command:
docker run -d --name=strongswan --privileged=true \ --net=host --cap-add=ALL \ -v /lib/modules:/lib/modules \ -v /opt/strongswan/ipsec.conf:/etc/ipsec.conf \ -v /opt/strongswan/ipsec.secrets:/etc/ipsec.secrets \ houselabs/strongswan
The final step is to connect to the container on host docker1 and establish the connection to docker2:
root@docker1:~# docker exec -ti strongswan sh / # ipsec up docker2 initiating IKE_SA docker2 to DOCKER2_PUBLIC_IP generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(HASH_ALG) ] [...] scheduling reauthentication in 3410s maximum IKE_SA lifetime 3590s connection 'docker2' established successfully / # ping -c3 172.18.0.1 PING 172.18.0.1 (172.18.0.1): 56 data bytes 64 bytes from 172.18.0.1: seq=0 ttl=64 time=0.670 ms 64 bytes from 172.18.0.1: seq=1 ttl=64 time=0.541 ms 64 bytes from 172.18.0.1: seq=2 ttl=64 time=0.580 ms --- 172.18.0.1 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max = 0.541/0.597/0.670 ms
At this point we have a working connection between docker1 and docker2, but to be sure everything works we will create a test container on each host and send a ping between them:
root@docker2:~# docker run --rm -ti alpine sh / # ip addr sh dev eth0 34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe12:2/64 scope link valid_lft forever preferred_lft forever
root@docker1:~# docker run --rm -ti alpine sh / # ip addr sh dev eth0 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::42:acff:fe11:2/64 scope link valid_lft forever preferred_lft forever / # ping -c3 172.18.0.2 PING 172.18.0.2 (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=62 time=0.730 ms 64 bytes from 172.18.0.2: seq=1 ttl=62 time=0.645 ms 64 bytes from 172.18.0.2: seq=2 ttl=62 time=0.668 ms --- 172.18.0.2 ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max = 0.645/0.681/0.730 ms