DEBOPS.TINC(5)

NAME
debops.tinc - Configure tinc mesh VPN network

SYNOPSIS

debops run service/tinc [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

tinc <https://tinc-vpn.org/> is a Virtual Private Network daemon, it can be used to create encrypted and tunneled connections to other hosts, forming a separate network, either a centralized or a mesh one.

debops.tinc Ansible role allows you to install and configure a mesh VPN using tinc, including automatic public key exchange between all hosts in the Ansible inventory, connection to external hosts and secure configuration.

GETTING STARTED

The debops.tinc role by itself only defines the connections between hosts, posing as Ethernet tunnels. To make a proper network, you need a defined subnet and a way to assign IP addresses to hosts in the network. You can use the debops.ifupdown role to define an internal subnet for the hosts in the VPN, and debops.dnsmasq to provide DHCP and DNS services inside the network.

  Example inventory

Hosts added to the [debops_service_tinc] inventory group will have the tinc daemon installed and configured.

Here is a example inventory that defines a Tinc mesh0 VPN with one of the hosts acting as the DHCP/DNS server and optionally a gateway:

[debops_service_ifupdown]
gateway
[debops_service_dnsmasq]
gateway
[debops_service_tinc]
gateway
hostname1
hostname2

If you don't want the hosts to be included by default in any Tinc mesh networks, you can put them in the [debops_service_tinc_aux] inventory group instead.

The gateway needs some additional configuration which should be placed in the Ansible inventory of the host:

tinc__host_networks:
  'mesh0':
    bridge: 'br2'
ifupdown__host_interfaces:
  'br2':
    type: 'bridge'
    inet: 'static'
    inet6: 'static'
    nat: True
    addresses:
      - '2001:db8::23/64'
      - '192.0.2.23/24'

By default Tinc configures host configuration to contain the primary FQDN address of a given host, so that when its IP address changes, Tinc will query the DNS to get the current IP address. In addition, all publicly routable IP addresses will be added to the host configuration file as well.

However, the FQDN will only be added, if a given host has a publicly routable IP address. This means that hosts without public IPs won't have their addresses mentioned in their host configuration file. This allows these hosts to still connect to a public gateway with access to the Tinc network.

If you want to test the Tinc VPN only on a private network, or allow VPN connections between hosts, you can tell the debops.tinc role to add the private IP addresses of the hosts to their host configuration files by adding in the Ansible inventory:

tinc__host_addresses: '{{ tinc__host_addresses_fqdn +
                          tinc__host_addresses_ip_public +
                          tinc__host_addresses_ip_private }}'

  Example playbook

The debops.tinc role uses other roles present in the DebOps project to configure certain aspects of a host, like firewall, installation of the tinc package from Debian Backports repository on certain OS releases, and so on. To do that, a special Ansible playbook is used to access other roles. A "mini role" called debops.tinc/env is used to pass variable data generated from templates to other roles.

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.tinc role:

---
- name: Configure Tinc VPN
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_tinc', 'debops_service_tinc_aux' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  pre_tasks:
    - name: Prepare tinc environment
      ansible.builtin.import_role:
        name: 'tinc'
        tasks_from: 'main_env'
      tags: [ 'role::tinc', 'role::tinc:secret', 'role::secret', 'role::ferm' ]
  roles:
    - role: secret
      tags: [ 'role::secret', 'role::tinc:secret' ]
      secret__directories: '{{ tinc__env_secret__directories }}'
    - role: etc_services
      tags: [ 'role::etc_services', 'skip::etc_services' ]
      etc_services__dependent_list: '{{ tinc__env_etc_services__dependent_list }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules: '{{ tinc__env_ferm__dependent_rules }}'
    - role: tinc
      tags: [ 'role::tinc', 'skip::tinc' ]

If you are using this role without DebOps, here's an example Ansible playbook that uses debops.tinc together with the debops.persistent_paths:

