by Steven J. Owens (unless otherwise attributed)
Generally speaking, you can use ssh to securely login to a remote host by just entering your password when prompted. However, there are times when it's useful or a good idea to set up passwordless access, by generating a set of public and private keys and putting the key files in the right places on both your client machine and the remote host.
I wrote this tutorial because I had a project that required programs to run on thirty or so different linux boxes. Part of how the project ran required programs to be compiled, copied over, started, shut down, results data copied back around, and so forth.
Therefore, I needed scripts to control all of this; to start all of the programs at once, to stop all of the programs at once, and to gather the results all back to a single directory on a single machine for processing. And for each of these steps, I needed to type in the password to ssh into the account on the machine in question. That got old fast.
Here's an outline of the process:
At the bottom, I'll have two more sections:
The easiest way to set things up was to have the list of host names in a build file, which constructs a start and stop script for each hostname, with appropriate settings for that machine.
A central startall.sh and stopall.sh each contain a list of ssh commands. Each command in the list uses ssh to login to some machine and run the individual scripts for that machine (e.g. start_foo.sh, start_bar.sh, etc.).
The central startall.sh script is pretty straightforward:
#!/bin/bash ssh foo.example.com /home/username/myproject/start_foo.sh & ssh bar.example.com /home/username/myproject/start_bar.sh & ssh bat.example.com /home/username/myproject/start_bat.sh & ...
This variation on the ssh command tells ssh (in the first line) to log into the machine foo.example.com and run the command /home/username/myproject/startfoo.sh.
The & at the end runs that entire line (the whole command, including ssh) in the background, so it doesn't have to wait for start_foo.sh to complete and exit before it invokes start_bar.sh.
The stopall.sh script is similar.
However, with this approach ssh still prompts me for the password - for each of the 30 ssh commands! This is a pain, so I set up ssh key-based, passwordless logins.
There will be two accounts in this example:
We are setting up ssh so you can log into frodo@shire normally, with a password, and then do "ssh frodo@mordor" and immediately log into frodo@mordor without any password. (Or so you can do "ssh frodo@mordor somecommand.sh" without typing a password.)
Note that we're setting up so frodo@shire (local) can get into frodo@mordor (remote), but that doesn't mean frodo@mordor can get into frodo@shire. To make it work both ways, just repeat the process with the local and remote accounts swapped.
This tutorial will also cover how to set up ssh-agent with linux:
Your private ssh key has to be stored securely, otherwise anybody could just get into your box and then use your private key to get into the rest of your machines. To secure your private key, your private key itself is stored in an old-fashioned password-encrypted file -- actually it's a pass phrase, it can be and is recommended to be much longer than a password. When you run the command, you're prompted for the passphrase to decrypt your key file.
Since typing the passphrase for your private key file every time you use ssh to login to another box is just as much hassle as typing your remote account password, ssh supports using a tool called ssh-agent. Once you give ssh-agent the passphrase for your key file the first time, when you run ssh again later, ssh will note the ssh-agent environment variable and instead of prompting you for the keystore passphrase, ssh will ask ssh-agent for the already-decrypted private key. This is fairly secure.
You don't need to do this step if you have already generated a public key. If you want to generate your public key again, that's okay as long as you haven't used the existing public key to encrypt anything or sign anything or set up any other remote access. If so, you'll need to either use your existing public key, or figure out how to juggle public keys, which is beyond the scope of this document.
First, log into frodo@shire:
Next we run ssh-keygen with:
a) the "-t typename" argument specifying type "dsa",
b) when prompted, pressing enter to select the default file to save the key in, and
c) for the passphrase, entering a phrase or short sentence that you will be able to remember.
Here's an entire example, which I'll then dissect further below:
frodo@shire:~$ ssh-keygen -t dsa Generating public/private dsa key pair. Enter file in which to save the key (/home/frodo/.ssh/id_dsa):<enter> Created directory '/home/frodo/.ssh'. Enter passphrase (empty for no passphrase): examplepassphrase<enter> Enter same passphrase again: examplepassphrase<enter> Your identification has been saved in /home/frodo/.ssh/id_dsa. Your public key has been saved in /home/frodo/.ssh/id_dsa.pub. The key fingerprint is: 4f:58:b3:a5:76:16:41:11:d1:c9:96:6e:54:26:c3:22 user@shire frodo@shire:~$
Let's start with the first line:
frodo@shire:~$ ssh-keygen -t dsa
See "man ssh-keygen" for more info, but basically your types can be "rsa1", "rsa" or "dsa". rsa1 is rsa for first-generation ssh. Second-generation ssh is generally recommended. Second-generation "dsa" seems to be what most people recommend.
Generating public/private dsa key pair. Enter file in which to save the key (/home/frodo/.ssh/id_dsa):<enter> Created directory '/home/frodo/.ssh'.
You do not need to create a ~/.ssh directory. If you don't have one, ssh-keygen will create one.
You do not need to do anything about your existing .ssh directory... unless you need to keep the existing ssh key. If you have an existing ssh key of that type, ssh-keygen will ask you if you want to overwrite it. Don't overwrite if you're already using your existing public key for encrypting stuff, signing stuff or have set up your public key on a remote system. Again, figuring out how to keep your old ssh key and juggle the ssh keys is outside the scope of this document.
ssh-keygen will generate the key file. See the example below. On most systems this should happen immediately, but if your system is under heavy load, it may take a few seconds.
When prompted for where to save the generated key, press the <enter> key to take the default.
Enter passphrase (empty for no passphrase): examplepassphrase<enter> Enter same passphrase again: examplepassphrase<enter>
When prompted for a passphrase, enter a fairly long phrase that you can remember.
The passphrase will be used to encrypt your saved keys. The passphrase should be significantly longer and harder to guess than a normal password.
The classic example is the first line of Coleridge's poem, Xanadu:
"In Xanadu did Kublai Khan a stately pleasure dome decree"
Of course, the first time somebody used that as an example, the next day 20,000 users entered that as their passphrase. So don't use that.
Using a blank passphrase is, of course, generally regarded as stupid, since anybody can then copy your private key. Any purpose for which you think you need a blank passphrase is better done using ssh-agent (see below).
Your identification has been saved in /home/frodo/.ssh/id_dsa. Your public key has been saved in /home/frodo/.ssh/id_dsa.pub. The key fingerprint is: 4f:58:b3:a5:76:16:41:11:d1:c9:96:6e:54:26:c3:22 user@shire frodo@shire:~$
The key fingerprint that is printed is intended to be a more (relatively) human-readable checksum for occasions when you have to manually key in the public key, and need to make certain you did so correctly.
Check to see that the key file is now in ~/.ssh:
frodo@shire:~$ ls -l .ssh total 8 -rw------- 1 frodo frodo 668 Feb 4 21:36 id_dsa -rw-r--r-- 1 frodo frodo 612 Feb 4 21:36 id_dsa.pub frodo@shire:~$
There's plenty of other stuff about setting up ssh out there, but I'm including this bit here just so you don't have to go look for it elsewhere.
After you run keygen, you'll find two files in ~/.ssh, named id_dsa and id_dsa_pub. Here's an example id_dsa_pub:
ssh-dss AAAAB3NzaC1kc3MAAACBAOxroI5MRtohI8gWtpEM/+wsY2W8qtpLnPKr1sAMjxZBqb4vmXtJ SQDiVS2+RS6JaUK3xVE+9QMkyiIxPIlO+oXju4ow5s+Qew2vm6Chas2kZDPDvMOcRQMgE193l9gxh1hc HuX8ndXftemWfUKRIyHh+xMMgeTZHNTdlh6xYXSvAAAAFQDzVEYFC0rrCxCeT7CvkpUByyTYeQAAAIAN SOwEKts5qGFw408NlJGVILa2IbBaoFE8aS2iOO28cf4WgLqbRzQw8bYhrnFiyMQ5P+bXQSckTQtSLe1/ h3Iz/vJt4gFEvWgL3rEwGExMmt/eQpgHRjeZXd93YH4Pj2PsY8XXQFOP8q9vsd1DJNjbAfV0fTSn6qif L9RLLVf+UwAAAIAuUCehwILoFIK/lIUFgRxPwSBtUmxH4MoaTHa2UvwetWNCV9x2TZdJxTPWrku6rKBp jJiFndkyHhCKhESu0x9Ut8w4qWqc5u2NGmTCQ1DRcSoVpkVnov77IezKXnpGmLdsEKab/r4LYjQYn2T1 wJe6Hy0WrzRqPox0wxpfEHUqiA== frodo@shire
Specifically, now you have to get the contents of this file:
Into this file:
If you're setting up a really secure setup, you're going to need to manually re-key the public key instead of sending it over a potentially compromised network connection, even using another, already existing public key, because that might potentially be compromised.
However, odds are you're not setting up a really secure network, or that if you are, you're too lazy to really set it up securely by doing it the hard way and manually re-keying. So there is a simple utility, named ssh-installkeys, written by Eric Raymond, in Python. ssh-installkeys makes it easier to create and copy the ssh keys to the right place in the remote account (~/.ssh/authorized_keys).
Here's the brute-force obvious, insecure way to get ssh-installkeys. Of course, any real security paranoid would immediately worry about whether or not the ssh-installkeys code downloaded is compromised, or about whether your local python binaries are compromised. But we're being lax today, so:
frodo@shire:~$ wget http://www.catb.org/~esr/ssh-installkeys/ssh-installkeys-1.4.tar.gz frodo@shire:~$ gunzip ssh-installkeys-1.4.tar.gz frodo@shire:~$ tar -xf ssh-installkeys-1.4.tar frodo@shire:~$ cd ssh-installkeys-1.4
However, before you actually run ssh-installkeys, see step 3.
Note: Since I wrote this, this detail may have been fixed already.
For some systems, you need to make sure that the remote file containining the public key has the correct permissions. If it doesn't, ssh will prompt you for a password even if the public key is correct and in the right place.
I'm not sure which systems have this requirement, nor how to check yours, other than actually setting it up and trying it. But if your system requires g-w permissions on authorized_keys, you can do it manually or you can edit the ssh-installkeys script to have it do it for you. Near the end of the ssh-installkeys script file you will find this line:
Immediately afterward, insert the following line:
session.ssh("/bin/chmod u+r,g-w,o-w ~/.ssh/authorized_keys")
The entire paragraph should look like this:
# OK, install keys remotely if they don't exist if not check and not session.exists("~/.ssh/authorized_keys"): session.scp(keyfile, "~/.ssh/authorized_keys") session.ssh("/bin/chmod u+r,g-w,o-w ~/.ssh/authorized_keys") except SystemExit:
Python uses indentation to indicate block structure, so make certain that the chmod line is indented with spaces to the exact same indentation as the scp line, so python will know they're both in the same block.
Run ssh-installkeys with the remote username frodo and hostname mordor:
frodo@shire:~$ ./ssh-installkeys frodo@mordor Checking your local configuration... I see the following public keys: id_dsa.pub id_dsa corresponding to id_dsa.pub exists and is readable. Local configuration looks correct. Starting session to mordor... Remote password: Remote ~ permissions are drwx------ which is OK. Remote ~/.ssh doesn't exist. Creating remote ~/.ssh Remote ~/.ssh permissions are drwxrwxr-x which is wrong. User read should be on, Group and Other write permissions should be off. Correcting permissions... Keys for frodo on shire have been installed. frodo@shire:~$
Even though you have a public/private key pair set up, the private key is encrypted with your passphrase. You could simply not use a passphrase if the situation is otherwise secure enough, but in general usage, this is considered stupid. Instead, use ssh-agent.
ssh-agent acts as a sort of key server; to be specific, ssh-agent doesn't serve your actual private key, it serves operations that can only be done with a copy of the key. When ssh wants to do something that requires your private key, it tries to get ssh-agent to do it first. If it can't, then it asks you for your passphrase so it can decrypt your private key.
So ssh-agent never hands out your private key, and your private key file is never in an unencrypted state. There's still a potential that somebody could run an evil program that scans memory to get your private key. Some people set ssh-agent to timeout the private key every 5 or 10 minutes.
There are certain normal ways ssh-agent is used (see below), but here's how I set it up for quick and dirty use. I created a simple bash script:
#!/bin/bash ssh-agent sh -c 'ssh-add < /dev/null && bash'
This starts ssh-agent and tells it to run a shell and in that shell run the command ssh-add. ssh-add prompts me for my passphrase and uses that to decrypt my private key and load it into the ssh-agent daemon. Then ssh-agent starts a bash shell process, and the new bash shell inherits the SSH_AUTH_SOCK environment variable from the ssh-agent process.
Any subsequent processes I start from that bash shell will also inherit the SSH_AUTH_SOCK. When I ultimately run ssh, the ssh process will also inherit SSH_AUTH_SOCK and will use the socket value from the environment variable to ask the ssh-agent process for help logging me into the remote system.
I later figured out the right way to set it up for bash shell. I simply added the following line to the end of my .bash_profile file:
Then, after I logged out and logged back in, my initial bash shell had the SSH_AUTH_SOCK environment variable and the rest of my processes inherited it.
The ssh commands in your scripts have to use the same sort of public/private keys as you've generated, so make sure you are. Generally this means not specifying -1 on your ssh command, unless you generated your ssh keys using rsa1.
Here are three somewhat useful URLs that I read when putting this all together:
These next three URLs were extremely helpful, but of course only came up in google hits after I learned enough to ask the right questions. Note that the second paragraph of the snailbook about-agent.auto.html pagef contradicts the developerworks page. I'm kinda leanings towards the snailbook's explanation, mainly because that sounds like just the sort of twisty thing that cryptogeeks would do.
However, the developerworks URL is handy because it discusses keychain, which I haven't otherwise dealt with.
This is actually not that tricky once you understand it, but it's hard to explain clearly. See the URLs above. Instead of describing what could or couldn't happen, I'll describe what happens when everything goes right.
Note: if you run ssh and ssh-agent does NOT have your private key loaded via ssh-add already, ssh will prompt you for your passphrase but WILL NOT load your private key into ssh-agent.
Starting ssh-agent Properly
When you run ssh-agent, you run it with the eval command:
frodo@shire:~$ eval `ssh-agent`
This results in your process taking whatever output ssh-agent produces and executing them as shell commands. This output includes commands to set the SSH_AUTH_SOCK environment variable in your process. Now any further processes you spawn will inherit the SSH_AUTH_SOCK environment variable, and know how to talk to the ssh-agent daemon.
Starting ssh-agent Properly In Xwindows
In normal usage, usually the "eval `ssh-agent`" command is put in your .xinitrc so it gets started during Xwindows startup, and in turn the rest of the Xwindows related processes inherit the SSH_AUTH_SOCK environment variable.
Starting ssh-agent Properly In Bash Shell
Put the "eval `ssh-agent`" line in ~/.bash_profile, and then when you log in, the first thing you do is run ssh-add.