General SSH Functionality
SSH is one of the most important tools for serious networked computing. It stands for "Secure SHell" and is a secure upgrade from the ancient unprotected rsh (Remote SHell). While it is mostly used by people to "log in" to another machine, what is really happening is that the other machine’s is running its shell with output going to the SSH process which then forwards it somewhere else. Most people don’t realize that the core element of SSH is not the shell, but rather this remote forwarding. SSH runs a text shell (Bash usually) by default, but it can actually run anything.
Here I ping a remote host, xed.ch and the time is about 24ms. But I then use ssh to run the same command on that host and the time for this machine to basically ping itself is 300 times faster.
$ H=xed.ch $ ping -c1 $H | grep icmp_ 64 bytes from xed.ch (111.222.222.111): icmp_seq=1 ttl=47 time=23.7 ms $ ssh $H ping -c1 $H | grep icmp_ 64 bytes from xed.ch (111.222.222.111): icmp_req=1 ttl=64 time=0.079 ms
Even though I’m sitting at the same computer and pinging the same computer, the SSH version of the command simulates me actually being present at the remote computer with input and output tunneled over a secure connection.
To "log in" to remote machines the normal command is something like this:
$ ssh remote.example.com
If the login name on the remote system is different (you’re logged in as cxedwards here but need to log in as chris there) you can specify the correct login name to use like this:
$ ssh -l chris remote.example.com
A common short cut for this option is the following format:
$ ssh chris@remote.example.com
Keys
General Strategy
SSH is a public key cryptographic system. This means that it does not use a symmetrical "shared secret", i.e. the password I encode with may not be the password you decode with. Instead it generates a key pair, one private and one public. The private key is very important and must be very closely guarded. To let someone have access to it is to improve their odds of breaking into what it protects substantially. The public key is not sensitive information and that can be freely distributed without concern.
Let’s say you have a host called house and another called office. You want to work from home which entails logging into office from house. On house generate a key pair. Create a file on office called ~/.ssh/authorized_keys which contains the public key from house. This file can contain several public keys, one per line. It’s fine to edit it manually by cutting and pasting or any way you can.
Note the permissions section below to sort out any permissions issues.
Assuming your key is named housekey, and the contents of housekey.pub are in office:~/.ssh/authorized_keys, you should be able to log into office from house like this:
$ ssh -i ~/.ssh/housekey office
Key Generation
Generally just use the command ssh-keygen. It will prompt for the name which should include the full path or else it will use $PWD. This name will be the name of the private key. The public key will also be generated and be the same name with a .pub added to it. It will then ask you for the passphrase.
Here’s a good command line way to generate keys with minimal questions:
$ ssh-keygen -C "Xed General Purpose" -f ~/.ssh/xedgen
If you don’t provide the comment (-C) it defaults to $USER@$HOSTNAME.
Using Keys To Work With A Cluster
If you ever work with a cluster, it is common that each node on the cluster will have a common shared file server where a user’s home directory is stored. This means that a file changed on node n12 will also be changed on node n17. Often it is useful to set up a key pair with no password so that you can easily control things on any node of the cluster. Since each node is probably on a local network (i.e. not available to the general internet) the security issues are less problematic. Also, if any node is compromised, with a shared file system all nodes are then compromised regardless of what key pairs are lying around.
To set this up is relatively easy since your home and office hosts as used in the previous examples are basically the same machine as far as setup goes. Perhaps the easiest way is to just run:
$ ssh-keygen
And then accept defaults and when it asks for a password, just hit enter (it will ask for confirmation - hit enter again). Then it will create the key pair in ~/.ssh. You can ls -l ~/.ssh to have a look. Now that you have a key pair you can use, you have to tell your account (which is replicated on all the nodes) that you will accept this key pair for log in attempts. A good way to do that would be:
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
If you had an authorized_keys file you have now just appended the contents of your new public key to it (this example assumes that the new public key was ~/.ssh/id_rsa.pub, but it could be ~/.ssh/id_dsa.pub or some other name you gave it).
Note the permissions section below to sort out any permissions issues.
Once the keys are set up and have the correct permissions, you should be able to log into another machine without a password. Just
$ ssh node22
If node22 has the same home directory source, it should find your authorized_keys file there (even though you set it up "here") and it should log in nicely. If there are problems, try using the -v option. This always is a helpful first step to diagnosing SSH key problems.
Key Permissions
Often there are problems with permissions. Permissions are important because you don’t want all this security only to have some one else log into the system and just read your ssh set up and learn what strategies would best be used to attack your account. This is why permissions can be fussy.
Note that the authorized_keys file should have no permissions for group or other or it will not work. Do this to make sure:
$ chmod 600 ~/.ssh/authorized_keys
You also have to have private keys be private for SSH to take them seriously:
$ chmod 600 ~/.ssh/id_rsa
The public keys can be any way you like; they’re public after all:
$ chmod 644 ~/.ssh/id_rsa.pub
I also think it’s a good idea to do the following:
$ chmod 700 ~/.ssh
This limits everybody but you and the SSH server from seeing how your security is configured.
Changing Passphrase
If your private key is encrypted with a passphrase (a very good idea), here is how you change it:
$ ssh-keygen -p -f ~/.ssh/superkey
Creating Special Key Pairs With Limited Functionality
Here is how to limit what a key can do by origin and by function:
from="ok.xed.ch",command="/bin/tcsh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAA......keynoisehere.......WHB73nQ== user@example.com
Just manually edit your authorized_keys file and include the keys you want to authorize with any restriction prefixes.
Note that the command= directive won’t block a different command, but rather it will just run the command specified in the authorized_keys file. So if you have this key in host alpha:.ssh/authorized_keys:
command="cal" ssh-rsa AAA........keynoisehere.....WHB73nQ== user@example.com
And do this from beta:
ssh alpha.example.com ls
You’ll get the results of cal. In other words, instead of simply not working, it works differently than you explicitly commanded it to.
Specifiying Commands
How do you know what command is really being run remotely? Have SSH monitor it and report it back to you. To do this use a program like this:
#!/bin/sh # test_ssh_cmd # Put this in front of the key of interest in .ssh/authorized_keys: # command="/fullpath/test_ssh_cmd" ssh-rsa AAAAB3.....x4sBbn62w6sISw== xed@xedshost # Then check the contents of sshcmdlog for what ssh really saw. That # is now usable in a command= directive to limit the activity to what # you really intend. echo "$SSH_ORIGINAL_COMMAND" >> sshcmdlog exec $SSH_ORIGINAL_COMMAND
This is useful if you’re trying to set up a key that only allows a specific rsync job. Using test_ssh_cmd, you can run your rsync command and see that it really runs on the sshd server like this:
rsync --server -vnlogDtpr --delete-during --partial .
Then you can clip that out and put it verbatim into a command="" specifier and it should work. To otherwise just guess this can be difficult.
Key Agents
General
Private keys that are used interactively should always be protected with a passphrase. However, it’s reasonable to decrypt the passphrase and leave it available in memory for the duration of a session you might want to use it. This allows you to use the key without entering a passphrase every time. This is accomplished with a key agent. This is a special program that SSH knows how to consult with to see if any of the encrypted keys it needs to use have been decrypted recently and loaded in memory. If so, it just uses it again.
gnome-keyring-daemon
I don’t know the details, but it looks like by default Ubuntu (and probably other distributions' display managers) uses a Gnome key manager that seems pretty easy to use. Basically when you need a private key decrypted, a window pops up and offers you the chance to load it into its manager. Then subsequent usage of this key will use it from the key manager’s copy and not bug you for the decryption passphrase.
ssh-agent
Sometimes you don’t have a GUI and a fancy integrated keyring manager won’t be possible. If you’re trying to get a key agent working with text console logins you have to resort to the standard key agent. The classic key agent is called ssh-agent. It’s kind of an unusual program. It basically needs to be the top level parent process of all the processes that might be running that would require its services. This can be a pain if you’re already running a shell that requires it and the agent wasn’t set up to be a parent process. So you have to plan in advance. This is done by running it and then running your shell as a sub process.
Sub-Process Method
The most basic way to have a process (and its children) know about the key-agent is to make that process a subprocess of the agent. Here are some likely examples of how you’d use it in a simple way:
$ ssh-agent xterm & $ ssh-agent tcsh $ ssh-agent screen
Here’s an example I use:
alias sx='( nohup ssh-agent startx & ) ; clear; exit'
This alias is useful if you often use a computer without graphics, but when you do want graphics, this starts it up and makes the entire X Windows session a sub process of the ssh-agent. This means that whatever xterm you might have open, all of it is in touch with the key-agent. (Note that this alias also cleanly shuts down the tty console you were using which I think is a nice security feature so that people can’t Ctrl-Alt-F1 into the shell you started X from when you’ve locked the screen.)
Modify Current Environment Method
Basically the agent works by exporting some important ssh-agent environment variables to the subprocess. You can also just have ssh-agent generate the shell commands to establish the current shell as an environment that knows about the agent. To do this, run (only one of these):
$ eval `ssh-agent -s` # In bash.
or
> eval `ssh-agent -c` # In csh.
After this is done the environment variables should be in place for ssh clients to know how to use the agent.
These are untested, but seem reasonable for getting a key agent environment set up automatically from a .bashrc (in Bash):
SSHAGENT=/usr/bin/ssh-agent
SSHAGENTARGS="-s"
if [ -z "$SSH_AUTH_SOCK" -a -x "$SSHAGENT" ]; then
eval `$SSHAGENT $SSHAGENTARGS`
trap "kill $SSH_AGENT_PID" 0
fi
Having this in bash allows the new shells to find the agent. To start the agent the first time, use something like this:
$ eval `ssh-agent`
keychain
You can also install the program "keychain" which manages a lot of this mess. Apparently, you run the program:
$ keychain
And it creates files in a directory .keychain:
$ keychain KeyChain 2.6.8; http://www.gentoo.org/proj/en/keychain/ Copyright 2002-2004 Gentoo Foundation; Distributed under the GPL * Initializing /home/xed/.keychain/office.example.com-sh file... * Initializing /home/xed/.keychain/office.example.com-csh file... * Initializing /home/xed/.keychain/office.example.com-fish file... * Starting ssh-agent
Now you just have to source one of the files in ~/.keychain:
$ source ~/.keychain/office.example.com-sh
or
$ source ~/.keychain/office.example.com-csh
Now when you log in from somewhere else, you can use the same key agent by sourcing this keychain script.
To stop a keychain spawned agent (and get rid of the .keychain scripts):
$ keychain -k all
Agent Forwarding
Having an agent keep your keys in memory is cool, but sometimes you need something fancier. You’re on hostA and you need to log in to hostC from hostB. This could happen, for example, if you’re at home on hostA and hostB is the secure gateway that lets you in through the firewall to hostC. I also use this setup to have a system administration machine (hostB) at work that controls various machines (hostC) from home (hostA).
The first thing to check is that hostB allows agent forwarding.
$ sudo grep AllowAgentForwarding /etc/ssh/sshd_config AllowAgentForwarding yes
If not, reset this to yes and restart you ssh server. Once this is enabled, hostB pretends to have an agent running even when it doesn’t. When you ask the agent for keys, it basically forwards this request over the already established connection to hostA. Apparently if hostB is messy with untrusted activity, it might be best not to do this since an attacker can hijack all your loaded keys if there is a compromise. Otherwise I think it’s pretty safe.
Also your client on hostA needs to be cool about giving the agent information to your connections that ask for it. You need to set this on hostA:
[hostA]$ grep -B1 ForwardAgent /etc/ssh/ssh_config Host * ForwardAgent yes
Note that you can do this per host so that only certain hosts get the forwarded keys stored in the agent. Shown above is how to do it for all hosts. You can also do it on the command line using:
[hostA]$ ssh -o ForwardAgent=yes hostB
To see if things have a shot at working you can use this command to see what keys are loaded on hostA:
ssh-add -l
Use -L to see the full keys. If hostA shows you some keys, then you know your agent is working at least. Next run that same command on hostB. You should see the exact same keys available. If in doubt, you can delete a key from the list back on hostA and check to see if that did the same thing on hostB. Use this to remove a key from the agent’s control:
ssh-add -d hostC_rsa
Once you have keys loaded in an agent, real or forwarded from somewhere else, you should be able to proceed as if it were a simple A to B connection.
|
Note
|
If you’re using screen or tmux then you might need to restart the whole session if you’re just turning on agent forwarding. At the very least, do initial testing without this complication. In fact, screen is tricky. Here’s a good discussion of the situation. I was able to just set the environment variables explicitly in the screen session that existed in the normal login. Something like: |
export SSH_AUTH_SOCK=/tmp/ssh-ilYGf77777/agent.17777
Host Keys
Host keys are not the same thing as the keys discussed above. What host keys do is ensure that when you contact a machine, it really is the machine you intended to contact. The technology is similar - the server has a private key and you have a public key for that server. When you contact it, you ask it to authenticate itself as the machine you really want and if it really is that machine, it uses its private key to create a message that could only be created by that key. If your version of the public key can decode the message (this all happens automatically - you never read the message per se) then you are well assured that you are talking to the machine you think you are.
Imagine it from another point of view. Let’s say you’d like to steal some log in credentials from ssh users. You figure out how to interject your server on the network in such a way that when users try to log into the official server, they actually get to yours. But just as the server asks the user to authenticate, the user should ask for confirmation that the server is still good.
Now, back on earth, it’s really pretty difficult to pull off this kind of attack even without identification verification of the host key. But if this mechanism were not there, bad people would focus on the resulting weakness, so we need to take care of it. So that’s what host keys are all about.
When you get an SSH session complaining about host keys the odds are overwhelming that it’s because of some kind of misconfiguration or legitimate situation. The two most common situations are when you’ve never logged into that server before (this mechanism is completely ineffective for first time log ins) and when the operating system has been reinstalled. In the latter case, the cryptographic keys are generated anew with the new OS installation. Upgrading your SSH could conceivably cause this too.
In most cases, the situation is that during first time log in, SSH asks you if you are confident that the host is not bogus. Here I show this process.
$ ssh a.xed.ch date The authenticity of host 'a.xed.ch (137.110.243.111)' can't be established. RSA key fingerprint is 5e:bd:a3:e0:31:d6:9e:58:a1:69:67:3d:9d:2b:39:df. Are you sure you want to continue connecting (yes/no)? y Please type 'yes' or 'no': yes Warning: Permanently added 'a.xed.ch,137.110.243.111' (RSA) to the list of known hosts. Password: Tue Aug 7 17:52:19 PDT 2012 $ cat ~/.ssh/known_hosts a.xed.ch,10.0.0.111 ssh-rsa AAAAB3NzaC1lp2RNNNNOVjNNNDRNcJNPun43a3AuCMg21JCjWiXZbRSu2zql8bJ iM7behoN9Tvo7NKe9xbn6DH+yWv6jELJWBJ7Hzwmeiwxmfd34QtRb2ZZwGOKV5xAV7ajqtdk10T4/o24BoG9hdB2nNh hnuYUp9k7gO8EDacz6sVUvQaXFRmZ3HBkINKANV2xgSsG19ZUv3AjUvwV5xLdSQX+Pu/k3kf0XjLCGxWKl1dQVYga0t aXHEw8tYiQJiJNcwpDRGqDCm2XpVPZO73ov8RrSmmIT9+UyFJ4CdTo5EPFbT2qWAKmjtb6KekTvOTHd4JmOL8fk62Fy cAdvXb9Ov4TO3J8JafnpwlJvfrfzpwqCBD==
Notice that you need to type "yes" to have it accept. Then the example shows the host key in the file ~/.ssh/known_hosts, in other words this is a list of hosts you know and trust.
Let’s say I go messing with the known_hosts file and I make a change to the host key for this host. I would see a message like this:
$ ssh a.xed.ch @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that the RSA host key has just been changed. The fingerprint for the RSA key sent by the remote host is 08:74:6b:90:49:39:3b:28:7f:4b:84:41:b2:14:83:38. Please contact your system administrator. Add correct host key in /home/xed/.ssh/known_hosts to get rid of this message. Offending key in /home/xed/.ssh/known_hosts:12 RSA host key for a.xed.ch has changed and you have requested strict checking. Host key verification failed.
Generally the best way to handle this is to edit the known_hosts file and delete the offending key. This can be tricky with SSH configurations that don’t easily identify the hosts with the key. But the clue is where it says Offending key in.... That line ends in a 12 and that’s the line number of the file where that key is. The way I handle it is to type vim ~/.ssh/known_hosts +12 and then once that loads type dd to delete that line and then ZZ to quit and save. You can use your own editor or an in line sed edit:
`sed -i 12d ~/.ssh/known_hosts`
Because host keys problems are so irksome to me, I wrote a little script that very aggressively cures them.
#!/bin/bash # Chris X Edwards - xed.ch - Sat Jun 26 2010 # "Serious SSH". Curing host key annoyances. # Don't you hate it when you try to use ssh and it balks because the # host key has changed? Well I often do a lot of things on a lot of # machines that cause the host key to change (reinstall OSes for # example). When you are using ssh amongst a large collection of # machines, this can be quite a pain. Also since ssh saves and checks # host keys for various ways to specify the same machine, it really # gets irritating. # This program is designed so that the user can re-run the failed ssh # command with `sssh [sshopts] [user@]host [cmd [cmdopts]]` and it # will then parse out the host, figure out any other likely names # associated with that host, and then rerun the ssh command (in a way # that accepts the new host key). # EXAMPLE: ## :-> [swan][~]$ ssh ostester ## Warning: the RSA host key for 'ostester' differs from the key for the IP address '10.0.133.157' ## Offending key for IP in /home/xed/.ssh/known_hosts:25 ## Matching host key in /home/xed/.ssh/known_hosts:53 ## Are you sure you want to continue connecting (yes/no)? ^C ## :-< [swan][~]$ sssh ostester ## KILLING KEY FOR: ostester ## /home/xed/.ssh/known_hosts updated. ## Original contents retained as /home/xed/.ssh/known_hosts.old ## KILLING KEY FOR: 10.0.133.157 ## /home/xed/.ssh/known_hosts updated. ## Original contents retained as /home/xed/.ssh/known_hosts.old ## KILLING KEY FOR: ostester.xed.ch ## /home/xed/.ssh/known_hosts updated. ## Original contents retained as /home/xed/.ssh/known_hosts.old ## RUNNING THIS SSH COMMAND: ## ssh -o 'StrictHostKeyChecking no' ostester ## Warning: Permanently added 'ostester,10.0.133.157' (RSA) to the list of known hosts. ## xed@ostester's password: ## Last login: Sat Jun 26 23:17:10 2010 from netblock-72-25-108-23.dslextreme.com ## :-> [ostester.xed.ch][~]$ # What this program does: # Step ONE - parse the ssh command line and extract the host # Step TWO - find all other ways to address this host # Step THREE - delete host keys for all hostnames found # Step FOUR - rerun the command adding the flag to accept new host key # Returns first word in a list: "returnsthis notthis orthis orthis" function firstword { if [ -z "$1" ]; then return 1; fi echo $1 } # End function firstword. # Step TWO function find_host_aliases { HH=$1 #ETCHOSTS=`grep " $HH " /etc/hosts` ETCHOSTS=`grep "\(^$HH \| $HH \)" /etc/hosts` if [ "$ETCHOSTS" ] then IP=`echo $ETCHOSTS | awk '{print $1}'` ALIST=`echo $ETCHOSTS | sed "s/$IP *//"` if [ "$IP" == "$HH" ]; then IP="" ; fi for A in $ALIST do if [ "$A" != "$HH" ] then ALIASES="$ALIASES $A" fi done else IP=`host "$HH" | sed -n 's/.*has address //p'` if [ $IP ]; then ALIASES=`nslookup "$IP" | sed -n 's/.*name = \(.*\).$/\1/p'` if [ "$ALIASES" == "$HH" ]; then ALIASES=""; fi fi fi echo $HH $IP $ALIASES } # End function find_host_aliases. # Step ONE # The whole point here is to reclaim the option string for ssh options # only and rebuild it to use when resubmitting the ssh command. while getopts "1246AaCfgKkMNnqsTtVvXxYyb:c:D:e:F:i:L:l:m:O:o:p:R:S:w:" OPT do case ${OPT} in 1 | 2 | 4 | 6 | A | a | C | f) OL="$OL -$OPT";; g | K | k | M | N | n | q | s) OL="$OL -$OPT";; T | t | V | v | X | x | Y | y) OL="$OL -$OPT";; b | c | D | e | F | i | L | l) OL="$OL -$OPT $OPTARG";; m | O | o | p | R | S | w) OL="$OL -$OPT $OPTARG";; esac done shift $(( $OPTIND -1 )) CMD=$@ POSSIBLEHOST=`firstword $CMD` if [[ "$POSSIBLEHOST" =~ "@" ]] then H=${POSSIBLEHOST##*@} else H=${POSSIBLEHOST} fi UHOST=${POSSIBLEHOST} shift CMD=$@ if [ -z "$H" ]; then echo "ERROR: Can not figure out host."; exit; fi shift # Step THREE for HA in `find_host_aliases $H` do echo KILLING KEY FOR: $HA ssh-keygen -R $HA -f $HOME/.ssh/known_hosts done # Step FOUR echo RUNNING THIS SSH COMMAND: SSHCMD="ssh $OL -o 'StrictHostKeyChecking no' $UHOST $CMD" echo $SSHCMD eval $SSHCMD
The Pain Of Host Keys On Read Only File Systems
Now for a very unusual but very irritating problem. Let’s say that for some crazy reason, you want to have a home directory that is not writable. This means that the normal place (~/.ssh/known_hosts) can’t accumulate host keys.
There are some things that can be tried. If you are the admin for a bunch of machines (maybe a compute cluster) you can think about adding all the nodes' host keys to /etc/ssh/ssh_known_hosts which contains a system wide definition of known host keys. This relieves users of having to verify them.
Another possibility that might be even easier is this option:
ssh -o StrictHostKeyChecking=no newhost.xed.ch
That’s a lot to type and I’ve had trouble with it believing my intentions sometimes.
Another trick is to collect the host keys in a non-typical place and put them in the right place some other way (elsewhere maybe). To do that use this option:
$ ssh -o UserKnownHostsFile=/tmp/a_temp_known_hosts_file example.xed.ch
Hashed Host Keys
Sometimes you’re trying to sort things out with the known host keys and you find something like this:
|1|ob6ehezy0ZtO5CZQ3YRHABNH890=|NfJD9i/tBGTGgGDgm2VaCP7ktK0= ssh-rsa AAAAB 333aC1yc2EAAANOVjNNNDRNiZth9r8wWJ4hrfGbWPwfWkGnTPWiXV0JNA6VWwqhT3mdZgfAaA6 3NVJR4qbNsmxgdk3++ADHIb7k9dfogkAClieGcNX6p83sfc174wwho7QkzCyuUYkrBWGGyNW+X +/D38Ow/kbiqSNTrm0G1WsaP2YpoExV4iEyHzQzCRYBeMmx+PvpLW42c0a51uWb2OewTdHAqsk KfiR2RlY6UCa4jtMI/Dw97q3s27WVgBkphEIxwnwj/0NVVO11TzPf/SWlAT0gGj/YieBXS//Fg mPsvnYWU5Ad5eVKtZ2f4cKH13bIThH2u6wAuQBHYG2a9lQfcKCe6Bmp7nMwwwz6Bq7OsD==
This is a hashed known host key. The idea here is that if an attacker compromises an account, the first thing they will attempt is to look at your known hosts file for a record of exactly where else you like to log in. Then they’ll try those credentials that got them into this account on those accounts. This is how SSH viruses propagate. By hashing the host key, you have enough information for verifying a host, but not enough to propagate an attack to other machines SSH is known to have contacted. That said, this is a huge pain to deal with in many cases. The way to turn off (or on) hashed host keys is to set the option:
ssh -o HashKnownHosts=yes example.xed.ch
To fix this permanently, add this option to /etc/ssh/ssh_config. It’s off by default (if not configured) though configured on by default in some distributions.
Also take a look at the -F option of ssh-keygen to deal with unruly hashed keys. If the idea of hashed host keys sounds great to you you can convert a plain host key file with ssh-keygen -H filename.