Tinc VPN (and Ansible)

VPNs are a pain. The more I work with technology the more I see how some things like VPNs are just poor constructs and are, in a way, workarounds. Maybe that’s all IT is…workarounds. I don’t know. This is philosophical. At any rate, VPNs are difficult to setup, secure, and use, so sometimes it’s fun to play with technologies that try to make things easier, and I think tinc fits into that category. Would I use it in production? Probably not, but it’s still good to take a look at other implementations and see what can be done.

Tinc

I think the most interesting thing about tinc is that it sets up a mesh.

Regardless of how you set up the tinc daemons to connect to each other, VPN traffic is always (if possible) sent directly to the destination, without going through intermediate hops.

Also, it’s easy to extend.

When you want to add nodes to your VPN, all you have to do is add an extra configuration file, there is no need to start new daemons or create and configure new devices or network interfaces

So those are a couple of good reasons to try out tinc.

Tinc with Ansible

I was playing around with tinc a few days ago and came up with an Ansible playbook to setup a tinc VPN between several Ubuntu Trusty servers.

Right now I have four virtual machines running in the same OpenStack infrastructure, which is a FlatDHCP system, so the tenants don’t share a private network…unless we set one up for them with Tinc. (This is not something you’d normally want for production systems…I don’t think, I’m just messin’ around.)

curtis@parker:~/tinc-testing$ cat hosts
trusty1 ansible_ssh_host=x.y.z.21
trusty2 ansible_ssh_host=x.y.z.22
trusty3 ansible_ssh_host=x.y.z.23
trusty4 ansible_ssh_host=x.y.z.24

I can ping all those with Ansible.

curtis@parker:~/tinc-testing$ ansible -m ping all
trusty3 | success >> {
    "changed": false,
    "ping": "pong"
}

trusty4 | success >> {
    "changed": false,
    "ping": "pong"
}

trusty1 | success >> {
    "changed": false,
    "ping": "pong"
}

trusty2 | success >> {
    "changed": false,
    "ping": "pong"
}

And I can run the Ansible playbook that sets up one VPN, called “vpnone.”

curtis@parker:~/tinc-testing$ ansible-playbook site.yml
SNIP!
PLAY RECAP ********************************************************************
trusty1                    : ok=17   changed=1    unreachable=0    failed=0
trusty2                    : ok=17   changed=1    unreachable=0    failed=0
trusty3                    : ok=17   changed=1    unreachable=0    failed=0
trusty4                    : ok=17   changed=1    unreachable=0    failed=0

Now Tinc has setup a tun0 on each node.

curtis@parker:~/tinc-testing$ ansible -a "ip ad sh tun0" all
trusty3 | success | rc=0 >>
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.0.0.23/24 scope global tun0
       valid_lft forever preferred_lft forever

trusty2 | success | rc=0 >>
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.0.0.22/24 scope global tun0
       valid_lft forever preferred_lft forever

trusty1 | success | rc=0 >>
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.0.0.21/24 scope global tun0
       valid_lft forever preferred_lft forever

trusty4 | success | rc=0 >>
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none
    inet 10.0.0.24/24 scope global tun0
       valid_lft forever preferred_lft forever

And I can ping nodes from one another. In this example I just ping 10.0.0.24 from all four nodes.

curtis@parker:~/tinc-testing$ ansible -m shell -a "ping -c 1 -w 1 10.0.0.24" all
trusty2 | success | rc=0 >>
PING 10.0.0.24 (10.0.0.24) 56(84) bytes of data.
64 bytes from 10.0.0.24: icmp_seq=1 ttl=64 time=0.685 ms

--- 10.0.0.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.685/0.685/0.685/0.000 ms

trusty4 | success | rc=0 >>
PING 10.0.0.24 (10.0.0.24) 56(84) bytes of data.
64 bytes from 10.0.0.24: icmp_seq=1 ttl=64 time=0.029 ms

--- 10.0.0.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.029/0.029/0.029/0.000 ms

trusty3 | success | rc=0 >>
PING 10.0.0.24 (10.0.0.24) 56(84) bytes of data.
64 bytes from 10.0.0.24: icmp_seq=1 ttl=64 time=0.501 ms

--- 10.0.0.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.501/0.501/0.501/0.000 ms

trusty1 | success | rc=0 >>
PING 10.0.0.24 (10.0.0.24) 56(84) bytes of data.
64 bytes from 10.0.0.24: icmp_seq=1 ttl=64 time=0.702 ms

--- 10.0.0.24 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.702/0.702/0.702/0.000 ms


By default the playbook sets up “vpnone” but the name is configurable. But it only does one VPN right now, though tinc can support many.

ubuntu@trusty1:~$ ls /etc/tinc/vpnone/
hosts  rsa_key.priv  tinc.conf  tinc-down  tinc-up
ubuntu@trusty1:~$ ls /etc/tinc/vpnone/hosts
trusty1  trusty2  trusty3  trusty4
ubuntu@trusty1:~$ ls /etc/tinc/
nets.boot  vpnone

Firewall rules

I should note that tcp/udp on port 655 needs to be open between the nodes.

So there you go

If you want a VPN setup between several hosts, then tinc is a good way to do that, and if you want to use my playbook to automatically setup a single vpn between several hosts, then that’d be great. The playbook is not perfect, so if you see something you’d like changed, just let me know or send a pull request. :)

Happy tincing!