---
- name: Configure Tinc VPN and ensure persistence
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_tinc_persistent_paths', 'debops_service_tinc_aux' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  pre_tasks:
    - name: Prepare tinc environment
      ansible.builtin.import_role:
        name: 'tinc'
        tasks_from: 'main_env'
      tags: [ 'role::tinc', 'role::tinc:secret', 'role::secret', 'role::ferm' ]
  roles:
    - role: secret
      tags: [ 'role::secret', 'role::tinc:secret' ]
      secret__directories: '{{ tinc__env_secret__directories }}'
    - role: etc_services
      tags: [ 'role::etc_services', 'skip::etc_services' ]
      etc_services__dependent_list: '{{ tinc__env_etc_services__dependent_list }}'
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules: '{{ tinc__env_ferm__dependent_rules }}'
    - role: tinc
      tags: [ 'role::tinc', 'skip::tinc' ]
    - role: persistent_paths
      tags: [ 'role::persistent_paths', 'skip::persistent_paths' ]
      persistent_paths__dependent_paths: '{{ tinc__persistent_paths__dependent_paths }}'

  Static vs DHCP connection type

By default, the debops.tinc role configures a node to start its VPN interface in a "DHCP" mode, without connecting to any other bridge interface, and ask the mesh network for an IP address.

To have properly configured networking in the mesh, you need to configure at least one VPN host to work in a "static" mode and preferably connect it to a bridge which connects to a network with DHCP/DNS server. If the bridge parameter is specified without the link_type, role will assume that the host should be configured as static and enable this automatically.

Example network configuration:

tinc__host_networks:
  'mesh0':
    link_type: 'static'
    bridge: 'br2'

In this mode, hosts will be configured to start their VPN interface with a dummy 0.0.0.0 IP address and connect it to a specified bridge. This bridge can be created by the debops.ifupdown.

In "static" mode, the VPN interface will act as another layer 2 connection on the bridge and DHCP requests from the VPN will be passed along to a suitable server. You can configure a DHCP/DNS server using debops.dnsmasq.

  Host configuration exchange

The debops.tinc role uses directories created in the secret/tinc/ directory on the Ansible Controller to exchange host configuration files which contain the RSA public keys between hosts in a given VPN. Each network has its own directory tree:

secret/tinc/
└── networks/
    └── mesh0/
        ├── by-group/
        │   ├── all/
        │   │   └── hosts/
        │   └── debops_service_tinc_mesh0/
        │       └── hosts/
        ├── by-host/
        │   ├── gateway/
        │   │   └── hosts/
        │   ├── hostname1/
        │   │   └── hosts/
        │   └── hostname/
        │       └── hosts/
        └── by-network/
            └── mesh0/
                └── hosts/
                    ├── gateway
                    ├── hostname1
                    └── hostname2

By default all host configuration files in a given mesh network will be stored in:

secret/tinc/networks/<mesh>/by-network/<mesh>/hosts/

The by-group/all/hosts/ directory can be used to distribute public keys to all hosts in a given mesh network. You can also distribute the keys only to hosts in a particular Ansible group, or even to a specific host.

Only the hosts in the current ansible-playbook run will get the keys present in the hosts/ directories. This means that when you add a new host to the mesh, you will need to run the role against all of the hosts of the mesh, otherwise the new host won't be accepted by the mesh due to unknown public keys.

  Support for systemd tinc@.service instances

On a legacy systems without systemd, you can manage Tinc VPN networks using the /etc/init.d/tinc init script.

If systemd is detected as the current init process, debops.tinc will configure a set of systemd unit files:

tinc.service
    This is the main unit that manages all of the Tinc VPN networks and propagates start/stop/restart events.

tinc@.service
    This unit can be used to manage individual Tinc networks. The unit argument is the name of the VPN.

    With systemd, you can manage each Tinc network separately by issuing commands:

    systemctl status tinc@mesh0
    systemctl start tinc@mesh0
    systemctl stop tinc@mesh0

  debops.persistent_paths support

In case the host in question happens to be a TemplateBasedVM on Qubes OS <https://en.wikipedia.org/wiki/Qubes_OS> or another system where persistence is not the default, it should be absent in debops_service_tinc and instead be added to the debops_service_tinc_persistent_paths Ansible inventory group so that the changes can be made persistent:

[debops_service_tinc_persistent_paths]
hostname

Note that the tinc__user (tinc-vpn by default) created by the role is not made persistent because making /etc/passwd and related files persistent might interfere with template changes.

You will need to ensure that the user exists by one of the following ways:

•   Create the user in the template using useradd --system tinc-vpn --comment 'tinc VPN service' --home-dir '/etc/tinc' --shell '/bin/false'

•   Running the above command on start in the TemplateBasedVM

•   Run the role against your template with the role configured in such a way that it only creates the user. Note that this is normally discouraged on Qubes OS <https://www.qubes-os.org/doc/software-update-vm/#notes-on-trusting-your-templatevms>.

    Besides that, the tinc__base_packages are expected to be present (typically installed in the TemplateVM).

    Also note that you will need to set core__unsafe_writes to True when you attempt to update the configuration on a system that uses bind mounts for persistence. You can set core__unsafe_writes directly in your inventory without the need to run the debops.core role for this special case. Refer to Templating or updating persistent files <https://docs.debops.org/en/latest/ansible/roles/persistent_paths/guides.html#templating-or-updating-persistent-files> for details.

EXAMPLES

  Minimal

Minimal configuration of a default Tinc mesh0 VPN:

tinc__networks:
  'mesh0':
    port: '655'

  Per-group VPN

Create a separate Tinc network with a specific group of hosts included in the [tinc_vpn] Ansible inventory group:

# inventory/group_vars/tinc_vpn/tinc.yml
tinc__group_networks:
  'vpn0':
    port: '656'
    inventory_groups: 'tinc_vpn'
    connect_to: '{{ groups.tinc_vpn }}'

  Star network

Create a star network with 192.0.2.0/24 with the master connecting to the slaves:

# master node:
tinc__host_networks:
  'star0':
    address: "192.0.2.254/24"
    connect_to: 'slave01'
    connect_to: 'slave02'
    bridge: [ 'br0' ]
# slave01 node:
tinc__host_networks:
  'star0':
    address: "192.0.2.1/24"
    host_address: "{{ ansible_host }}"
# slave02 node:
tinc__host_networks:
  'star0':
    address: "192.0.2.2/24"
    host_address: "{{ ansible_host }}"

  IPv6 over IPv4 tunnel

Obtain IPv6 connectivity while in an IPv4-only network.

This assumes a [tinc_dualstack] group with IPv4-accessible hosts and a [tinc_ipv4only] group for hosts that need a tunnel.

Hosts in [tinc_dualstack] should have the tap interface bridged via br0 to an interfaces that is receiving router advertisements. See debops.ifupdown for information on how to do that and debops.radvd if you need to set up router advertisement.

Note that if you bridge the tap interface to a wired interface in the [tinc_ipv4only] hosts, you may be providing IPv6 to the whole network.

# inventory/group_vars/tinc_dualstack/tinc.yml
'six_tunnel':
   port: '49180'
   link_type: 'static'
   bridge: 'br0'
# inventory/group_vars/tinc_ipv4only/tinc.yml
'six_tunnel':
   port: '49180'
   link_type: 'dynamic'
   connect_to: '{{ groups.tinc_dualstack }}'

DEFAULT VARIABLE DETAILS

Some of debops.tinc default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  tinc__networks

The tinc__*_networks variables is a collection of YAML dictionaries that define Tinc networks. All dictionaries are recursively combined together in the order they appear in the defaults/main.yml file.

Each entry in the tinc__*_networks dictionaries is a YAML dictionary. The key of a given entry is either a network interface name (for example mesh0 or tap0) or a "label" that holds the preferences for a network denoted by the name parameter. Configuration parameters in labeled sections will be merged with the real network preferences.

You can also use YAML lists of dictionaries, however you cannot combine both dictionaries and lists in the same tinc__*_networks variable. YAML dictionaries specified in a list need to have the name parameter that specifies the interface name, otherwise they will be skipped.

