Over SSH, can you use the same private key on the host side for other purposes?

I'm sat on one computer ('client') and connecting through SSH to another computer ('host') using my private key.

On the host computer, I would like to clone a Git repo using the private key that's stored on the client computer.

enter image description here

Is this possible? (Without copying the private key over to the host computer.)


Solution 1:

Theory

The functionality you seek is called "SSH agent forwarding". It works like this:

  1. There is an agent (a program, commonly ssh-agent) running in your local computer (or already forwarded from elsewhere). Other programs can communicate with the agent via a UNIX-domain socket, if only they know the path to the socket. Proper setup provides the right path.
  2. You add keys to the agent on demand (ssh-add …), or programs like ssh add keys they use (if they are configured to do so).
  3. Programs use the agent instead of using the key(s) on their own. In terms of this answer of mine we would say they ask the agent to solve puzzles for them.
  4. You use ssh -A locally to connect to an SSH server. Programs you then run on the server will be able to communicate with your local agent. Anything you run that knows how to talk to an agent or uses ssh under the hood will be able to use your local agent. The fact the agent is on another computer won't matter for them.

Starting an agent

There are few ways to start an agent:

  1. Some systems are configured to start an agent when a user logs in. Each user gets their own agent. Your OS may have already started an agent for you. In a shell invoke:

    env | grep '^SSH_AUTH_SOCK='
    

    Non-empty output means the SSH_AUTH_SOCK variable is in the environment. The value of it is (or at least should be) the path to the UNIX-domain socket that allows programs to communicate with the already running agent. Programs find the socket by checking this exact environment variable. The existence of the variable normally means an agent is running and ready. A variable named SSH_AUTH_SOCK that doesn't point to the socket of your running agent is abnormal (something went wrong).

    If you see the variable then most likely an agent is there for you. Proceed to the next section of this answer.

  2. You can start an agent by invoking ssh-agent in a shell, but don't do this directly. You want programs to be able to find the right variable in the environment. Sole ssh-agent cannot alter the environment of its parent shell. It prints commands designed to be evaluated by the shell, so the shell itself changes its own environment. You can invoke ssh-agent to see what it prints, but then kill the PID you learn from the output, otherwise the useless agent will keep running in vain.

    The right command is:

    eval "`ssh-agent`"
    

    There are few things that need explanation:

    • eval can be dangerous, you may have heard that "eval is evil". Here it's the right thing because ssh-agent is one of few tools specifically designed to be used with eval safely.

    • Different shells require different syntax. What ssh-agent prints needs to match the shell. The tool prints shell code valid either in sh (and shells with similar syntax, e.g. Bash) or in csh (and shells with similar syntax). ssh-agent guesses the right style by examining the SHELL variable; if it cannot guess then it will generate code for sh. You can explicitly tell it what style you need by using -s (for sh-like shells) or -c (for csh-like shells). The command will be like:

      eval "`ssh-agent -s`"
      

      If you want to see the difference, invoke ssh-agent -s and ssh-agent -c without eval. Don't forget to kill the right PIDs afterward.

    • Nowadays we often use $() instead of backticks (and it's the right thing, $() is better), but $() doesn't work in csh. This answer uses backticks to be equally useful in sh-like shells and in csh-like shells.

    After the eval … command, env | grep '^SSH_AUTH_SOCK=' should show you something. The agent will run until you kill it. The most proper way to kill the agent is:

    eval "`ssh-agent -k`"
    

    in the same shell as before (use -s xor -c if needed). Without eval the command is able to kill the agent but the variables will stay in the environment of the shell. No matter what you do in this shell, the variables will stay in the environments of child processes that inherited them and are still running.

  3. Yet another way of starting an agent is like this:

    ssh-agent some_command …
    

    The tool will start an agent and then run some_command … in the right environment. some_command may be bash, i.e. if you do:

    ssh-agent bash
    

    then you will find yourself in an interactive Bash where SSH_AUTH_SOCK is already in the environment (note if your original shell is also Bash then it may seem the command did nothing; like invoking bash from Bash may seem a no-op, until you exit and find out you're still in Bash).

    The agent exits automatically when some_command terminates, no maintenance is needed. This method is quite straightforward.

    If you need an agent only for one local ssh then some_command may be the ssh. In your case you need ssh -A:

    ssh-agent ssh -A …
    

    Depending on what key(s) you want to use and the configuration of ssh, the above may or may not be a good idea. In some circumstances you may want to do something with the agent before you run ssh -A (like adding keys, see below).


Adding keys to an agent

When an agent starts, it does not have any private keys. If you find an agent started by your OS, it's theoretically possible the OS not only started the agent but also added some key(s). Keys are added using ssh-add or by ssh when AddKeysToAgent is set in ssh_config.

In a shell with access to an agent (SSH_AUTH_SOCK in the environment, the agent running) invoke:

ssh-add -l

to see keys (identities) represented by the agent. There may be none. To add a key run:

ssh-add path/to/key

Note ssh-add without arguments probes some standard pathnames and adds files it finds. See man 1 ssh-add for details.

If AddKeysToAgent is set in ssh_config (by default it's not) or if you request it in the command line, ssh itself will add the key it uses. This means if you want to use the same key to authenticate to the server and later on the server then you don't need ssh-add. You can do:

# with agent already running
ssh -A -o AddKeysToAgent=yes …

This works with keys specified with -i as well. These are the circumstances that allow you to run an agent and ssh in one command (the method mentioned at the end of the previous section):

# already running agent (if any) is irrelevant
# we are starting a new agent
ssh-agent ssh -A -o AddKeysToAgent=yes …

Invoking ssh

The relevant option is -A. From man 1 ssh:

-A
Enables forwarding of connections from an authentication agent such as ssh-agent(1). This can also be specified on a per-host basis in a configuration file.

(The keyword in the configuration file is ForwardAgent.)

The server may or may not allow forwarding. See AllowAgentForwarding and DisableForwarding in man 5 sshd_config.

Be warned:

Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's UNIX-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent. […]

If the server allows forwarding and the right key has been added to the local agent then -A is all you need to add to the ssh command you use to connect to the server. It may be as easy as:

ssh -A user@server

And with ForwardAgent yes in ssh_config (or in your ~/.ssh/config) you don't even need -A in the command line, pure ssh user@server will do.


Situation on the server

If your local ssh -A … gives you an interactive remote shell then you can use the same command as we used earlier (locally), now on the server:

# on the server
env | grep '^SSH_AUTH_SOCK='

If the forwarding works then you will find the variable set. It's as if ssh-agent was running, but it's really the SSH server who listens on the socket and relays to your local ssh-agent. Programs that use the socket don't care. You can even forward the agent further (with ssh -A from the server to yet another computer).

Simply use ssh or another command able to talk to an agent. It should find the socket and talk to the local ssh-agent.

ssh-add is no different. It's not obvious, but if you use ssh-add on the server to add some key then the tool will actually add the key from the server (rather than telling your local ssh-agent to open a local file with the same path). You can disconnect from the server and your local agent will keep holding the added key. So a local ssh-agent not only allows you to use your local key on the server; it also allows you to use your key from the server to connect from your local to wherever. There's a big difference though. ssh-agent needs to store some representation of added keys, if only in memory. Local ssh-add talking to a local ssh-agent does not make the local key leave the local computer. Remote ssh-add talking to a local ssh-agent copies the remote key to local memory.

If your local ssh -A … specifies a command to run on the server (as opposed to an interactive shell), nothing will change in the mechanics of agent forwarding. The remote command will find SSH_AUTH_SOCK in the environment and it will be able to talk to your local ssh-agent.


Possible problems (already solved), interesting cases

  • How do I get ssh to use only one key from ssh-agent?
  • SSH always using first key accepted by server

Documentation

  • man 1 ssh-agent
  • man 1 ssh-add
  • man 1 ssh
  • man 5 ssh_config
  • man 5 sshd_config

Other

  • Guide on GitHub