.. title: SSH tunnel with single hop, using systemd-networkd and autossh
.. date: 2015-02-01 20:00 UTC+02:00
.. modified: 2015-02-01 20:00
.. tags: arch linux, autossh, ssh, tunnel, systemd, systemd.network, postfix, TUN
.. category: admin
.. slug: ssh-tunnel-with-single-hop-using-systemd-networkd-and-autossh
.. summary: HOWTO on setting up a SSH tunnel with the help of a systemd-networkd between two machines, with no direct access to each other and modifying Postfix to use that tunnel.
.. authors: David Runge

| Recently I had the pleasure of setting up a |abbr_ssh| tunnel between two virtual machines that share no route and are located in two different subnets.
| They can however reach each other via SSH, hopping their host.
| Let's assume the following setup:

* **client1** (Arch Linux) has *10.0.5.2/24*
* **client2** (Arch Linux) has *10.0.6.2/24*
* **host** (Debian) is *10.0.5.1/24* to **client1** and *10.0.6.1/24* to **client2**

| As I needed the two clients to be able to send mail to each other and reach each others' services, I did some digging and opted for a SSH connection using |abbr_tun| devices (aka. "poor man's |abbr_vpn|").
| The following is needed to set this up:

* root access on both virtual machines (**client1** & **client2**)
* a user account on the **host** system
* SSH (|openssh| assumed) installed on all three machines

.. TEASER_END

Connect the clients
___________________

Change sshd_config
------------------

| The following two settings have to be made in each clients */etc/ssh/sshd_config* (to allow root login and the creation of TUN devices):  

  .. code:: apache

    PermitRootLogin yes
    PermitTunnel yes

| I hope it is needless to say, that permitting root access via SSH has its caveats. You should make sure to set a very secure password, or only allow SSH keys for login.
|

Generate and exchange keys
--------------------------

| Generate SSH keys on **client1** (you can of course use other key types, if your OpenSSH installation allows and supports it):

  .. code:: bash

    ssh-keygen -t rsa -b 4096 -C "$(whoami)@$(hostname)-$(date -I)"

| Here you can choose between setting a password for the key (to unlock the key with *ssh-add* yourself) or not setting one (to be able to use the key on system boot with an automated service).  
| Add them to your user at **host** like this:

  .. code:: bash

    ssh-copy-id -i .ssh/id_rsa user@host

| Also add it to */root/.ssh/authorized_keys* on **client2**.
|

Use ProxyCommand to connect
---------------------------

| To make a first connection between the clients, one can use the following settings in */root/.ssh/config* of **client1** to hop **host** and connect to **client2**:  

  .. code:: apache

    Host client2
      ProxyCommand ssh user@10.0.5.1 -W 10.0.6.2:%p
      ForwardAgent yes
      User root
      ServerAliveInterval 120
      Compression yes
      ControlMaster auto
      ControlPath ~/.ssh/socket-%r@%h:%p

| The *ForwardAgent yes* setting here is especially interesting, as it forwards the SSH key of **client1** to **client2**.
| On **client1** a simple

  .. code:: bash

    ssh client2 -v

| should now directly connect to **client2** by hopping **host**.
|

Tunneling
_________

Start the tunnel
----------------

| Now to the fun part: Creating the tunnel.
| OpenSSH supports a feature similar to VPN, that creates a TUN device on both ends of the connection. As the "direct" (hopping **host**) connection between **client1** and **client2** has been setup already, let's try the tunnel:  

  .. code:: bash

    ssh -w5:5 client2 -v

| The *-w* switch will create a TUN device (*tun5* to be exact) on each client.  
| Now, to start the tunnel without executing a remote command (*-N*), compression of the data (*-C*) and disabling pseudo-tty allocation (*-T*), one can use the following:  

  .. code:: bash

    ssh -NCTv -w5:5 client2

Setting up the TUN devices
--------------------------

| A short

  .. code:: bash

    ip a s