Each Tinc network is described by specific parameters:

port
    Required. TCP and UDP port used by this Tinc VPN.

name
    Optional. Name of the mesh network, used as the name of the directory in /etc/tinc/ as well as the systemd instance argument. If not specified, the YAML dictionary key will be used as the network name.

interface
    Optional. Name of the virtual Ethernet device which will be managed by the Tinc VPN.

    If not specified, the role will generate an interface name from the network name and device type (tun or tap). If the interface name does not start with tun or tap, the device type will be added as a prefix to the generated interface name.

    Using names like tunX or tapX will ensure that DNS configuration received from the nameserver will be ordered correctly by the resolvconf package.

node_reachable
    Optional, boolean. Defaults to True. Whether a node should be reachable for other nodes or if the node should act in "client mode". If the node is in "client mode", the Firewall will not allow other nodes to initiate a connection to the Tinc daemon. Furthermore, the Tinc daemon is configured to only listen on the loopback interface.

allow
    Optional. List of IP addresses or CIDR subnets which will be allowed to connect to the Tinc VPN port through the firewall. If this list is empty, any IP address can connect.

bridge
    Optional. Name of the network bridge to which the virtual Ethernet device will be connected, if the interface is configured in the "static" mode. This should be set on a host that provides the DHCP and DNS services for the mesh.

    If the bridge parameter is specified, and the link_type parameter is not specified, role will automatically enable the static link type.

link_type
    Optional. If empty, debops.tinc defaults to a standalone network interface with dhclient requesting network configuration using DHCP. Possible values are:

static
    Set the Tinc interface in a "static" mode with an IP address, optionally attached to a network bridge. This should usually be done just on one host in the mesh to provide DHCP/DNS services.

hwaddr
    Optional. By default the tinc-up script will create the virtual Ethernet device with a random, but predictable and not changing MAC address. Using item.hwaddr you can specify your own MAC address (in the format accepted by ip link command). Set the MAC address value to '*' to let the system generate a random hardware address.

metric
    Optional. Specify the network metric which will affect the Linux routing table. If not specified, by default the role will tell dhclient to set the 100 metric which should prevent issues with misconfigured default route.

boot
    Optional, boolean. Enable or disable start of the given Tinc VPN at boot time. By default all mesh networks are started at boot.

user
    Optional. Name of the UNIX user account under which the tincd daemon will be running. If not specified, tincd will be run under tinc-vpn account.

mlock
    Optional, boolean. If present and True, tincd will be executed with the --mlock option which will lock the daemon's memory in RAM, preventing the system from moving it to the swap space. When no connection can be established, it can be tried to set this to False. Apparently the "Error while processing METAKEY from" might not be fully resolved in Debian Jessie (problem also occurred with 1.0.28 from jessie-backports). This usually happens when the amount of RAM reserved for locked process memory is too low. See the tinc__ulimit_memlock variable for more details.

chroot
    Optional, boolean. If True, the tincd daemon will be run chrooted to the directory with the VPN configuration files.

state
    Optional, string. Whether the mesh should be present or absent. Defaults to present.

address or addresses
    Optional. List of IP addresses in the host/prefix form which should be configured on the Tinc network interface if it's configured statically.

host_address or host_addresses
    Optional. List of FQDN or IP addresses which should be included in the host configuration. These addresses will tell other Tinc nodes how to connect to a specific host.

    If not specified, the role will use the filtered list of the host's FQDN (if the public IP addresses are available) and public IPv4/IPv6 addresses.

exclude_address or exclude_addresses
    Optional. List of FQDN or IP addresses which should be excluded from the host configuration. This list excludes the IP addresses of the mesh interface as well as the bridge interface, so that Tinc doesn't try to connect to remote hosts over the VPN connection.

mode
    Optional. Specify the Tinc routing mode to use for this network connection (router, switch, hub). If not specified, the switch mode is used by default. See the tinc.conf(5) <https://manpages.debian.org/tinc.conf(5)> for more details.

