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), 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
EOFOnce it's done we can start the authentication agent:
systemctl --user start ssh-agentOr even make it load at session opening:
systemctl --user enable ssh-agentAfter 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 ~/.bashrcFinally we add our (default) private key identity to the authentication agent
ssh-addNote: it have 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, 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
EOFWe'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
EOFWe'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@testtunOr make it load a session loading:
systemctl --user enable ssh-tunnel@testtunNote: 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-testLet'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
EOFThen start the tunnel and make it load at session loading:
systemctl --user start ssh-tunnel@machine001-mysql
systemctl --user enable ssh-tunnel@machine001-mysqlWe can now connect to the machine001's MySQL service using:
mysql -h 127.0.0.1 -P 3307