Loading…

Connect Containers From Two Docker Hosts Using IPsec

11 May 2016

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[1] 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

 

by George Lucian Tabacar

Want to learn more?