An often overlooked feature of the OpenSSH client is the ControlMaster
feature. This feature allows multiple sessions to share a single TCP connection. This results in fast session startup and single sign on.
There are many web pages which explain the feature in more detail which is not the point of this post. Here are some sources:
- Rackspace – Speeding up SSH session creation
- OpenSSH/Cookbook/Multiplexing
- OpenSSH – ssh_config Manual Page
I wanted to use this feature but I also had some of requirements:
- I wanted the sockets that are created (specified by the
ControlPath
setting) to be stored in RAM and not written to disk. This results in all of the files being cleared on reboot. - A new RAM file system should be used for this purpose, not just reusing an existing one like “/tmp”. The new file system should be mounted in my users home directory.
- The permissions for the mount point should be restrictive.
- The configuration should be maintainable by using native systemd features, not adding hacks in
/etc/rc.local
for example.
In the below instructions you will need to replace the path /home/username/.ssh/sockets
with the correct path for your environment.
RAMFS Setup
First I will configure the RAM file system. I will be creating a 2MB file system that will be mounted at /home/username/.ssh/sockets
. Ensure the sockets directory path has already been created before continuing. The RAMFS in the below mount file has 10,000 inodes; this should be more than enough in almost every situation but it can be raised if required (each socket will take up 1 inode).
Create the mount file. The mount file is stored in /etc/systemd/system
and is should be named in the format “home-username-.ssh-sockets.mount
“. This file must reside in the global system directory, it is not (currently) possible to use a user systemd mount file. The contents of the file should be as follows:
[Unit] Description=RAM FS for SSH Multiplexing (/home/username/.ssh/sockets) Documentation=https://systemd.io/TEMPORARY_DIRECTORIES Documentation=man:file-hierarchy(7) Documentation=https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems ConditionPathIsSymbolicLink=!/home/username/.ssh/sockets ConditionPathExists=/home/username/.ssh/sockets DefaultDependencies=no Conflicts=umount.target Before=umount.target After=home.mount [Mount] What=tmpfs Where=/home/username/.ssh/sockets Type=tmpfs Options=mode=0700,strictatime,nosuid,nodev,size=2M,nr_inodes=10k,uid=username,gid=username [Install] WantedBy=local-fs.target
You must make the following changes (the lines have been highlighted above):
- Line 2 – Set an appropriate description
- Line 6, 7 and 15 – Set the correct mount location
- Line 11 – Since I have
/home
on a separate partition, I add a dependency on that mount point. If you do not have/home
on a separate partition you can remove that line. - Line 17 – Set the correct UID and GID. Optionally you can resize the tmpfs size.
Note: The filename for the systemd mount file MUST match the path that the file system will be mounted at with all forward slashes replaced with hyphens. You will get an error like this if it is incorrectly named:
Mar 03 23:58:15 laptop systemd[1624]: SSH-RAMFS-test.mount: Where= setting doesn't match unit name. Refusing.
You will need to make sure the contents of the file has had the correct file system path set as well as the correct UID and GID configured in the mount options.
The mount can then be enabled and started:
sudo systemctl enable home-username-.ssh-sockets.mount sudo systemctl start home-username-.ssh-sockets.mount
The new RAM file system should be mounted and ready to use. If you get an error starting the file system, check the logs with systemctl status home-username-.ssh-sockets.mount
and journalctl -xe
.
SSH Client Setup
The SSH client can now be configured to use the RAM file system to store the sockets. In the SSH configuration file (~/.ssh/config
– it may not exist if you have never configured anything inside it before) you can select which hosts will use the ControlMaster
feature.
To use the feature for any hosts on the example.com domain, the following content would be added:
Host *.example.com ControlMaster auto ControlPath ~/.ssh/sockets/%C ControlPersist 36000
If you would like the configuration to apply to ALL hosts you would simply set a wildcard host:
Host * ControlMaster auto ControlPath ~/.ssh/sockets/%C ControlPersist 36000
In the above configuration, I have set the ControlPersist
option to 36,000 seconds (10 hours). The available settings for this option are:
no
– The default if not specified. If you set this option and logout of the “master” SSH session the logout will hang until all other SSH sessions are closed.yes
– The socket will remain indefinitely. You can logout of the “master” session and the control socket will persist.- <integer> – The number of seconds the socket will remain open. Again, you can logout of the “master” session and the control socket will persist for the defined amount of time after the last remaining session is closed.
The ControlPath
in the above examples uses the %C
token to create the socket name for each connection. This token is a hash of the connection (username, host, port etc.) generated at run time; it prevents the socket name exposing what username and hostname it is associated with. An example socket name with this token is 17f10663fa84f87449a6ad6ecc09947845b0db99
.
If you prefer to expose the username/host/port you can change this to your liking. As an example:
ControlPath ~/.ssh/sockets/%r@%h-%p
This will result in socket names in the format <username>@<hostname>-<port>. A complete list of tokens is available in the ssh_config man page here.
Verification
You should now be able to create a new SSH connection and verify that the socket is created in ~/.ssh/sockets
. You should then be able to open a second connection and you will not be prompted for any authentication and the session should establish very quickly.