Skip to content

Instantly share code, notes, and snippets.

@helix84
Forked from olbat/ssh-tunnel-systemd.md
Created January 8, 2019 13:41
Show Gist options
  • Save helix84/2200e2cf0a26a1e8c6fbb5c998d4e182 to your computer and use it in GitHub Desktop.
Save helix84/2200e2cf0a26a1e8c6fbb5c998d4e182 to your computer and use it in GitHub Desktop.
systemd SSH tunnel

Create SSH tunnels using systemd units

Table of contents

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 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), Arch Linux 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 using ssh-add (see ssh-add(1)).

ssh-add

Note: it has 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, Arch Linux wiki page, Fedoramagazine article).

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

# create the [email protected] unit
cat <<'EOF' > ~/.config/systemd/user/[email protected]
[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_HOST=127.0.0.1"
EnvironmentFile=%h/.ssh/tunnels/%i
ExecStart=/usr/bin/ssh -NT ${TARGET} \
  -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 \
  -L ${LOCAL_ADDR}:${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} ${SSH_OPTS}
RestartSec=10
Restart=on-success
RestartForceExitStatus=255

[Install]
WantedBy=multi-user.target
EOF

Create tunnels using systemd services

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

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@testtun

Or list the systemd services of current user:

systemctl --user list-units --type=service

Examples

Create a tunnel to a web server on a machine that's only accessible by SSH

Let's create a configuration file to access to the web server (80 port) of the www.privatecloud.domain.tld machine and make the tunnel accessible to other machines over the network:

cat <<'EOF' > ~/.ssh/tunnels/privatecloud-www
[email protected]
LOCAL_ADDR=0.0.0.0
LOCAL_PORT=8080
REMOTE_PORT=80
EOF

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

systemctl --user start ssh-tunnel@privatecloud-www
systemctl --user enable ssh-tunnel@privatecloud-www

We can now connect to the web server using:

curl http://127.0.0.1:8080/

or (from another machine that's in the same network):

curl http://mymachine.domain.lan:8080/

Create a tunnel to a MySQL service only accessible through an SSH bastion (directly)

Let's create a configuration file to access to the MySQL server (3306 port) of the mysql.privatecloud.domain.tld machine that's only through the access.privatecloud.domain.tld bastion:

cat <<'EOF' > ~/.ssh/tunnels/privatecloud-mysql
[email protected]
LOCAL_PORT=3306
REMOTE_PORT=3306
REMOTE_HOST=mysql.privatecloud.domain.tld
EOF

Then start the tunnel:

systemctl --user start ssh-tunnel@privatecloud-mysql

We can now connect to the mysql.privatecloud's MySQL service using:

mysql -h 127.0.0.1

Create a tunnel to a MySQL service only accessible through an SSH bastion (using SSH)

In this example, we want to create the same tunnel than the one in the previous example but this time the MySQL service is not accessible directly from the bastion (on port 3306).

Fortunately, the mysql.privatecloud machine is accessible from the bastion using SSH so we can create a tunnel using an ssh jump (see ssh(1), wikibooks article):

cat <<'EOF' > ~/.ssh/tunnels/privatecloud-mysql
TARGET=mysql.privatecloud.domain.tld
LOCAL_PORT=3306
REMOTE_PORT=3306
REMOTE_HOST=127.0.0.1
SSH_OPTS="-J access.privatecloud.domain.tld:22"
EOF

Then start the tunnel:

systemctl --user start ssh-tunnel@privatecloud-mysql

We can now connect to the mysql.privatecloud.domain.tld's MySQL service using:

mysql -h 127.0.0.1

Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment