- Overview
- Setup the SSH agent systemd unit
- Setup the SSH-tunnel systemd unit
- Create tunnels using systemd services
- Examples
- Further reading
This systemd service scripts will allow you to create SSH tunnels in background using systemd in user mode and a configuration file.
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 !
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
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
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/
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
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
- systemd's unit and service and manpages
- Arch Linux wiki's Systemd, Systemd/User and SSH_keys pages
- DigitalOcean's introduction to systemd units
You may want to look to on-demand aocket-activation for ssh tunnels on stackexchange, that supports stop-on-idle.