SSH allows secure connections from one host to another. All traffic is encrypted. Authentication is usually by means of a key pair, where the private key resides on your local machine, and the public key is imported to the remote system. SSH keys have become particularly important for cloud computing, where users need to access cloud servers over a potentially hostile Internet.
Sometimes, the requirement is to access one system via another. You “hop” through the first system to reach the second. The following article shows how to do that, in a secure way, without having to place a private SSH key onto the middle system.
SSH Key Arrangements
You could just create two SSH key pairs: one pair for access from A to B and the second to access C from B. However this involves installing a private SSH key onto the middle (potentially public) system, which is not desirable. An intruder who compromises server B might be able to read the private key there, and use it to access server C.
Instead, use only one key pair. Put the public key on B and C. Leave the private key on A only. This is the best and most secure arrangement. Using it will require SSH agent forwarding or SSH proxying. Either will do. The rest of this article explains how to do both.
SSH Agent Forwarding
We will cover agent forwarding first because it is better known. These examples assume that your user name on all 3 systems is “frederick“. You can use different user names on each system, just substitute as necessary.
With the private key on your local system (located in your home directory under .ssh/id_rsa), and the public key installed on both intermediate system B and target C (in authorized_hosts), proceed as follows.
Start the ssh-agent process.
frederick@hostA:~$ eval `ssh-agent`
Add your key information to the running agent:
frederick@hostA:~$ ssh-add ~/.ssh/id_rsa Enter passphrase for /home/frederick/.ssh/id_rsa: Identity added: /home/frederick/.ssh/id_rsa (/home/frederick/.ssh/id_rsa)
Check it by listing keys contained in the agent:
frederick@hostA:~$ ssh-add -l 2048 9e:db:5f:e0:92:b5:d4:67:2e:3a:ee:b4:32:b7:0e:28 /home/frederick/.ssh/id_rsa (RSA)
Next, use ssh with the -A flag to get onto system B.
frederick@hostA:~$ ssh -A -l frederick -i ~/.ssh/id_rsa hostB
where “hostB” is the IP address or resolvable host name of system B. Next, SSH into C, where “hostC” is the IP address or hostname of target system C:
frederick@hostB:~$ ssh -A C Enter passphrase for key '/home/frederick/.ssh/id_rsa': ... frederick@C:~ $
Success! You are logged into system C.
- The above method can be repeated through as many intervening systems as necessary, so long as your public key is installed.
- The key passphrase is entered when the private key is added to the agent, and does not have to be re-entered during the subsequent SSH.
- It is not necessary to start an ssh-agent process on any system other than A, your local system.
- The ssh-agent process on A will continue to run after you log out of all systems. Don’t forget to kill it.
Once the key has been added to the local ssh-agent, a “authentication token” is forwarded by the -A flag of the SSH command, telling the remote systems that you have already authenticated with the private key.
It is possible to connect to an end system via an intermediate one with a single command, with no agent involvement and no authentication forwarding. This SSH command will do the whole lot in one big jump. As in the above example, the public key should be on B and C, with the corresponding private key on A only:
frederick@A:~$ ssh -l frederick -oProxyCommand='ssh -l frederick -i ~/.ssh/id_rsa hostB nc hostC' C Enter passphrase for key '/home/frederick/.ssh/id_rsa': Enter passphrase for key '/home/frederick/.ssh/id_rsa':
It is two ssh commands. The “inner” one, within single speech marks, starts a netcat (“nc”) process on intermediate server B, which acts as a proxy, carrying data between B and the target server C. This is connected to the “outer” ssh (outside the single speech marks). The two combine to take your input via server B to server C, and bring output back again. You have to type in the passphrase twice: once for each SSH process.
Note: “nc” must be installed on B for this to work. nc is not installed on all systems by default.
More recent versions of SSH include a “-W” switch, obseleting the nc:
frederick@A:~$ ssh -l frederick -oProxyCommand='ssh -l frederick -i ~/.ssh/id_rsa hostB -W hostC' hostC
In fact the inner “hostC” can be replaced with the -W special flag %h, which translates to the host to connect to:
frederick@A:~$ ssh -l frederick -oProxyCommand='ssh -l frederick -i ~/.ssh/id_rsa hostB -W %h' hostC
If ssh is running on a non standard port, there is also a “%p” that can be used with the -W switch:
frederick@A:~$ ssh -l frederick -oProxyCommand='ssh -l frederick -i ~/.ssh/id_rsa hostB -W %h:%p' hostC
The proxying example uses a single key pair. It is, of course, possible to use two key pairs instead, one for system B and the other for C. In which case, the corresponding private keys should both be installed on A, with one public key on B and the other on C. The two passphrase prompts will pertain to each pair and might require different passphrases to be entered.
Proxying vs Agent Forwarding
Why is proxying better and more secure than agent forwarding? Well, first you don’t have to bother running an agent on the originating system and adding a key to it. Secondly and more importantly, because there is no agent, there is no socket file created. To recap, when you run an agent, a socket file is created and envrironment variables are set accordingly.
frederick@A:~$ ssh-agent SSH_AUTH_SOCK=/tmp/ssh-JVE6e5qBu5O1/agent.27366; export SSH_AUTH_SOCK; SSH_AGENT_PID=27367; export SSH_AGENT_PID; echo Agent pid 27367; frederick@A:~$ ls -l /tmp/ssh-JVE6e5qBu5O1/agent.27366 srw------- 1 frederick frederick 0 Oct 24 15:37 /tmp/ssh-JVE6e5qBu5O1/agent.27366
Unfortunately, another user (on system A) can set the same variables and then ssh to the endpoint, using the key, and without having to enter the key passphrase, ie. they can use the forwarded authentication token. Permissions on the socket file would normally prevent it, but not for somebody with root rights, who could also acquire and set the neccessary variables. I have successfully tested the procedure, but won’t put further details here.
With proxying there is no socket file and no associated vulnerability.
SSH Proxting and agent forwarding both work. Proxying more secure and slightly more convenient.