User Tools

Site Tools


networking:ssh:multiple-systemd-ssh-agents

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]:
    1. Create directory structure:
      sudo mkdir -p /etc/systemd/user/
    2. 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]:
    1. Create directory structure:
      mkdir -p ~/.config/systemd/user/
    2. 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 another bar:
    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!


networking/ssh/multiple-systemd-ssh-agents.txt · Last modified: 2022/05/06 22:00 by chuck