| on **client1** and **client2** shows, that the *tun5* devices have been created on both clients. However they don't feature a link yet.
| This can be achieved by setting up a |systemd_network| with the help of |systemd-networkd|. By placing a *.network* file in */etc/systemd/network/*, the TUN device will be configured as soon as it shows up.
| Here I chose the *10.0.10.0/24* subnet, but you could use any other private subnet (that's still available in your setup).
| On **client1** (*/etc/systemd/network/client1-tun.network*):

  .. code:: ini

    [Match]
    Name=tun5
    Host=client1
    
    [Network]
    Address=10.0.10.1/24
    
    [Address]
    Address=10.0.10.1/24
    Peer=10.0.10.2/24

| On **client2** (*/etc/systemd/network/client2-tun.network*):

  .. code:: ini

    [Match]
    Name=tun5
    Host=client2
    
    [Network]
    Address=10.0.10.2/24
    
    [Address]
    Address=10.0.10.2/24
    Peer=10.0.10.1/24

| After adding the files a restart of the **systemd-networkd** service on both machines is necessary.

  .. code:: bash

    systemctl restart systemd-networkd

| Now starting the tunnel again should give a fully working point-to-point |abbr_tcp| connection between the two (virtual) machines using the TUN devices.
| If you need a more complex setup (i.e. to access the other clients' subnet), you will have to apply some routes (either using |netfilter| or |systemd-networkd|), depending on your individual setup.
|

Hosts
_____

| To make both hosts know about each other by hostname (and domain, if any), too, those can be added to the clients' */etc/hosts* files.
| On **client1** (*/etc/hosts*):

  .. code:: bash

    10.0.10.2 client2.org client2

| On **client2** (*/etc/hosts*):

  .. code:: bash

    10.0.10.1 client1.org client1

Postfix
_______

| If using |postfix| as |abbr_mta|, the service has to be configured to use */etc/hosts* before resolving to your networks DNS resolving.
| On **client1** and **client2** (*/etc/postfix/main.cf*):

  .. code:: ini

    lmtp_host_lookup = native
    smtp_host_lookup = native
    ignore_mx_lookup_error = yes

Autossh and system boot
_______________________

| Wrapping it all up, it's usually intended to have a tunnel service be started on system boot. SSH tunnels are supposedly known for their poor connectivity. One way to get around this issue is to manage them with |autossh| .
| A simple |systemd_service| file can then be used to manage this behavior.
| On **client1** (*/etc/systemd/system/tunnel@.service*):

  .. code:: ini

    [Unit]
    Description=AutoSSH tunnel to a host
    After=network.target
    
    [Service]
    Environment="AUTOSSH_GATETIME=0"
    ExecStart=/usr/bin/autossh -M 0 -NCTv -o ServerAliveInterval=45 -o ServerAliveCountMax=2 -o TCPKeepAlive=yes -w 5:5 %I
    
    [Install]
    WantedBy=multi-user.target

| Enable the service with

  .. code:: bash

    systemctl enable tunnel@client2

| Start the service with

  .. code:: bash

    systemctl start tunnel@client2


.. |openssh| raw:: html

  <a href="http://openssh.com" target="_blank">OpenSSH</a>

.. |systemd_network| raw:: html

  <a href="http://www.freedesktop.org/software/systemd/man/systemd.network.html" target="_blank">systemd network</a>

.. |systemd-networkd| raw:: html

  <a href="http://www.freedesktop.org/software/systemd/man/systemd-networkd.service.html" target="_blank">systemd-networkd</a>

.. |netfilter| raw:: html

  <a href="http://www.netfilter.org/" target="_blank">netfilter</a>

.. |systemd_service| raw:: html

  <a href="http://www.freedesktop.org/software/systemd/man/systemd.service.html" target="_blank">systemd service</a>

.. |autossh| raw:: html

  <a href="http://www.harding.motd.ca/autossh/" target="_blank">autossh</a>

.. |postfix| raw:: html

  <a href="http://www.postfix.org/" target="_blank">postfix</a>

.. |abbr_ssh| raw:: html

  <abbr title="Secure Shell" >SSH</abbr>

.. |abbr_tun| raw:: html

   <abbr title="network TUNnel (virtual-network kernel devices)" >TUN</abbr>

.. |abbr_vpn| raw:: html

  <abbr title="Virtual Private Network" >VPN</abbr>

.. |abbr_tcp| raw:: html

  <abbr title="Transmission Control Protocol" >TCP</abbr>

.. |abbr_mta| raw:: html

  <abbr title="Message Transfer Agent" >MTA</abbr>
