Secure Shell (SSH): Advanced Techniques
This document describes some more advanced techniques users of SSH (Secure SHell) may wish to try. A basic understanding of SSH, its use and its purpose is assumed. An earlier version of this document focussed on SSH protocol 1.5 and earlier implementations. It can be found here. This version will focus on the SSH protocol 2 as implemented by OpenSSH. OpenSSH now is part of the default OS installation on all BSDs, most Linux distributions I know of, and Sun's Solaris 9 (SunOS 5.9) and rapidly is becoming the de facto standard implementation. This document will not cover non-CLI versions of SSH, such as SecureCRT or NiftyTelnet.
Before going further, let me point you to my SSH-related links collections. And, look here for the change history of this document.
Public Keys
A sadly underutilized feature of SSH is its ability to authenticate based on RSA and DSA public keys. A public key is a (by default) 1024-bit key signed by an optional passphrase. The key size and algorithm, in principle, make a passphrase-protected key practically unbreakable by brute force attack. Cryptographically, RSA and DSA are distinct but as far as I know they are entirely comparable functionally. I will concentrate on DSA keys in this document for no particular reason.
DSA key authentication is a public key system, similar to PGP, GnuPG or SSL, though there is no public authority that verifies key authenticity. When you generate a key via ssh-keygen(1), two key files are created: $HOME/.ssh/id_dsa and $HOME/.ssh/id_dsa.pub. The id_dsa file is your private key, and should be mode 600 or 400. Your id_dsa.pub is your public key. To perform DSA authentication, you append the public key to $HOME/.ssh/authorized_keys on some remote host. Verify that directories leading to this path do not provide world or group write. Now, when you initiate an SSH connection to the remote host, you will be prompted for the passphrase to your DSA key. To sum up:
jrandom@foo.example.com:~ $ ssh-keygen -t dsa Generating public/private dsa key pair. Enter file in which to save the key (/home/jrandom/.ssh/id_dsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/jrandom/.ssh/id_dsa. Your public key has been saved in /home/jrandom/.ssh/id_dsa.pub. The key fingerprint is: a8:26:45:d0:d8:3d:87:8e:b7:42:e8:35:03:6b:35:6b jrandom@foo.example.com jrandom@foo.example.com:~ $ cat $HOME/.ssh/id_dsa.pub | \ ssh -ljrandom bar.example.com cat >> .ssh/authorized_keys jrandom@foo.example.com:~ $ ssh bar Enter passphrase for RSA key 'jrandom@foo.example.com': jrandom@bar.example.com:~ $ |
Key Installation
If something doesn't go according to plan, try running ssh with the "-v" flag set. Verbose SSH output might reveal the problem with your DSA key. Oftentimes it's permissions. No node leading to the authorized_keys file may be writable by anyone other than yourself or the superuser. Another common problem you may encounter is that authorization on a system may be precluded by your account's status being "locked" or otherwise unavailable. For example, a "*LK*" entry for your password in a Solaris shadow password file (which is the state until the password is set for the first time), will preclude your ability to login.
Keys without Passphrases
Now that you have a key, you can investigate passphrase-less options for its use.
The simplest approach is not to set a passphrase. For certain purposes, this is necessary. For example, to use keys for remote access in automated tools, such as cronjobs, no opportunity will be presented to provide passwords. Hence a passphrase-less key is called for. If you choose to use a key without a passphrase, be warned. If the key is compromised, so is the trust level that key provided on remote hosts.
ssh-agent
Another viable option is the use of ssh-agent(1). ssh-agent is invoked with a command as an argument. For example, "ssh-agent sh". The resulting process is invoked after forking the ssh-agent in the background. The ssh-agent manages your keys for you, providing passphrase-less access to each key you add to the agent's ring, via ssh-add(1). Further, the process started by ssh-agent has two special environment variables set: SSH_AGENT_PID and SSH_AUTH_SOCK. Their presence is detected by ssh, which will attempt to obtain usable keys from the agent. Note that these environment variables are passed to all subprocesses of whatever command is spawned by ssh-agent. Another example:
jrandom@foo.example.com:~ $ ssh-agent bash jrandom@foo.example.com:~ $ env | grep SSH SSH_AGENT_PID=12345 jrandom@foo.example.com:~ $ ssh-add Need passphrase for .ssh/identity Enter passphrase for jrandom@foo.example.com: Identity added: .ssh/identity (jrandom@foo.example.com) jrandom@foo.example.com:~ $ env | grep SSH SSH_AGENT_PID=12345 SSH_AUTH_SOCK=/tmp/ssh-jrandom/agent.12345 |
Please note that root may compromise your ssh-agent environment trivially, by gaining access to your agent's socket.
You may also have noticed that careful environment manipulation might allow your cronjobs to access a passphrase-protected key exposed via ssh-agent. While this is true, no real security enhancement has been gained. In either event, a root compromise compromises the key.
ssh-agent chains
Note that ssh-agent is automatically started on remote hosts you login to. These agents will forward key requests back to the originating session's agent. Thus you may use a key from one host to login to a host by way of an intermediary host. That is, if your key is authorized on the host baz.example.com and you login to bar.example.com, you can still use your key from bar to login to baz. Confusing?
jrandom@foo.example.com:~ $ ssh bar jrandom@bar.example.com's password: jrandom@bar.example.com:~ $ ssh baz jrandom@baz.example.com:~ $ |
You may also load keys into your local agent from remote hosts. For example, let us assume that in the scenario above, baz.example.com holds a key which is accepted on bar.example.com. Here is how things might play out:
jrandom@foo.example.com:~ $ ssh bar jrandom@bar.example.com's password: jrandom@bar.example.com:~ $ ssh baz jrandom@baz.example.com:~ $ ssh-add Need passphrase for .ssh/identity Enter passphrase for jrandom@foo.example.com: Identity added: .ssh/identity (jrandom@foo.example.com) jrandom@baz.example.com:~ $ exit jrandom@bar.example.com:~ $ exit jrandom@foo.example.com:~ $ ssh bar jrandom@bar.example.com:~ $ |
This can lead to bizarre, nested commands like "ssh bar ssh baz ssh-add" and stranger variations.
X and ssh-agent
Many users of RSA keys find it convenient to begin their X Windows System sessions wrapped inside the ssh-agent. If you are accustomed to entering X from a commandline prompt via "startx" or "xinit" or something of that sort, simply run your X initializtion as the argument to ssh-agent. For example, "ssh-agent startx". Things are more complicated if you use a display manager and session manager. See the appendix for hints on configuration with GUI X initialization.
If you have ssh-agent start X for you, your entire X session will inherit the ssh-agent keys. Simply open a shell window in X and ssh-add your key for any window on your display to have access to your ssh keys. Please be cautious with your X server's access control if you do this! Anyone with access to your display will be able to gain access to your keys in this manner. Use some form of hashed X authentication such as MIT-MAGIC-COOKIE-1 or XDM-AUTHORIZATION-1. See the Xsecurity(1) man page for more information.
If you don't feel that your X session is sufficiently safe, or if you're having trouble configuring ssh-agent in an XDM-type environment, you can still use ssh-agent in a more limited way. You may start an agent and then set the two SSH environment variables explicitly in other shells to gain access to the keyring. Close the ssh-agent session when you're done. In the example given above, the bash process will be given access to the jrandom@foo.example.com key until the shell terminates. In the meantime, you'll be free to make repeated use of the key without the passphrase.
authorized_keys Options
Sometimes, you might like to automate the process for some user to perform some task on your host, but don't want to provide all the functionality a normal user might be allowed. There are options to the authorized_keys file which may help you achieve your goal.
In the sshd(8) manpage, you will find information on how to limit SSH functionality available to a user when logging in with a specific key. For example, you can disallow tunneling, agent-forwarding and X11. The control is quite granular. You might also like to read the relevant chapter in O'Reilly's upcoming SSH: The Definitive Guide. It provides excellent information on this subject.
Nifty Tricks
Now that you know how to use passphrase-less DSA keys, you may wish to propogate your key to the $HOME/.ssh/authorized_keys files throughout your network. Having done this, you can enjoy powerful remote execution capabilities. A simple example:
jrandom@foo.example.com:~ $ ssh bar cat /etc/resolv.conf |
ssh can be invoked with a commandline argument, which gets executed by a non-interactive shell on the remote host. Hence bar's copy of /etc/resolv.conf will be printed to the local stdout. Since the remote command's stdout is connected to the local shell on foo, you can become more tricky, and do things such as:
jrandom@foo.example.com:~ $ ssh bar cat /etc/resolv.conf | \ diff /etc/resolv.conf - |
Which will show you the difference between foo and bar's resolv.conf. The ability to link together remote and local file descriptors via ssh is extremely powerful. Consider:
jrandom@foo.example.com:~ $ ssh bar tar -c -f - -C / . \| gzip -c | \ gzip -cd | tar -x -f - -C /home/bar |
In the above example a full system tarball is gzip'd prior to passage across the network within the ssh connection. Upon reception on foo, the datastream is unzip'd and untar'd to /home/bar.
Also, simple shell expressions also have enormous potential here. I regularly find that I'm forced to look at numerous copies of the same file to sanity check their contents. If you create a file named hosts.txt with one hostname per line, you could do something like:
jrandom@foo.example.com:~ $ for i in `cat hosts.txt`; do ssh $i cat /some/file done |
Simple shell loops and keys take a lot of the drudgery out of life.
There's no reason to stop at simple shell commands though. You could also try complex pipelines like the gzip pipeline with tar, or even passing a local script to a remote interpreter, such as:
jrandom@foo.example.com:~ $ cat test.pl |\ ssh bar perl jrandom@foo.example.com:~ $ for i in `cat hosts.txt`; do cat test.pl | ssh $i perl done |
Tunnels
SSH is also capable of tunneling, another terribly underutilized feature. Imagine that at 2AM you are paged because a host has failed. As you attempt to begin troubleshooting, you realize that you lack access to critical data on the corporate intranet. Lynx is not an option for some reason. Rather than trekking to the office, create a tunnel!
There are both local and remote tunnels. Generally local tunnels are more useful. A local tunnel begins with an arbitrary port on the local host (beware the privileged ports below 1024). This port is bound so that data traffic is piped to an Out Of Band stream within your SSH connection. On the remote host, data is forwarded to an arbitrary host and port. N.B. the remote host need not be the remote terminus of your tunnel. For example, I am at home at home.example.net and wish to reach a host at example.com, with a private IP address. I could ssh to baz.example.com, which has a public interface and tunnel to the host on the private network. That is:
jrandom@home.example.net:~ $ ssh -L 1025:192.168.1.2:80 baz.example.com |
Note that baz may be an entirely different host than 192.168.1.2. As long as it possesses a route to the private network, everything will work.
Having spun up a tunnel, if you point your local web browser at http://localhost:1025/, you'll be taken to port 80 of whatever host is at 192.168.1.2 on the private network.
Remote tunnels forward traffic to an arbitrary port on the remote host through the local host to an arbitrary host/port. Remote tunnels are established using the -R flag.
Note that any number of local and remote tunnels may be opened using a single SSH command. For example:
jrandom@home.example.net:~ $ ssh \ -L10025:smtp.example.com:25 \ -L10110:pop3.example.com:110 \ bar.example.com |
Switch User (su)
SSH can also be used as a replacement for the su(1) program. There's no specific reason SSH cannot be used to connect to the local host as a different user. If you don't need the granularity and logging capabilities offered by a program like sudo, SSH might be a good replacement for su.
Many environments require a large number of people to share root access to a host or group of hosts. This can become quite cumbersome, as staff members come and go, requiring passwords to be changed and relearned in order to revoke access of ex-employees. sudo and similar software can be used under the circumstances, but so can RSA keys. For example, rather than having all super-users sharing a single root password, one can add each super-user's RSA key to root user's authorized_keys list. After doing so, an eligible super-user can:
jrandom@foo.example.com:~ $ ssh -lroot localhost root@foo.example.com:~ $ |
Revoking a user's privileges is as simple as removing the key from the list of authorized_keys. Note that this technique need not apply exclusively to the root user account. Perhaps the Database Administrators frequently work as the database user account -- it may be appropriate to use keys rather than shared passwords.
Be careful however! Remember that any uid 0-user can compromise all passphrase-less keys stored on a system, simply by copying them or ssh-add'ing them to his agent. Theoretically, this user cannot compromise keys locked by a strong passphrase, but the passphrase itself could be stolen via a keystroke logger installed by the rogue root user. However, these forms of attack require a fair amount of planning prior to privilege revocation. Even without RSA keys, the miscreant could subtly compromise your systems using backdoor daemons, keystroke loggers to capture new passwords, etc.
Appendix
XDM and ssh-agent
For Solaris users using dtlogin, try setting your *wmStartupCommand resource in your .Xdefaults to "ssh-agent /usr/dt/bin/dtwm".
XFree86 users who wish to use ssh-agent should create a $HOME/.xsession file. This file is a simple shell script that invokes your preferred session, as well as possibly loading some X environment preferences. Here is a trivial example:
|
This .xsession should work for pretty much any XFree86 installation.
History
CVS commit logs are handy.
8/11/2000, clarified information on tunneling
8/23/2000, added information about chains
11/13/2000, added links
11/21/2000, su(1) replacement ideas
12/2/2000, added info on authorized_keys options and plug
for the book coming from ora.com; also added warning about
root and passphrase-less RSA keys
2/11/2001, removed links and relocated this page
9/11/2001, changed some presentation
7/15/2002, numerous changes for conversion to OpenSSH 3,
protocol version 2, and DSA keys
(C) 2000, Michael Han
$Id: advanced.html,v 1.9 2003/06/28 22:01:46 mikehan Exp $