device_type
    Optional. Specify the network device type used by Tinc. If not specified, tap is used by default. See the tinc.conf(5) <https://manpages.debian.org/tinc.conf(5)> for more details.

cipher
    Optional. The cipher used to encrypt the connections. If not specified, the role will use the AES-256-CBC algorithm.

digest
    Optional. The digest algorithm used to authenticate the connections. If not specified, the role will use the SHA512 algorithm.

compression
    Optional. A level of compression used by Tinc (0-11). By default the compression is disabled (0).

address_family
    Optional. Specify the address family to use for network connections (ipv4, ipv6, any). If not specified, any is used by default.

hostname
    Optional. Set the hostname used by this host. If not specified, the value of tinc__hostname will be used automatically.

inventory_self
    Optional. List of inventory names that the host is known as. This is used to filter out the current host from the list of hosts to connect to. If not specified, th tinc__inventory_self value is used instead.

inventory_groups
    Optional. List of names of the Ansible inventory groups that are used to manage Tinc networks. This list will be used to create directories required by the role in the secret/ directory on Ansible Controller.

connect_to
    Optional. List of hosts which a given Tinc node should connect to, the host names are the names of the files in the :file`hosts/` Tinc directory. If not specified, and the host is not configured as "static", the global tinc__inventory_hosts list is used to select which hosts to connect to.

add_connect_to
    Optional. Additional list of hosts to connect to. This can be used to add additional connections to the mesh network, for example to external hosts. This list will be added to the existing autogenerated list of hosts to connect to.

tinc_options
    Optional. Dictionary variable which specifies options stored in the /etc/tinc/<network>/tinc.conf configuration file. Each key of the dict is the option name, values can be strings or lists of strings, in which case the option will be repeated as many times as there are elements in the list.

    If not specified, Tinc configuration will be autogenerated by the role with sensible defaults. If specified, role will use the autogenerated values, therefore you need to specify all required Tinc configuration.

    To see the list of available options, check the tinc.conf(5) <https://manpages.debian.org/tinc.conf(5)> manual page.

add_tinc_options
    Optional. Dictionary variable which specifies additional options stored in the /etc/tinc/<network>/tinc.conf configuration file. Unlike tinc_options, this parameter will not "mask" the autogenerated values but will add the specified options to the autogenerated ones.

tinc_host_options
    Optional. Dictionary variable which specifies options stored in the /etc/tinc/<network>/hosts/<hostname> configuration file. Each key of the dict is the option name, values can be strings or lists of strings, in which case the option will be repeated as many times as there are elements in the list.

    To see the list of available options, check the tinc.conf(5) <https://manpages.debian.org/tinc.conf(5)> manual page.

dns_nameservers
    Optional. Specify list of DNS nameservers to configure in /etc/resolv.conf. The configuration will be performed by the resolvconf command. This option is used only in the "static" network interface configuration.

dns_search
    Optional. Specify list of DNS search domains to configure in /etc/resolv.conf. The configuration will be performed by the resolvconf command. This option is used only in the "static" network interface configuration.

accept_ra
    Optional. Specify the accept_ra value for the configured tinc interface. If missing or True, defaults to '1'. If set to anything else than '0', '1', '2' or True, the value will not be set and behaviour will depend on the OS settings. See: ip-sysctl.txt in the Linux Kernel Documentation.

post_up
    Optional. If defined, debops.tinc will call this code after setting up the interface when it is going up.

pre_down
    Optional. If defined, debops.tinc will call this code before cleaning up the interface when it is going down.

tinc_up
    Optional. If defined, debops.tinc will not attempt to help you configure the interface when it is going up but will try run this instead. You will have the same variables available. If you use this, make sure you review and understand debops.tinc/templates/etc/tinc/networks/tinc-up.j2.

tinc_down
    Optional. If defined, debops.tinc will not attempt to help you configure the interface when it is going down but will try to run this instead. You will have the same variables available. If you use this, make sure you review and understand debops.tinc/templates/etc/tinc/networks/tinc-down.j2.

AUTHOR
Maciej Delmanowski, Robin Schneider

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others