Table of Contents
Multiple systemd SSH Agents
This works great on my Debian Bullseye / i3wm system. I don't see why this wouldn't work on other distros / DEs but YMMV.
Create a ssh-agent systemd service template
- To enable agent creation for all users, create
/etc/systemd/user/[email protected]
:- Create directory structure:
sudo mkdir -p /etc/systemd/user/
- Create the service file:
cat << EOF | sudo tee /etc/systemd/user/[email protected] [Unit] Description=SSH authentication agent for %I Before=default.target [Service] Type=simple Environment=SSH_AUTH_SOCK=%t/%i-agent.socket ExecStart=/usr/bin/ssh-agent -D -a %t/%i-agent.socket SuccessExitStatus=2 [Install] WantedBy=default.target # vim: ft=dosini ts=2 sts=2 sw=2 sr et EOF
- To enable agent creation for only you, create
~/.config/systemd/user/[email protected]
:- Create directory structure:
mkdir -p ~/.config/systemd/user/
- Create the service file:
cat << EOF > ~/.config/systemd/user/[email protected] [Unit] Description=SSH authentication agent for %I Before=default.target [Service] Type=simple Environment=SSH_AUTH_SOCK=%t/%i-agent.socket ExecStart=/usr/bin/ssh-agent -D -a %t/%i-agent.socket SuccessExitStatus=2 [Install] WantedBy=default.target # vim: ft=dosini ts=2 sts=2 sw=2 sr et EOF
- Enabling creation for all users does NOT mean that all users can access your agent, it just means that they can create their own agents!
Administration
Manage the ssh-agent service
- Start and enable the agent as your regular user (and similar for the other agent(s)):
systemctl --user enable --now ssh-agent@<name>.service
- Check status of the ssh-agent service:
systemctl --user status ssh-agent@<name>.service
- Stop the ssh-agent service:
systemctl --user stop ssh-agent@<name>.service
Naming the ssh-agent services
- Whatever name you use for each service will be included in the name of the agent's socket.
- For example, if we named one
foo
and anotherbar
:systemctl --user enable --now ssh-agent@foo.service systemctl --user enable --now ssh-agent@bar.service
- We would have two new socket files:
"$XDG_RUNTIME_DIR/foo-agent.socket" "$XDG_RUNTIME_DIR/bar-agent.socket"
Set a default ssh-agent
- Create an environment variable in your preferred startup file (eg
~/.profile
,~/.bash_profile
, etc):socketpath="$XDG_RUNTIME_DIR/<name>-agent.socket" if [ -S "$socketpath" ]; then export SSH_AUTH_SOCK="$socketpath" fi
Configure SSH to use the agent
- Use the IdentityAgent directive and add the SSH agent socket in your
~/.ssh/config
:Host myhost ... AddKeysToAgent yes IdentityAgent "/run/user/%i/<name>-agent.socket" IdentitiesOnly yes
- ForwardAgent can be configured to forward the agent in
~/.ssh/config
:Host myhost ... AddKeysToAgent yes ForwardAgent "/run/user/%i/<name>-agent.socket" IdentityAgent "/run/user/%i/<name>-agent.socket" IdentitiesOnly yes
- Leaving those unconfigured, the hosts will use whatever
$SSH_AUTH_SOCK
is set to by default.
Manage the keys in the ssh-agent
- For the default ssh-agent, running only the commands will work:
- Add keys to the agent:
ssh-add ~/.ssh/ed_25519
- List keys in the ssh-agent:
ssh-add -L
- Clear keys from the agent:
ssh-add -D
- Prepend the command with the environment variable to access other ssh-agents:
- Add keys to the agent:
SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/<name>-agent.socket" ssh-add ~/.ssh/work
- List keys in the ssh-agent:
SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/<name>-agent.socket" ssh-add -L
- Clear keys from the agent:
SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/<name>-agent.socket" ssh-add -D
Configuring IdentityAgent and AddKeysToAgent in ~/.ssh/config
will automatically add the key to the correct agent when you use it.
Gotchas
- Debian auto-starts a ssh-agent at login.
- Comment or remove the line in
/etc/X11/Xsession.options
to stop it from starting:sudo sed -i 's/^use-ssh-agent$/# use-ssh-agent/g' /etc/X11/Xsession.options
ssh-add -L
will only list keys in$SSH_AUTH_SOCK
.- Specify the ssh-agent by passing the variable:
SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/<name>-agent.socket" ssh-add -L
- If using ssh-askpass, add the following to the service file:
# DISPLAY required for ssh-askpass to work Environment=DISPLAY=:0
- ssh-agent exits with code 2 which results in 'failed' state on stop and I found 2 options to remedy this:
- Inform systemd that a successful exit status is
2
:SuccessExitStatus=2
- Prefix the ExecStart with a
-
to suppress this behavior:ExecStart=-/usr/bin/ssh-agent -D -a %t/%i-agent.socket
- Currently untested: Enable linger for users who need the agents to be active while not logged in:
loginctl enable-linger <user>
Example
After following the steps above, as an example I'll create a “default” agent for my everyday ssh keys and a “work” agent for my work ssh keys.
Create "Default" SSH Agent
- Start and enable the “default” ssh-agent:
systemctl --user enable --now ssh-agent@ssh.service
- Check status:
systemctl --user status ssh-agent@ssh.service
- Configure environment for the “default” agent:
cat << EOF >> ~/.profile socketpath="$XDG_RUNTIME_DIR/ssh-agent.socket" if [ -S "$socketpath" ]; then export SSH_AUTH_SOCK="$socketpath" fi EOF
- Source the file to load the variable:
source ~/.profile
- Add keys to “Default” agent:
ssh-add ~/.ssh/key
- List keys in “Default” agent:
ssh-add -L
Create "Work" Agent
- Start and enable a “work” agent:
systemctl --user enable --now ssh-agent@work.service
- Check the status:
systemctl --user status ssh-agent@work.service
- Add entries for work machines:
Host work-workmachine-1 HostName XX.XX.XX.XX ForwardAgent "/run/user/%i/work-agent.socket" ProxyJump work-jump
- Add wildcard entry for work machines at bottom of
~/.ssh/config
:Host work-* User username IdentityFile "%d/.ssh/work" IdentityAgent "/run/user/%i/work-agent.socket"
- Add keys to “Work” agent:
SSH_AUTH_SOCK="XDG_RUNTIME_DIR/work-agent.socket" ssh-add ~/.ssh/work
- List keys in “Work” agent:
SSH_AUTH_SOCK="XDG_RUNTIME_DIR/work-agent.socket" ssh-add -L
Configuring IdentityAgent and AddKeysToAgent in ~/.ssh/config
will add the key to the correct agent when you first use it.
Bonus Tip
- You can create a
ssh
directory in your$XDG_RUNTIME_DIR
to contain the agent socket files. - This is a good opportunity to make use of systemd's
tmpfiles.d
to create the directory at boot. - Create directory to hold the config files:
mkdir -p ~/.local/share/user-tmpfiles.d
- Create a configuration file:
cat << EOF > ~/.local/share/user-tmpfiles.d/ssh-config.conf d %t/ssh 0700 - - - EOF
- Create the directory:
systemd-tmpfiles --user --create
- Enable the tmpfiles service so it creates the directory at boot:
systemctl --user enable --now systemd-tmpfiles-setup.service
- Enable the cleanup timer:
systemctl --user enable --now systemd-tmpfiles-clean.timer
- Edit the paths in the files above to add the
ssh
dir. For example:Environment=SSH_AUTH_SOCK=%t/ssh/ssh-agent.socket
For multi-user systems, make sure to add/configure files in /etc/skel
so the agent dependencies are set up during user creation.
Conclusion
That's the general idea and should get your system set up with as many ssh-agents as you would ever want to create. Enjoy!