Skip to content

Instantly share code, notes, and snippets.

@polyrand
Forked from olbat/ssh-tunnel-systemd.md
Last active November 26, 2020 18:07
Show Gist options
  • Select an option

  • Save polyrand/6f9987a7cfb09244a8e18f61b814c2d7 to your computer and use it in GitHub Desktop.

Select an option

Save polyrand/6f9987a7cfb09244a8e18f61b814c2d7 to your computer and use it in GitHub Desktop.
systemd SSH tunnel

Create SSH tunnels using systemd services

Overview

This systemd service scripts will allow you to create SSH tunnels in background using systemd in user mode and a configuration file.

Setup the SSH authentication agent systemd unit

To be able to run SSH tunnels in background while using a password protected SSH key to connect to the targeted host, we'll first have to setup an ssh-agent (see ssh-agent(1)).

The agent will be run in background using a user systemd service unit (see systemd.service(5), systemd.unit(5), ArchLinux wiki page).

# (create the systemd config directory if it does not exists)
$ mkdir -p ~/.config/systemd/user/

# create the ssh-agent.service unit
$ cat <<'EOF' > ~/.config/systemd/user/ssh-agent.service
[Unit]
Description=SSH key agent
Documentation=man:ssh-agent(1)
Wants=environment.target
Before=environment.target

[Service]
Type=forking
Environment="SSH_AUTH_SOCK=%t/ssh-agent.socket"
ExecStart=/usr/bin/ssh-agent -a $SSH_AUTH_SOCK
ExecStartPost=/bin/systemctl --user set-environment \
  SSH_AUTH_SOCK=${SSH_AUTH_SOCK}

[Install]
WantedBy=multi-user.target
EOF

Once it's done we can start the authentication agent:

    systemctl --user start ssh-agent

Or even make it load at session opening:

    systemctl --user enable ssh-agent

After that, we make the $SSH_AUTH_SOCK env variable be loaded at our shell initialization (here bash):

    echo 'export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"' >> ~/.bashrc
    source ~/.bashrc

Finally we add our (default) private key identity to the authentication agent

    ssh-add

Note: it have to be done at every boot in order for the tunnels to work !

Setup the SSH-tunnel systemd unit

As for the SSH authentication agent service, we'll run our SSH tunnels (ssh(1)) using a systemd service unit but this unit will be instanciable (see systemd.unit(5), Lennart Poettering's blog, Archlinux wiki, Fedoramagazine article).

# (create the systemd config directory if it does not exists)
$ mkdir -p ~/.config/systemd/user/

# create the ssh-tunnel@.service unit
$ cat <<'EOF' > ~/.config/systemd/user/ssh-tunnel@.service
[Unit]
Description=SSH tunnel to %i
Documentation=man:ssh(1)
Wants=ssh-agent.service
After=network.target ssh-agent.service

[Service]
Type=simple
Environment="LOCAL_ADDR=127.0.0.1"
Environment="REMOTE_ADDR=127.0.0.1"
EnvironmentFile=%h/.ssh/tunnels/%i
ExecStart=/usr/bin/ssh -NT ${TARGET} ${SSH_OPTS} \
  -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 \
  -L ${LOCAL_ADDR}:${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT}
RestartSec=10
Restart=on-success
RestartForceExitStatus=255

[Install]
WantedBy=multi-user.target
EOF

Creating tunnels !

We'll now create systemd service instances to run our tunnels.

Our ssh-tunnel service is going to look for it's configuration file into the ~/.ssh/tunnels/ directory so we start with creating the directory: mkdir -p ~/.ssh/tunnels/

We'll then have to create a configuration file for our tunnel:

$ cat <<'EOF' > ~/.ssh/tunnels/testtun
# This parameters will be used to run:
#     ssh ${TARGET} ${SSH_OPTS} \
#     -L ${LOCAL_ADDR}:${LOCAL_PORT}:${REMOTE_ADDR}:${REMOTE_PORT}

# The target of the SSH command
TARGET=user@hostname

# The local port to listen to
LOCAL_PORT=8080

# The remote port to forward
REMOTE_PORT=80

# The local address to listen to (default: _127.0.0.1_)
#LOCAL_ADDR=0.0.0.0

# The remote host to forward to (default: _127.0.0.1_)
#REMOTE_HOST=domain.tld

# Additional -custom- SSH options
#SSH_OPTS=-v
EOF

We'll then have to create a configuration file for our tunnel, the allowed parameters are the following:

  • TARGET: the target of the SSH command (ie. user@hostname)
  • LOCAL_ADDR: the local address to listen to (default: 127.0.0.1)
  • LOCAL_PORT: the local port to listen to (ie. 8080)
  • REMOTE_HOST: the remote host to forward to (default: 127.0.0.1)
  • REMOTE_PORT: the remote port to forward (ie. 80)
  • SSH_OPTS: additional -custom- SSH options

Finally we can run our systemd service unit that will create the tunnel:

    systemctl --user start ssh-tunnel@testtun

Or make it load a session loading:

    systemctl --user enable ssh-tunnel@testtun

Note: make sure your SSH authentication agent has been loaded properly using ssh-add, the tunnel will not work without it

We can check the tunnel's status using:

    systemctl --user status -l ssh-tunnel@localhost-test

Example: create an SSH tunnel to a MySQL service only accessible through an SSH bastion

Let's create a configuration file to access to the MySQL service (3306 port) of the machine001.domain.tld machine that's only through the access.domain.tld bastion and make the tunnel accessible to other machines over the network:

$ cat <<'EOF' > ~/.ssh/tunnels/machine001-mysql
TARGET=user@access.domain.tld
LOCAL_ADDR=0.0.0.0
LOCAL_PORT=3307
REMOTE_PORT=3306
REMOTE_HOST=machine001.domain.tld
EOF

Then start the tunnel and make it load at session loading:

    systemctl --user start ssh-tunnel@machine001-mysql
    systemctl --user enable ssh-tunnel@machine001-mysql

We can now connect to the machine001's MySQL service using:

    mysql -h 127.0.0.1 -P 3307
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment