Optimizing OpenSSH

From linsec.ca
Jump to: navigation, search

NOTE: This section was originally written by Vincent Danen as a piece for the MandrakeSecure website ("Making the Most of OpenSSH").

OpenSSH is a secure replacement for deprecated protocols such as telnet and rsh. It has become a De-facto standard as a remote login service for Linux, BSD, and other *nix variants for quite a while. It was based on the last free version of the original SSH (version 1.2.12) and is available under the BSD license. Because SSH became a "for-profit" program, OpenSSH has been rapidly gaining ground as a fully-functional replacement to the commercial SSH since it's conception.

One of the problems with OpenSSH, however, is it's excess complexity. OpenSSH offers too many options, in both the client and the server, and these options make OpenSSH complicated to use and allows bugs to creep in. While OpenSSH is supposed to be a security tool, it has had a very patchy security history itself. There have been eight security-related advisories for OpenSSH from MandrakeSoft since October 2000. Does this make using OpenSSH inadvisable? Absolutely not. The possibility of a remote hole in OpenSSH will always be there with code this complex, however the benefits of using it far outweigh the drawbacks versus similar remote login protocols. Using OpenSSH is far more secure than using telnet. And with recent versions of OpenSSH, steps have been taken to reduce the possibility of remotely exploitable holes, protecting users from possible coding mistakes that could allow such holes.

Of course, using OpenSSH (or any SSH for that matter) can be as complex or as easy as you wish it to be. We will try to make using OpenSSH simple, yet allow you to take advantage of some of the very nifty features it has. While some of these features may add to the "bloat" of OpenSSH, the mere presence of them begs for its use.

Contents


The Passwordless Entry... Using Public/Private Keys with OpenSSH

One of the best features of OpenSSH is the ability to use public keys to login to a remote server. While OpenSSH encrypts all traffic, thus ensuring that a password sent across the network will be secure, the use of keys is much more desirable. By using keys, you eliminate the need to send your password over the wire to obtain access to the remote system, which will render some snooping tools designed for SSH traffic, such as dsniff, unusable. It is also far more secure than using a password. Individuals may be able to brute-force your password on a remote system; they may even have your password, obtained by some other means.

For instance, assume that you are the administrator of a particular server. You regularly SSH into the server to perform maintenance. This server is also a mail server from which you retrieve your mail. While you are astute enough to use SSH to protect the information you send in-transit (passwords, commands used for your administration, etc.), you use a regular POP3 client to retrieve your POP3 mail. Most people know this is not a good idea as POP3 traffic is completely in the clear; the POP3 password will be sent with no encryption and no security whatsoever. For the sake of argument, let's assume that you mean to setup a POP3S server (POP3 over SSL) in the future, but haven't had the opportunity yet. Any malicious individual sniffing network traffic could obtain your POP3 password. Since you don't use any virtual mail-hosting software, and the POP3 account is linked to your shell account, you are basically sending your login information across the network for all to see. The attacker could take your login name and password and use it to obtain shell access to the system; they would most likely use your secure alternative to telnet to do so.

This scenario is a bad one; that goes without saying. There are a few simple ways to mitigate this problem: The first is to use a virtual mail hosting system that separates mail users from system users, ensuring that mail users do not have a valid shell account. The second is to use encryption for POP3 traffic by utilizing POP3 over SSL. The final method is to use SSH keys.

To extend the above scenario, assume the administrator has configured the OpenSSH server to disallow password authentication; anyone connecting to the server must have a valid key on the account they wish to access. No key, no access. Disabling password authentication means that anyone attempting to access the machine will never receive a password prompt. The administrator can still log in because he has his public key on the server, which corresponds to the private key on his workstation or home system. In this case, even if the attacker did obtain his username and password, without a password prompt by the SSH server, and without the administrator's private SSH key, they would be unable to access the server.... via SSH, anyway. (Using SSH keys is not an excuse for poor system configuration and insecure protocols like POP3!)

The advantages of SSH keys should be obvious. The first step in heading towards a password-less SSH experience is to generate a keypair that has both a public and private key. SSH uses three types of keys: RSA1, RSA2, and DSA. It is recommended to completely disregard RSA1 entirely (both asenabled protocol in OpenSSH and as a key type), so instead concentrate on using RSA2 or DSA. I, personally, prefer using DSA keys. The difference between RSA1 and RSA2 keys is the SSH protocol they are used with. RSA1 keys are used with SSH protocol 1, and RSA2 keys (normally just called RSA keys) are used with SSH protocol 2. DSA keys can only be used with SSH protocol 2.

To generate a DSA keypair, execute (as the user to whom the keys should belong):

$ ssh-keygen -t dsa

This will, by default, create a 1024 bit DSA key with the private key being stored in ~/.ssh/id_dsa and the public key being stored in ~/.ssh/id_dsa.pub. If you wish to use an RSA2 key, use "-t rsa" instead on the command line and if you wish to generate an RSA1 key, use "-t rsa1".

You will be asked for a passphrase to secure the private key. This passphrase should be between 10 and 30 characters and difficult to guess. It should also be a good mix of numbers and letters, punctuation and whitespaces. It is deliberately called a passphrase, not a password. This private key must be protected at all costs, so the passphrase is important. The private key file should also be readable only by the user and no one else.

If you wish to change your passphrase, you can do so using this command:

$ ssh-keygen -p -f ~/.ssh/id_dsa

This tells ssh-keygen to change the passphrase on the private key stored in the file ~/.ssh/id_dsa (a DSA private key). You will be asked for the old passphrase and the new passphrase. If you ever forget the passphrase for a key, there is no way to retrieve it. You will have to generate a new key, delete the old key, and distribute the new public key to the servers you wish to use it with.

Now that the first step is completed, you must copy your public key onto whichever remote server you wish to access. If password authentication is enabled on the remote server, you can copy the file to your home directory there yourself; if password authentication is not enabled, you will need to send the public key to the administrator of the system for them to place for you.

Public keys belong in the ~/.ssh/authorized_keys file on the remote server. For instance, compA is your home computer and compB is your work system, to which you wish to have SSH access. The private key must remain on compA, while the public key should be in ~/.ssh/authorized_keys on compB. The authorized_keys file may contain more than one key, so if you have a home system and a laptop, you can generate two separate keys, one for each machine, storing both public keys on the server, allowing you to connect without a password from either home or the laptop.

The caveat is that if you use a protocol 2 key, the server must support protocol 2. This should be a non-issue because protocol 2 is much more secure than protocol 1, and should be used over protocol 1. Some sites have taken to disabling protocol 1 altogether, while by default in OpenSSH itself, the default protocol of choice is protocol 2, falling back to protocol 1. Your vendor, depending on the operating system you use, may have chosen to disable protocol 1 support completely.

Now, when you wish to copy a file with scp or login via ssh, you will be asked for your passphrase locally. The passphrase will unlock the private key, allowing you to log into the remote server without any passwords being exchanged over the network.

One final note. Unless you copy your public key to the remote server yourself, verifying the host is who it is supposed to be and using a secure means of transport (ie. SSH itself), you should not trust the public key. For instance, if Joe sends his public key to Bob (who is the administrator of said remote system), asking Bob to apply his public key to his account's authorized_keys file, Bob should not trust the key itself, unless Joe provides him the key physically (ie. on a disk, in person). If Joe emails the key to Bob, Bob must assume that the key may not be legitimate as email is an insecure means of transport. To get around this, Joe can email the key to Bob and then give him a phone call and let him know what the key's fingerprint is. Again, depending on Bob's level of paranoia, this may or may not be an acceptable means of verifying the key. This dilemma is similar to signing keys in GnuPG, and applies with any public key tool when an insecure transport method is utilized. For the sake of argument, Bob knows who Joe is, they've met, and he can verify that Joe is who he claims to be on the phone. The fingerprint that Joe would give to Bob can be obtained by executing:

$ ssh-add -l id_dsa.pub
1024 61:ed:d2:82:35:b4:54:3d:92:26:f8:48:69:21:de:c3 /home/joe/.ssh/id_dsa (DSA)

This shows the fingerprint for Joe's public DSA key. Bob can likewise used the same command on the key Joe claimed to have sent him, and if the two fingerprints match, he can assume the key is legitimate.

This more or less covers the use of public/private keys in OpenSSH, with exception to host keys. Host keys are covered in the Server Configuration section.

Using Agents

While using keys is more beneficial than using passwords, there is a particular drawback. Because a passphrase should be long and difficult to guess, re-typing it each time can be cumbersome and just plain annoying. To get around this, OpenSSH supports the use of an agent, which will store private keys in memory, thus preventing you from having to re-type your passphrase each time you wish to use a private key. To use the SSH agent, you must first start the agent, and then add identities (keys) to the agent's cache.

$ ssh-agent
$ ssh-add

This will not work if you execute it in an xterm; if you do, ssh-add will generate an error that it cannot connect to the agent. This is because the output of ssh-agent needs to be sourced as environment variables; it provides the PID (process ID) of the agent and the path to the socket file the agent uses. Without this information, ssh-add doesn't know which agent to connect to, nor does it know how to connect to it. ssh-agent is meant to be run during a login shell or when starting X; and some operating systems, such as Mandrakelinux, do this automatically for you when you start a new X session. In Mandrakelinux, if you start a new X session, then open a terminal and simply type "ssh-add", your keys will be added to the appropriate agent, provided you give the correct passphrase.

If you wish to delete an identity from the agent, you can do so with the -d parameter. For instance, if you have both an RSA and DSA key, and wish to remove the RSA key from the agent, you would use:

$ ssh-add -d ~/.ssh/id_rsa

If you wish to know what identities are loaded, you can do so with the -l option. Using -D will delete all identities from the agent.

There is a very handy utility called Keychain which you can use to load your identities into an agent that can be used for the duration of the machine's uptime. Currently, if you log out of X, your agent is removed, and when you log in again later, you will have to re-enter your passphrase. With Keychain, you enter your passphrase once and it can be used across multiple X sessions and even a console login without re-entering your passphrase. If the system reboots, you will have to re-enter your passphrase again, but it will persist until the agent is killed or the system is rebooted again.

Keychain is extremely simple to install. It is a single bash script that you install into /usr/bin. Download the Keychain tarball, untar it, and install it into /usr/bin as root. Then, for each user that wishes to use Keychain, the ~/.bashrc file must be edited to look like this:

# .bashrc

# User specific aliases and functions

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

/usr/bin/keychain -q ~/.ssh/id_dsa
. $HOME/.keychain/${HOSTNAME}-sh

The last two lines are of interest. This tells Keychain to load the ~/.ssh/id_dsa key and to be quiet when doing so (usually Keychain is verbose and tells you if it started a new agent, connected to a new one, and whether or not it added a key). If you wish to have more than one key, simply add the keys to the command line. For instance, an individual using an RSA1, DSA, and RSA keys might use:

/usr/bin/keychain -q ~/.ssh/identity ~/.ssh/id_dsa ~/.ssh/id_rsa

Now, the next time you log in to the console or open an xterm, Keychain will run and load all of the specified keys into your agent.

One final note on using agents: They are not very secure. While standard UNIX permissions will protect the IPC channel used to communicate with the agent, if a system is compromised, these permissions can be circumvented. To this end, agents should only be used on trusted machines because they load your private key into the cache, unencrypted. If an attacker were to compromise a system and obtain access to the agent, they could make their own requests of the agent until the agent was killed or the private keys removed from the cache.

If you are using the agent in a workplace or other untrusted or semi-trusted environment, you will want to remove your private keys from the agent if you will be away from your system for a significant period of time (such as overnight or if you leave the premises for lunch). Using the -D option with ssh-add will clear out all keys. By using Keychain, you are not opening yourself to any further holes; Keychain simply uses available programs to do it's job. If the identities are not in the agent, Keychain will call ssh-add, which will in turn ask for your passphrase. The obvious drawback here is that a user can try to guess your passphrase, thus obtaining access to your private key. Of course, common sense prevails here as well. Clearing out the agent is a very appropriate step, but locking your console or terminal, or logging out completely while you are away, is an even better means of protecting your private keys.

X and Port Forwarding

Probably one of the more widely used features in SSH is it's ability to create secure tunnels for insecure transports. For example, we all know that POP3 is an insecure protocol. If you have shell access to the POP3 server you retrieve mail from, you can use an SSH tunnel to pass your POP3 data via SSH from the remote to the local machine. The same goes for X applications; you can ssh into a remote machine, execute a GUI application on the remote system and have the application "open" on your local display. This feature of SSH is extremely powerful as it allows you to use insecure protocols in a secure manner, without opening additional ports on your firewall or relaxing access permissions or utilizing unencrypted traffic in order to make the connection or execute the application.

Let's assume for a moment that you want to wrap connections to your POP3 server with SSH. This can be done if you have SSH access to your POP3 server. The first step is to select a local port between 1024 and 65535; these are port numbers that unprivileged users are allowed to bind to. For example, choose port number 10110. Create the SSH tunnel like this:

$ ssh -L10110:localhost:110 remotesystem

This creates a tunnel that links port 10110 on the localhost to port 110 on the remote system, "remotesystem". It will also open a normal SSH connection to the remote host. By using the netstat tool, we can see that the port is in fact open and listening for connections:

[user@localhost user]$ netstat -l --tcp -p|grep ssh
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
 tcp        0      0 *:ssh                   *:*                     LISTEN      -
 tcp        0      0 localhost.localdo:10110 *:*                     LISTEN      31295/ssh
[user@localhost user]$

When you exit the SSH shell, the forwarding connection will close as well. Before you exit the shell, however, you can have your email client retrieve POP3 mail; not by connecting to remotesystem:110, but by connecting to localhost:10110. Having an open terminal, however, may not be always what you want. For instance, if you wanted to use cron to retrieve your emails via fetchmail or a similar program, you don't want a shell to be opened to the remote host. To accomplish this, you must give the SSH command something to do on the remote server. There are two avenues you can take here; persistent forwarding or temporary forwarding. With temporary forwarding, you are opening the tunnel for a short period of time; just enough to allow your email client or retrieval program to connect to the remote host via the tunnel. For instance:

$ ssh -f -L10110:localhost:110 remotesystem sleep 10

This tells SSH to go into the background (-f), open our tunnel, and execute the command "sleep 10". Normally, once this command is complete, SSH will terminate the session. However, if fetchmail utilizes the tunnel within that 10 second window, SSH will wait for the connection to terminate before exiting. This allows you to make use of the tunnel for the duration of your fetchmail run, but doesn't leave it open for a prolonged period of time. On the other hand, if you check your mail every few minutes, it may be more convenient to keep a persistent connection open. You can do this by giving the sleep command a longer amount of time, such as a value of 100000 or so. You could even get creative and execute a small script on the remote system that does nothing but continuously call the sleep command with extremely large values. This prevents you from opening and closing connections on a continual basis.

Forwarding X11 data works on the same principle. In the client configuration file (/etc/ssh/ssh_config or /etc/ssh_config on some systems), if ForwardX11 is set to "yes" for all hosts then you can use X11 forwarding for all hosts you connect to. In the system-wide configuration file (/etc/ssh/sshd_config or /etc/sshd_config on some systems), X11Forwarding could likewise set to "yes" to allow X11 forwarding for all connecting clients.

When you ssh into a remote host, you can execute X11 programs on the command line, just as you would if you were using an xterm on the local system. On the local system, the DISPLAY variable is used to tell X11 programs where to run; on the local system the DISPLAY variable might be set to ":0". On a remote system, when connecting via SSH, the value of DISPLAY will be something like "localhost:11.0". Again, this is forwarding magic that is virtually transparent to the programs you run, and to yourself. As with all things *nix, there are a few ways you can run remote X11 programs. The first, and probably most widely used, is to actually SSH into the remote system and execute the command, like this:

[user@localhost user]$ ssh user@remotesystem
[user@remotesystem user]$ gkrellm &

This illustrates connecting to the remote box "remotesystem" as the user "user" and executing gkrellm, a graphical monitoring tool. Note the "&" character at the end of the commandline to indicate that gkrellm should launch into the background, keeping the terminal free for you to use. You could omit the "&" character and simply keep gkrellm attached to the terminal, minimizing the terminal on your desktop and ignoring it. A more efficient means of launching gkrellm remotely is by executing:

[user@localhost user]$ ssh -f user@remotesystem gkrellm

Doing this, you can close the xterm you used to execute ssh, leaving the gkrellm monitor on your desktop, unattached to any session, because ssh is backgrounded. If you want to close the connection, kill the program you called. When you close the program, the instance of ssh is likewise terminated.

Unattended SSH

There are times when you will want to use SSH and you won't be around to enter in a passphrase. Backups from remote systems over SSH are a good example, or launching commands on remote systems via cron on the local system. In these cases, you may want to create a special account to do the maintenance; an account that cannot be accessed on the local system.

For instance, assume you wanted to run a backup every night using rsync over ssh. You would create a user with the name "backup" or "mirror" or something similar. Create a new SSH keypair for this user with an empty passphrase. Copy this user's key to the remote server. Depending on what you want to backup, you may have to add this user's key to the root account's ~/.ssh/authorized_keys file. Be warned that this gives the user immediate and password-less access to the root account on the remote system! This can be a very serious security hole if you do not configure things properly.

Now that the user has access to the root account, you can write your shell script and associated crontab entry to run the backup. For instance, you may run the following script daily on your system, as the backup user:

#!/bin/sh
RSYNC="/usr/bin/rsync -aP --quiet --delete -l -t -e ssh"

cd /data/backup/host
$RSYNC root@host:/etc .
$RSYNC root@host:/home .
...

Now this script can be executed every night by the backup user. It will run successfully because there is no passphrase defined for the user's private key, and the public key is in the remote root user's authorized_keys file. Now you have to secure the backup user's account. The first step is to modify the sshd_config file and add to it:

DenyUsers backup

This will disallow anyone from attempting to login to this account via SSH. Next, edit the /etc/passwd file. Here we will remove the ability for the backup account to login to the system via the console. To do this, execute the =vipw= program as root. Assuming your backup user's name is "backup", you will want to find the line that looks like this:

backup:x:512:512:Backup User:/home/backup:/bin/bash

The second field, "x", represents a shadow password. However, we can change the "x" to a "*" which tells login to disallow logins for this account. So this line would look like this when you have finished editing it:

backup:*:512:512:Backup User:/home/backup:/bin/bash

By default, vipw uses the vi text editor. If you are unfamiliar with vi, press the "i" character to get into INSERT mode; this will allow you to change the password field. When you have changed it press ESC to get back into command mode, then type ":wq" to exit and save your changes. When you are done, vipw will ask you if you want to edit the /etc/shadow file as well. Press "n"; we still want this account to have a real password.

What we have now accomplished is this: User "backup" cannot login via SSH, nor can "backup" login at the command line. You can still use su to change to the user, which is why we want to keep the shadow password; non-root users will still have to supply a password to change to "backup". Make it as cryptic as possible; if you have root privilege on the system, you don't have to even worry about remembering the password as you can su to "backup", as root, without knowing the user's password. This will protect the "backup" user from access via SSH, taking care of remote logins, from local access by disallowing login for this user, and from existing users by retaining a password.

Finally, another option is to use Keychain, as noted previously. With Keychain installed, you can do your backups as a regular user, with a passphrase-protected keypair, without causing problems for the cron-scheduled job provided that you supply the passphrase at least once before your cron jobs run.

Client Configuration

Configuring the client consists of three files: /etc/ssh/ssh_config (or /etc/ssh_config), which is the system-wide configuration file, ~/.ssh/config which is the user's personal SSH configuration, and ~/.ssh/authorized_keys which can configure incoming connections for this client.

A nice default client-side configuration that allows users to use X11 forwarding would look something like this:

Host *
  ForwardX11 yes
  Protocol 2,1
  StrictHostKeyChecking ask

The ssh_config(5) manpage covers each option in detail. Here we are essentially telling SSH to enable X11 forwarding, use protocol 2 then fallback to protocol 1 if protocol 2 is not supported, and enable strict hostkey checking (but ask us what we want to do about it), on every remote connection. This is fairly liberal, but still relatively secure.

This configuration also does not allow you to forward the connection to your authentication agent to the remote system. The benefit here is if you are bouncing between multiple machines, you can use the same key on your originating system on all systems that have that public key available. For instance, if you are on compA and connect to compB (which has a copy of your public key), you will not need to specify your password on compB. If you then decide to ssh into compC (from compB), you will have to enter your password on compC if ForwardAgent is disabled on compA, even if compC contains your public key. With ForwardAgent enabled, you can simply jump through to compC without entering your password. This is a convenience feature, and is not enabled by default.

Leaving the ssh_config file as-is is probably a good idea. Anything special that you wish to configure can be done in the user's own configuration file, ~/.ssh/config. There are a lot of time-saving things you can do with your personal configuration. The basic format of the file is:

Host [host]
  options
  ...

Host [host2]
  options
  ...

The file is read top-down in a "first match wins" order. When creating your configuration file, make sure that the more specific entries are listed first, with wildcard or global Host entries define last. Likewise, there is a distinct order of preference with options specified by SSH because you can specify options on the commandline, in the user's configuration file, and in the system configuration file. The highest precedence of a given command is given to the commandline, then the user's local configuration file, then the system configuration file.

The following is an example ~/.ssh/config file:

Host devel
  HostName myserver.mydomain.com
  User special

Host otherserver.mydomain.com
  User admin
  ForwardAgent no
  Port 220
  ForwardX11 no

Host *.mydomain.com
  ForwardAgent yes
  Compression yes

This configuration file illustrates a few things. The first entry defines the Host "devel", which has a real HostName of myserver.mydomain.com. This is a way of aliasing and allows you to use "ssh devel" instead of "ssh myserver.mydomain.com" when logging in. This also indicates that the default user to use is "special", so using "ssh devel" is the equivalent of using "ssh special@myserver.mydomain.com"; a little less typing for you.

The second entry defines the Host otherserver.mydomain.com. This entry uses the FQDN for the host and not an alias. It also specifies the default user to connect to as being "admin", that agent forwarding is to be disabled, that connections are to be made to port 220 by default, and that X11 forwarding is likewise disabled.

The next entry defines the wildcard *.mydomain.com. This defines that agent forwarding is allowed, and also that we enable compression by default. Now, with these three rules, we can determine what features will be enabled by each host. Remember, we're using a default ssh_config file, so we also have options defined there that work on all hosts. The following table shows what options will be enabled when connecting to myserver.mydomain.com, otherserver.mydomain.com, myhost.mydomain.com, and otherhost.otherdomain.com:

HostName User ForwardAgent ForwardX11 Port Compression Protocol StrictHostKeyChecking
myserver.mydomain.com special yes yes 22 yes 2,1 no
otherserver.mydomain.com admin no no 220 yes 2,1 no
myhost.mydomain.com user yes yes 22 yes 2,1 no
otherhost.otherdomain.com user no yes 22 no 2,1 no

Looking at the table, you may not see what you would expect. Why does myserver.mydomain.com have ForwardAgent enabled when, site-wide, ForwardAgent is disabled? The reason is because of the *.mydomain.com entry; here we enabled ForwardAgent and Compression for all hosts that match the wildcard *.mydomain.com, which includes the myserver.mydomain.com host. In other words, in addition to those options enabled site-wide, myserver.mydomain.com will also have ForwardAgent and Compression enabled, along with defining the User to be "special" and the alias "devel" to use to connect to it.

The otherserver.mydomain.com entry, on the other hand, negates the ForwardX11 definition site-wide, and the ForwardAgent definition for the *.mydomain.com entry. Thus ssh'ing into otherserver.mydomain.com will disable ForwardAgent and ForwardX11, although they are enabled elsewhere. It will also use Port 220 by default, with the "admin" user by default. However, because Compression is enabled for *.mydomain.com, Compression will also be enabled for this host.

