Paranoid Penguin - Managing SSH for Scripts and cron Jobs
After doing this, you will be using the proper key as intended.
Using ssh-agent is, of course, a great time saver for interactive use. When used in scripts, however, a human still needs to type in the passphrase at least once when the machine boots. This ends up being the best we can achieve with ssh-agent, even with scripts to automate most of the procedure. For more information on that topic, refer to SSH, The Secure Shell: The Definitive Guide. Ultimately, the ssh-agent option also did not meet our needs in deploying secure batch jobs, as our goal was to automate the jobs totally.
That left us with the option of using public keys without a password. The remainder of this article focuses on that setup, how to secure it further and some options to consider when using this setup. In any environment, thorough planning and review of security polices should occur before deploying or modifying security configurations.
The first method in securing our setup is to use the from= directive in the authorized_keys file. The syntax looks like this:
What this says is allow only users from host1 or host2 and authenticate them against the public key matching KEY. For example, to restrict logins from only hostA and hostB for our user scripts, the authorized_keys would look like this:
from="hostA,hostB" ssh-dss AAAAB..Aqbcw= scripts@hostA
This is by no means a foolproof restriction. As I mentioned before, it is possible to pretend to be another host and spoof an IP. But this restriction adds a layer of security and increases the effort needed to compromise our host. Notice that I intentionally shortened the key, which is quite long, due to space constraints. Be aware that the from= syntax is sensitive to short and long DNS names. HostA is not the same as HostA.somewhere.
Our second line of defense in securing our script setup is to use the command ="" directive, also specified in the authorized_keys file. The syntax for this looks like:
command ="command", KEY
This tells SSH to run command and then exit. It effectively limits your ability to run commands on the remote server. As you might expect, you can combine both of these in your authorized_keys file; simply make sure you separate the options by a comma:
from="hostA,hostB",command="/bin/df -k" ssh-dss AAAAB3N...Aqbcw= userA@hostB
Now, should someone compromise this user and key, the worst that can be done is retrieving a listing of disk space on the remote host. In fact, this is the only command you can run with this key. In order to run multiple commands securely, you have a few options. First, consider calling a script instead of command. For example, run top, df -k and hostname from a shell script named myscript.sh and set command="/path/to/myscript.sh". Second, if you need to run multiple commands at different times during the day to same host, you could create another key for your user. This time, use the -f option to specify a file other than the default:
[scripts@hostA]$ssh-keygen -t dsa -f backupkey Generating public/private dsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in backupkey. Your public key has been saved in backupkey.pub. The key fingerprint is: 14:ac:c5:5f:65:69:2f:8d:cf:0a:70:9e:5c:4e:c7:84 scripts@hostA
You would copy the contents of the new public key, backupkey.pub, to the authorized_keys file of the users on the hosts you want them to access, the same as the default key. Be sure to set the new command="" for your new key and the new command you want to run.
Now, you would use the -i parameter to make sure you're using your new key while logging in to the remote SSH server:
[scripts@hostA]$ssh -i backupkey hostB
To add another public key on a remote host without overwriting your original authorized_keys file, you can run this command:
$cat ~/.ssh/newkey.pub | ssh -l user host "cat >> .ssh/authorized_keys"
Finally, you could create a separate user for this task. For example, you could create one user to monitor disks and one to automate backups. Each configuration has its advantages and drawbacks. The question to answer here is, “Do you want to manage keys or user accounts?” I prefer to have different keys and make a note of them with a comment.
One piece of the SSH key we have not considered yet is the comment field. The default comment for userA's public key created on HOSTA is userA@HOSTA. Basically, everything after the key is ignored as a comment. So to keep track of keys and what they are used for, I make a comment in the remote server's authorized_keys file. For example:
ssh-dss AAAAB3NzaC1kc3MAAA......Jw= scripts@hostC-Key used for disk monitoring
Our third line of defense is the ability to limit the traffic we forward. We have three main options here that merit discussion. First, the no-port-forwarding option means what is says. When this key logs on, the ability to forward TCP/IP ports is denied. Forwarding ports is a great way to bypass firewalls; hence, the account used to run scripts should be given the ability to run only the commands necessary. The ability to forward TCP/IP ports is a potential security problem, so we restrict it.
Second, no-X11-forwarding tells the SSH process not to forward any X11 connections upon login. Any attempt to do so returns an error. We see that this is simply another way for an intruder to exploit our hosts, so we disable it. Again, we try to lock down what the account that logs in can do, but we also permit it to perform its job.
Third, no-agent-forwarding in the authorized_keys denies this key the ability to forward its ssh-agent and stored keys to another host. This reduces complexity and also takes away another avenue for a potential intruder to trespass.
The final option in the authorized_keys file we want to use is the no-pty option, which says not to allocate a pseudo-tty when logging in. Non-interactive commands continue to work using the associated key; however, you can no longer issue commands through an interactive session. Should an intruder gain access to your private key and somehow circumvent the other options, this option effectively ensures that he or she cannot issue interactive commands to do any damage.
With the above options in place, we have a reasonably restricted key that still can perform its job. Our final authorized_keys file looks like this:
from="hostA,hostB",command="/bin/myscript.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dss AAAAB3....o9M9qz4xqGCqGXoJw= scripts@hostA
Before we finish our discussion on options, let's look at two more that are not related directly to security. When running SSH in scripts, we use the -q and -o “BatchMode=yes” command-line options. The -q stands for quiet mode. The man page for sshd sums this up nicely: “Nothing is sent to the system log. Normally the beginning, authentication, and termination of each connection is logged.” This is useful for suppressing warnings otherwise interpreted as command output. The -o “BatchMode yes” makes sure SSH does not prompt the user. So our script changes a little more:
for host in $servers do ssh -q -o "BatchMode=yes" $host df -k done
Because we are specifying an option on the command line, we are certain the options will not be overridden as they take precedence. Typically, the global client configs are looked at first, usually /etc/ssh_config; then the local client configs, usually ~/.ssh/config; and finally the command line. As several versions and variations of SSH are available, always consult the man page for correct locations and syntax.
Ensuring proper options are set for each particular key and using a layered security approach goes a long way in making your servers less vulnerable to attacks. Setting the least privileges possible reduces the potential damage done during a successful attack. Using these methods, your data and networks become more secure and still run efficiently.
Resources for this article: /article/8400.
John Ouellette is a system administrator with nine years' experience in NT and UNIX. He believes the command line is king and loves chicken parmigiana. He can be reached at firstname.lastname@example.org.