SSH Persistent Tunnel and SSHFS Mount via "systemd" units
In this tutorial we will create a systemd service that will use the tool autossh
to create a persistent tunnel in order to make a service running on a local host available on a remote host. The steps provided below are tested within the circumstances of Ubuntu Server 22.04.
Setup SSH connection and Port forwarding
In the current scenario, so called local host will establish SSH connection to the remote host. So we will forward a remote port to listen at our local port – otherwise said we will bind the remote port to the local port. The service that I want to forward by this technique is Elasticsearch which currently listen on 127.0.0.1:9200
at the local host.
Create dedicated system user(s)
Here we will create a system user named sshfwd
, dedicated for the service created in the following step. We will use the same username on bot local and remote hosts, – so run the following command at both hosts.
sudo useradd -r -s /bin/false sshfwd
id sshfwd
uid=975(sshfwd) gid=975(sshfwd) groups=975(sshfwd)
-r
– create system user (note there is not specified home directory because we do need it),-s /bin/false
– this will be the default shell for the user.
Generate SSH key pair
It is preferable to use ED25519 based key, because it is more secure and also it is faster because is much shorten especially than 4096 bit RSA key.
Generate public and private SSH keys within your user's ~/.ssh
directory at the local host. The public key must be transferred to the remote host. The private key will be used at the local host.
mkdir -m700 ~/.ssh/sshfwd
ssh-keygen -t ed25519 -C 'sshfwd@local.host' -f ~/.ssh/sshfwd/id_ed25519_to_hostname -q -N ''
ls -la ~/.ssh/sshfwd
total 16
drwx------ 2 <user> <user> 4096 Jul 20 21:36 .
drwx------ 12 <user> <user> 4096 Jul 20 21:28 ..
-rw------- 1 <user> <user> 3381 Jul 20 21:36 id_ed25519_to_hostname
-rw-r--r-- 1 <user> <user> 742 Jul 20 21:36 id_ed25519_to_hostname.pub
-q
– silence;-N ''
– empty (without) passphrase.
Setup the Local host
1. Create a directory where the private key will be accessible for the sshfwd
user and copy it there.
mkdir -m700 /etc/ssh/sshfwd
sudo cp ~/.ssh/sshfwd/id_ed25519_to_hostname /etc/ssh/sshfwd/
sudo chown -R sshfwd:sshfwd /etc/ssh/sshfwd
2. Create the following configuration file.
sudo nano /etc/ssh/ssh_config.d/sshfwd-to-hostname.conf
# This file is loaded by a directive in /etc/ssh/ssh_config
Host sshfwd-to-hostname
HostName 192.168.2.200
IdentityFile /etc/ssh/sshfwd/id_ed25519_to_hostname
User sshfwd
Port 22
# While we are using 'GatewayPorts clientchoice' we must
# provide remote network interface whwere the prord will
# be bind otherwise it woll be 127.0.0.1:9200
RemoteForward 172.17.201.1:9200 127.0.0.1:9200
StrictHostKeyChecking no
Compression yes
Note the value sshfwd-to-hostname
of the directive Host
is an alias for the actual hostname or IP address provided by the HostName
directive. In the Example above 192.168.2.200
is the IP address of the remote host.
The directive RemoteForward 172.17.201.1:9200 127.0.0.1:9200
will bind the remote port 9200
on the remote interface 172.17.201.1
to the local port 9200
on the loopback interface 127.0.0.1
where our (Elasticsearch) service actually listen. Simplified version of this directive could be RemoteForward 9200 127.0.0.1:9200
which means the remote 0.0.0.0:9200
will be bind to the local 127.0.0.1:9200
.
However, In my case, I want to forward port 9200
only to a specific (docker's) remote interface and also I need to share it with the rest hosts that communicate with that interface – otherwise said the remote SSH server should be configured to operate as gateway, this will be done in the following steps.
The directive StrictHostKeyChecking no
is equivalent to the CLI option -oStrictHostKeyChecking=no
and will cause an automatic accept of the remote host's keys.
Setup the Remote host
0. The first thing you need to do is to transfer the file id_ed25519_to_hostname.pub
generated above to the remote host. In my case I already have an operational SSH connection, so rsync
is my first choice. Then log-in to the remote host – the following steps of this section are performed there.
rsync ~/.ssh/sshfwd/id_ed25519_to_hostname.pub remote.host.com:/tmp/
ssh remote.host.com
1. On the remote host, create a directory where authorized_keys
file for the sshfwd
user will be stored and use the the public key copied in the step above to create it.
mkdir -m700 /etc/ssh/sshfwd
sudo mv /tmp/id_ed25519_to_hostname.pub /etc/ssh/sshfwd/authorized_keys
sudo chown -R sshfwd:sshfwd /etc/ssh/sshfwd
2. Edit /etc/ssh/sshd_config
file on the remote host, and add the following match section at very bottom of the file.
sudo nano /etc/ssh/sshd_config
Match User sshfwd
AuthorizedKeysFile /etc/ssh/%u/authorized_keys
MaxSessions 0
GatewayPorts clientspecified
Explanation about the above directives could be found at man sshd_config
.
Match
– Introduces a conditional block. If all of the criteria on the Match line are satisfied, the keywords on the following lines override those set in the global section of the config file, until either another Match line or the end of the file. If a keyword appears in multiple Match blocks that are satisfied, only the first instance of the keyword is applied.AuthorizedKeysFile
– accepts the tokens%%
(A literal ‘%’),%h
(the home directory of the user),%U
(the numeric user ID of the target user), and%u
(the username).MaxSessions
– Specifies the maximum number of open shell, login or subsystem (e.g. sftp) sessions permitted per network connection. Multiple sessions may be established by clients that support connection multiplexing. Setting MaxSessions to 1 will effectively disable session multiplexing, whereas setting it to 0 will prevent all shell, login and subsystem sessions while still permitting forwarding. The default is 10.GatewayPorts
– Specifies whether remote hosts are allowed to connect to ports forwarded for the client. By default, sshd(8) binds remote port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports.GatewayPorts
can be used to specify that sshd should allow remote port forwardings to bind to non-loopback addresses, thus allowing other hosts to connect. The argument may beno
to force remote port forwardings to be available to the local host only,yes
to force remote port forwardings to bind to the wildcard address, orclientspecified
to allow the client to select the address to which the forwarding is bound. The default isno
.
- Note when you forwarding HTTP/HTTPS ports, which will be accessible via Revers Proxy you need to add
GatewayPorts yes
into the server's/etc/ssh/sshd_config
.
Test the connection
At this stage we should be able to establish a connection between the two hosts by using the new key pair. Note there is not login shell available for the sshfwd
user, for that reason if we attempt to just SSH into the remote host the connection will be immediately closed after it is successfully established. So we can add the options -T
and -N
to the ssh
command as follow.
sudo -u sshfwd ssh sshfwd-to-hostname -TN
Could not create directory '/home/sshfwd/.ssh' (No such file or directory).
Failed to add the host to the list of known hosts (/home/sshfwd/.ssh/known_hosts).
_
With this command we will establish an SSH connection on the name of the user sshfwd
. The connection will hang on the state shown at the gray box above. We can append the option -f
to our command in order to push the connection to the background.
sudo -u sshfwd ssh sshfwd-to-hostname -fTN
Explanation about the above directives could be found at man ssh
and man ssh_config
.
-f
– Requestsssh
to go to background just before command execution. This is useful ifssh
is going to ask for passwords or passphrases, but the user wants it in the background…-T
– Disable pseudo-terminal allocation.-N
– Do not execute a remote command. This is useful for just forwarding ports. Refer to the description ofSessionType
inssh_config
.
Later we can terminate the connection by killing all processes that belongs to the user sshfwd
.
sudo pkill -9 -u sshfwd
Before killing the connection you can log-in to the remote host and test whether the port is forwarded.
sudo netstat -pnat | grep sshfwd
tcp 0 0 172.17.201.1:9200 0.0.0.0:* LISTEN 317057/sshd: sshfwd
tcp 0 0 192.168.2.200:22 192.168.1.110:33948 ESTABLISHED 316980/sshd: sshfwd
Or as it is in my case test does the forwarded (Elasticsearch) service response correctly.
/usr/bin/curl 'http://172.17.201.1:9200'
{
"name" : "i2IO0Ft",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "UIIVp_38SVdt38NaDsawUA",
"version" : {
"number" : "5.6.16",
"build_hash" : "3a740d1",
"build_date" : "2019-03-13T15:33:36.565Z",
"build_snapshot" : false,
"lucene_version" : "6.6.1"
},
"tagline" : "You Know, for Search"
}
Install Autossh
The service below uses the command autossh
which is wrapper of the ssh
command. So if you don't want to install additional packages just modify the ExecStart
directive of the service, otherwise you will need to install it.
sudo apt update && sudo apt install autossh
Create "systemd" service for Port forwarding
sudo nano /etc/systemd/system/sshfwd-to-hostname.service
[Unit]
Description=Forward ports to <hostname>
After=network-online.target
[Service]
User=sshfwd
Environment=AUTOSSH_GATETIME=0
ExecStart=/usr/bin/autossh -M 0 -TN -q -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" sshfwd-to-hostname
ExecStop=/usr/bin/pkill -9 -u sshfwd # kill all processes owned by sshfwd
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable sshfwd-to-hostname.service
sudo systemctl start sshfwd-to-hostname.service
Install SSHFS
SSHFS is a filesystem client based on SSH. SSHFS allows you to mount a remote filesystem using SSH (more precisely, the SFTP subsystem). Most SSH servers support and enable this SFTP access by default, so SSHFS is very simple to use – there's nothing to do on the server-side.
By default, file permissions are ignored by SSHFS. Any user that can access the filesystem will be able to perform any operation that the remote server permits – based on the credentials that were used to connect to the server. If this is undesired, local permission checking can be enabled with -o default_permissions
.
By default, only the mounting user will be able to access the filesystem. Access for other users can be enabled by passing -o allow_other
. In this case you most likely also want to use -o default_permissions
.
It is recommended to run SSHFS as regular user (not as root). For this to work the mountpoint must be owned by the user. If username is omitted SSHFS will use the local username. If the directory is omitted, SSHFS will mount the (remote) home directory. If you need to enter a password sshfs will ask for it (actually it just runs ssh which ask for the password if needed).
In most cases it is not available by default and must be installed.
sudo apt install sshfs
Here is a basic example how to do mount via sshfs
at the command line.
sshfs hostname:/remote/path ~/sshfs-mount-point
- In this example
hostname
i defined within the file~/.ssh/config
or within a global config file – i.e./etc/ssh/ssh_config.d/sshfwd-to-hostname.conf
.
SSHFS can be used also within /etc/fstab
as it is described in its man page, or can be used within systemd mount unit as it is shown in the next section.
Create "systemd" service for SSHFS mounting
sudo nano /etc/systemd/system/mount-remote-fs.mount
[Unit]
Description=Mount a remote directory to /mnt/home.network.com/user-downloads
[Mount]
What=user@home.network.com:/home/user/Downloads
Where=/mnt/home.network.com/user-downloads
Type=fuse.sshfs
Options=_netdev,allow_other,port=22,default_permissions,IdentityFile=/etc/ssh/sshfwd/id_ed25519_to_hostname,reconnect,ServerAliveInterval=30,ServerAliveCountMax=5,x-systemd.automount,uid=33,gid=33
TimeoutSec=60
#Restart=always
#RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable mount-remote-fs.mount
systemctl start mount-remote-fs.mount # to mount the remote directory
systemctl stop mount-remote-fs.mount # to unmount the remote directory
systemctl status mount-remote-fs.mount # to check the status
In the above example we have mounted a directory from a remote instance for the user www-data
, probably some web service as NextCloud will access this directory :) This is a kin of special case, in most cases we will mount a directory for the current user in use as it is shown in this guide: Mount Remote Filesystems Using SSHFS and systemd.
References
- Freedesktop.org: Systemd Manual
- Linuxize: Using the SSH Config File
- GitHub Gist: Thomasfr/autossh.service
- Coderwall: Allow for selecting interface when port forwarding
- Eric's blog: Creating a persistent SSH tunnel in Ubuntu
- Buggycoder: Mount Remote Filesystems Using SSHFS and systemd,
- Github Gist: proprietary/mnt-mymountpoint.mount
- Ask Ubuntu: Access remote multiple servers behind NAT
- Ask Ubuntu: Using SSHFS on C accessed via A and B
- Ask Ubuntu: How do I setup SSH key based authentication for GitHub by using ~/.ssh/config file?
- Ask Ubuntu: How to copy (or move) files from remote machine to local machine?
- Ask Ubuntu: How to share files/folders between two different Ubuntu computers and are on different network?
- Ask Ubuntu: Filezilla or WinSCP alternative for Ubuntu
- GitHub: a2proxy – collection of Apache2 Reverse Proxy example configurations and generator script