Finally, when connecting to otherhost.otherdomain.com, only ForwardX11 will be enabled because it is done so site-wide. Since there is no other definition that matches the host otherhost.otherdomain.com, only those options specified site-wide will be applied.

As you can see, client-side configuration can be quite flexible, and doing so can be a time-saver. If you routinely ssh into a system with a username other than that on your local box (ie. userX on the remote system, userY on the local), you can define a Host entry to match the remote system and use the User keyword to specify userX. This way, you only have to type "ssh remotehost" instead of "ssh userX@remotehost". By default, if you only specify a hostname on the commandline (ie. "ssh remotehost"), ssh will try to log in with the same username invoking ssh (ie. userY invokes "ssh remotehost" so ssh will try to login as "userY@remotehost" which may not always be accurate).

So far, this has dealt with outgoing ssh connections. How about incoming? You can configure how SSH will handle connections to your account as well. There are a few limitations to configuring things this way. Firstly, configuration directives will never override server-wide configuration, unless permitted (for example, you can increase the server's idle timeout, but cannot enable password authentication if it is disabled site-wide). It is also highly recommended that you use public key authentication as it is the most flexible. With password authentication, many of these features will be unavailable to you.

Server-side client configuration is accomplished by modifying public key authorization files, typically ~/.ssh/authorized_keys (for both Protocol 1 and Protocol 2). An authorized_keys file will look something like this:

ssh-dss AAAAB3NxaC1kc3M...mc= user@somehost.com

The actual key has been abbreviated, but you can see that each line in the file begins with the string "ssh-dss", the DSA or RSA public key, and then the owner's address; usually the username and hostname of the originating machine. Using this file, we can add some extra commands to do particular things. For example, let's go back to our backup user scenario from the previous section. In that case, the user was able to use rsync to do the backup. This is done by executing the rsync command on the server. We can restrict what the backup user can do, as root, on the remote system quite easily by adding the following before the backup user's public key in root's authorized_keys file:

command="/usr/bin/rsync" ...key...

Now, if the backup user attempts to connect to the system and execute a login shell or any other program, they will execute the /usr/bin/rsync program every time. However, keep in mind that you can only assign one forced command per key, so if you wanted the backup user to be able to do something else, you would have to either use multiple keys or write a frontend shell script that would be the forced command.

In the case of using rsync over ssh, this obviously will not work because only the command in it's entirety is permitted. Using "/usr/bin/rsync --help" will not be allowed because it is not part of the command; the command is the entire command. So in our case, we would write a wrapper script. This is a very quick example and could probably be improved upon, but it illustrates what is required. Create the script /root/remote-rsync and use in the authorized_keys file:

command="/root/remote-rsync" ...key...

The contents of /root/remote-rsync would be something similar to this:

#!/bin/sh

log="/root/rsync.log"
command="$SSH_ORIGINAL_COMMAND"
denystring="No access.  Sorry."

send="0"
server="0"
deny="0"

date=`date +"%F %H:%M:%S"`

echo "$date  Connection from $SSH_CLIENT; command: $command" >>$log

if echo $command|grep -e "^rsync " >/dev/null 2>&1; then
  for arg in $command; do
    if [ "$arg" == "--sender" ]; then
      send="1"
    fi
    if [ "$arg" == "--server" ]; then
     server="1"
    fi
  done
else
  deny="1"
fi

if [ "$send" != "1" ]; then
  deny="1"
fi

if [ "$server" != "1" ]; then
  deny="1"
fi

if [ "$deny" == "0" ]; then
  $command
fi

What this does is test to make sure that the command being issued by the remote client begins with "rsync " and contain the strings "---server" and "--sender". All of this must be checked to ensure that the connection is a one-way "download" connection. We do not want the connection able to upload files to the system as it would allow the connection to overwrite or place any file anywhere on the system. If it does not, the script simply does nothing. You can have it echo something like "No access" if you like, but then you would have to make some changes to the above script (for some reason, if I try to echo something rsync thinks I've got a dirty shell and since the script works fine other than that, I elected just to not provide any information at all).

Because rsync over ssh passes commands like this:

rsync --server --sender -vlogDtpr --delete --partial . /etc

You can't possibly trap every call to rsync. However, you can make sure that "rsync[space]" is the first thing on the command line; this will reject "/usr/bin/rsync", and other variants. As long as the remote system is clean and there is no trojaned rsync in the path, you should be ok. In the case of rsync, make sure you pipe the output of grep to /dev/null otherwise the calling rsync will think there is a problem and not do the actual transfer. The main thing is to make sure that "--sender" and "--server" are checked for to ensure nothing is being written on the system. You could likewise take the script further to ensure that only certain paths are being copied and reject paths that don't match.

Finally, the script logs every command to /root/rsync.log with the command requested and the client's IP address so you can keep an eye on what that key is doing. It would be a good idea to perhaps use a unique script for every key that you give access to (although for a root account one would hope that you use as few as possible), this way you can track the commands issued from each particular key; this may help track down compromised systems if you do notice anything funny.

This also illustrates the environment variable, $SSH_ORIGINAL_COMMAND. This environment variable contains the command line that the client is requesting the server execute as the user connected. You can use this to create a log of all activity on a specific key by echoing the contents of the variable to a log file, then calling the variable directly, as illustrated in our remote-rsync script.

This is a very good way of permitting restricted access to an account and, in the case of our backup example, should be utilized as it will minimize the impact in case the backup account is compromised. While being able to use rsync will allow an attacker to gain copies of sensitive files, they won't actually be able to do anything as the root user, by way of the compromised backup account. However, keep in mind that many programs allow escaping to a shell and these programs should be avoided. For instance, most text editors and commandline mail clients, among many others, allow a user to escape to the shell. If you use one of these commands or programs as the forced command, the end user can easily "break out" of the forced command.

Another command you can use is the "from" command, which allows you to be even more cautious with authentication. In this case, you can specify the key that is able to access an account, but you can also specify the originating host. This means that the connection would have to be established from a particular host, regardless of the key. If the key matches, but the originating host does not, the connection is refused. To use this, place in your authorized_keys file:

from="client.mydomain.com" ...key...

You can use the FQDN, IP address, and wildcards. So instead of limiting it to one host, you could limit it to one domain by using "*.mydomain.com" instead. Also, you can specify multiple hostnames, IPs, and expressions by separating them with commas; you can also use the "!" character to negate an expression. For example:

from="*.mydomain.com,!xclient.mydomain.com" ...key...

Will allow access to this account using the specified key by any machine in the mydomain.com domain except for the machine xclient.mydomain.com. Also keep in mind that server-wide configuration settings override these; if the system is configured to keep all mydomain.com systems from connecting, then even using the above command will not let them in.

You can also set environment variables in the same way by using:

environment="EDITOR=joe" ...key...

In the above example, anyone connecting to the account with the specified key will have their $EDITOR environment variable set to "joe". You can set any environment variable you like this way, or even multiple environment variables by using:

environment="EDITOR=joe",environment="MYVAR=somevalue" ...key...

Another option is to set the idle timeout per key, like this:

idle-timeout=60s ...key...

In this case, the idle timeout is set to 60s for all connections to this account using the specified key.

Finally, you can also disable port forwarding, agent forwarding, and tty allocation on a key-by-key basis. The keywords to use are "no-port-forwarding", "no-agent-forwarding", and "no-pty" respectively.

As a final note, you can "stack" commands also. For instance:

no-agent-forwarding,environment="MYVAR=somevalue",command="/usr/bin/env|/bin/grep MYVAR" ...key...

would result in the following on a connection:

[user@localhost user]$ ssh someuser@somehost /bin/ls
MYVAR=somevalue
[user@localhost user]$ ssh someuser@somehost
MYVAR=somevalue
Connection to somehost closed.
[user@localhost user]$

As you can see here, the first thing attempted was to gain a listing of someuser's home directory. Instead our grep command's output was displayed. Likewise, on the second attempt, which was to gain shell access, the grep command's output was displayed and the connection was closed. This simply illustrates that command "stacking" is in effect: the environment variable $MYVAR was created initially and our call to the env program displayed it, while grep singled it out from the rest.

It should be quite obvious now that there are many ways that OpenSSH can be configured. Some of the options, if used without careful thought, are not secure. However, if you think about what it is you are trying to accomplish and implement it carefully, you can achieve a high level of flexibility using SSH while still maintaining security.

Server Configuration

Finally, configuring the OpenSSH server. This is perhaps the easiest part of the whole thing. The entirety of the OpenSSH server configuration is contained in the /etc/ssh/sshd_config (or /etc/sshd_config) configuration file. By default, the Mandrakelinux sshd_config (which we will pick on due to the fact that I configured it this way) looks like this (all comments removed):

Port 22
Protocol 2,1
HostKey /etc/ssh/ssh_host_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
KeyRegenerationInterval 3600
ServerKeyBits 768
SyslogFacility AUTH
LogLevel INFO
LoginGraceTime 600
PermitRootLogin no
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
RhostsAuthentication no
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
PasswordAuthentication yes
PermitEmptyPasswords no
ChallengeResponseAuthentication no
PAMAuthenticationViaKbdInt no
X11Forwarding yes
X11DisplayOffset 10
PrintMotd yes
KeepAlive yes
UsePrivilegeSeparation yes
Compression yes
Subsystem       sftp    /usr/lib/ssh/sftp-server

This basically tells the SSH server to listen on port 22 and use Protocol 2 and then fallback to Protocol 1 if the client doesn't support Protocol 2. For a higher level of security, this could be modified to be just "Protocol 2" and eliminate Protocol 1 altogether.

Next it defines three host keys: The first is the SSH1 (RSA) host key, following are the SSH2 (RSA) and SSH2 (DSA) host keys. A number of other options are specified here that are at their defaults; the sshd_config(5) manpage covers each of these options in depth. We will take a look at a few specific keywords here that may be of use if you are interested in tightening up your SSH security.

The PermitRootLogin keyword determines whether root will be allowed to login via SSH. It is good practice to disallow root logins altogether and use sudo or su to change to root from an unprivileged user. However, due to the way SSH is designed, allowing root to login via SSH isn't that much of a security threat, provided you use a strong password or disable password authentication. If you do not wish to disable password authentication site-wide, you can use:

PermitRootLogin without-password

This will refuse all attempts for password authentication on the root account, which is much more secure than allowing password authentication. If you must have password authentication enabled, you should protect root's account in this manner. Likewise, you could also use "forced-commands-only" which would only allow access to the root account by an authorized public key, but only if the command option is specified for the key. In this manner, password authentication is likewise disabled; either way, one needs their public key in root's authorized_keys file.

The PasswordAuthentication keyword determines whether or not password prompts will be given out to users without a valid keypair. For instance, when you ssh into an account without a key, you are asked for the user's password. With this disabled, you will never be asked for a password. In this instance, you will need to use SSH keypairs to gain access to the local user accounts, which you should be doing anyways.

The VerifyReverseMapping keyword is a good one for the paranoid. It tells sshd to try to verify the remote host name and make sure that the resolved host name for the remote IP address maps back to the very same IP address. By default, this is disabled.

The UsePrivilegeSeparation keyword is another important one. With this enabled, a lot of the code in OpenSSH that used to run as root is now run as an unprivileged user which will make compromising OpenSSH extremely difficult, if not impossible. There is really no reason for disabling this feature. Unfortunately, this feature does not (as of OpenSSH 3.4p1) play very nice with other *nix systems that use PAM and as a result some normal features, such as aging passwords, may not work as expected. It's reasonable to expect that the next version of OpenSSH should have most of these bugs worked out.

There are some other good keywords worth mentioning that aren't in the default Mandrakelinux configuration. The first of these is the AllowUsers keyword. This keyword defines what users, or users matching specified patterns, are allowed to login on the system. This can be a single name or a list separated by spaces. You can also use this to specify "calling domains" as well by using user@host instead of just user. For instance:

AllowUsers joe
AllowUsers jim@remotehost.com

This will allow the local users joe and jim to log in, but access to jim is only granted to those connecting from the host "remotehost.com". In other words, if a user is trying to log in as jim, but is connecting from otherdomain.com, they will not be permitted to log in. You can also use wildcards here to specify a domain, such as "jim@*.remotehost.com". The wildcards that can be used are "?" which matches any single character except the "@" character, and "*" which matches any sequence of characters, also except for "@".

Likewise, you can use the DenyUsers keyword. This will deny all users specified or that match the specified pattern, using the same wildcards as in AllowUsers. Be aware when using DenyUsers and AllowUsers in combination; the DenyUsers will always have precedence. For instance, if you denied logins to all accounts beginning with the letter "j" (ie. the pattern "j*"), yet had an AllowUsers statement for the user "jim", you might be tempted to think that jim would be accessible because it is the more specific pattern. This is not the case; logins are allowed only if there are no restrictions against the username. In this case, the restriction is against "j*" and thus jim would be denied.

You can also allow access based on groups with the AllowGroups and DenyGroups keywords. These work on the same premise as their user-based counterparts. It is used to determine access based on the unix group on the server. This will be checked against a user's primary and supplementary groups, and it only works on group names, not numbers. An example:

AllowGroups sshusers
DenyGroups users

In this example, users belonging to the group "sshusers" are able to login on the system, but users belonging to the group "users" are not. On many Linux distributions, it is standard for each user to belong to their own group as a primary group (ie. user joe will belong to group joe). You can add users to supplementary groups, such as users or, in this case, sshusers. Remember restrictions; they are the same here as they are for the user-based keywords. Here, if a user belongs to both the users group and the sshusers group, they will not have access to login.

Finally, if you want to use host-based access control, you can do so using the AllowUsers and DenyUsers commands, but without specifying a username. For instance, the pattern "*@somesite.com" will match against the domain somesite.com and affect any account they may wish to access.

It should be painfully obvious that OpenSSH is by no means a simple program. It is a large program with a lot of flexibility and power. To get the most out of OpenSSH, one has to dig around in manpages and configuration files; there is a lot of information hiding there to help you manage OpenSSH. Hopefully, this has illustrated some of those features that perhaps were not known to you before and perhaps given you an idea of how to lock down your system a little more, or give you an idea of how to increase what you can accomplish with OpenSSH.

One final note: Any time you make changes to your sshd_config file, make you sure you restart sshd with a "service sshd restart" as root (for initscript controlling Linux distributions). For other platforms, restart sshd accordingly or send a HUP signal to the daemon. Changes to user configuration files, such as ~/.ssh/authorized_keys and ~/.ssh/config require no restart of the sshd daemon.

References

Personal tools
other sites