Tighter SSH Security with Two-Factor Authentication
I enthusiastically use two-factor authentication whenever possible, because static passwords aren't the best mechanism around any moat. Traditional passwords are vulnerable to social engineering, key-loggers, yellow post-it notes and—especially as computers become ever faster—to cracking. Tossing them in favor of two-factor authentication is a good idea and helps me sleep better at night.
Unfortunately, network-based, commercial two-factor systems are generally too expensive and complex to use at home or on small networks. But, guess what? You already have the necessary parts on your Linux computer to build a two-factor authentication system. The ubiquitous secure communication tool, OpenSSH, provides all the tools necessary to create a host-based, two-factor authentication system suitable for the home, small office and even larger networks.
This article describes how to combine removable media with OpenSSH public/private keys and the amazing ssh-agent program to achieve two-factor authentication for both regular and privileged users.
Let's start by creating two-factor authentication for regular (nonroot) users. In this case, we use the well-known SSH public key authentication facility with a small twist. Rather than store the private key in the .ssh subdirectory of your home directory, as is the default, we'll place it on a USB pendrive.
For this example, you'll be logged in as the nonprivileged user bob on a Fedora Core computer, machine1. You'll connect to the remote Linux box machine2 as bob.
Let's start by creating the public/private key pair that we'll use to log in to machine2:
ssh-keygen -t rsa -f key-rsa-bob@machine2 -C key-rsa-bob@machine2
Enter a passphrase when prompted (the longer and more random the better). By default, the ssh-keygen program creates the key pair in the subdirectory .ssh in your home directory—in this case, /home/bob/.ssh. For this example, I've chosen an arbitrary yet descriptive filename to help identify the intended user and hostname at a glance; this will be important in the succeeding examples, which use multiple keys. (I'm assuming the USB drive is formatted with a Linux filesystem like ext3; vfat works, but you'll need to change the key's file permissions to 400 after every mount.)
Mount your USB pendrive, and you should see it as as /media/usbdisk, /media/usbdisk1, /media/disk or /media/disk-1. Move your newly created private key to the appropriate directory and limit access to the owner:
mv key-rsa-bob@machine2 /media/usbdisk chmod 400 /media/usbdisk/key-rsa-bob@machine2
Next, copy the public key (email@example.com) into the /home/bob/.ssh/authorized_keys file on machine2. Make the authorized_keys file readable only by the owner:
chmod 400 authorized_keys
Now, you can log in to the remote computer, machine2, from machine1, as bob, using the public/private key pairs (the -i option tells the ssh client what key to use):
ssh -i /media/usbdisk/key-rsa-bob@machine2 bob@machine2
Type in the private key passphrase when prompted, and the OpenSSH server on machine2 logs you in. Unmount and remove the USB device (or removable disc) on machine1, and your private key is protected. You've achieved two-factor authentication: one factor is the key stored on the USB device that you can keep separate from your computer, and the second one is the passphrase you store in your head.
Using SSH public key authentication is a common and familiar process to many. Putting the private key onto removable media is a simple way of physically separating one factor from another.
Example 1 shows how to log in to a remote machine securely using a USB device to separate one authentication factor from another. This works well when logging in as a nonprivileged user but not as root. We have to find a way to log in remotely as the superuser.
One solution would be simply to extend the previous example's method and configure the remote OpenSSH server to allow root logins directly from the network. No passwords or keys will traverse the network, but we would violate the age-old system administration prohibition against directly logging in as root. No shortcuts should be allowed, so we have to figure out how to first log in as a regular user and then as root.
Once again, OpenSSH comes to the rescue. In this case, we continue to use public/private keys but introduce a configuration twist. First, configure the remote SSH service to allow root logins via the internal loopback interface but not the external network. Second, configure the ssh-agent utility to allow the remote machine to authenticate root by querying the keys stored on the local machine.
Here's how the process works:
Create a private/public key pair for root on the local machine.
Copy the public key into root's authorized_users file on the remote machine.
Run the ssh-add utility locally to cache the private key.
ssh to the remote machine and log in as a regular user as described in Example 1; however, this time use the agent-forwarding option.
On the remote machine, ssh to the localhost interface as the root user. The remote OpenSSH dæmon queries the local agent, authenticates root, and you can log in as the superuser.
The ssh-agent utility provides just the functionality we're looking for. It allows remote SSH dæmons to authenticate users by querying the locally stored cache of decrypted private keys. Keys are never transmitted between machines—the private keys remain stored on removable media on your local workstation.
ssh-agent is powerful, but setting it up can be tricky. First, you need to use the ssh-add utility to decrypt your private key and hand it to ssh-agent. Second, you need to tell ssh-add how to communicate with ssh-agent. ssh-add communicates with ssh-agent via a socket, whose location is stored in the SSH_AUTH_SOCK environmental variable. By default, ssh-agent creates sockets with arbitrary names, and setting SSH_AUTH_SOCK correctly can take some work.
Fortunately, many Linux distributions, including Fedora Core, automatically set up the necessary ssh-agent/ssh-add connections when you log in graphically (such as on GNOME or KDE). Log in at the console, open a terminal console and type the following:
As long as ssh-add can communicate with ssh-agent, you should see either a list of your public keys or a message like “The agent has no identities”.
If, for any reason, ssh-agent isn't running or your SSH_AUTH_SOCK variable isn't set, or isn't set correctly, you will get the message “Could not open a connection to your authentication agent”. In that case, run the following command:
This starts an ssh-agent instance and automatically sets the environmental variables in your current shell.
Next, create a key pair for root as you did in the first example:
ssh-keygen -t rsa -f key-rsa-root@machine2 -C "key-rsa-root@machine2"
Move the private key to the removable media and give read access to the owner but nobody else:
mv key-rsa-root@machine2 /media/usbdisk chmod 400 /media/usbdisk/key-rsa-root@machine2
Copy the public key into the /root/.ssh/authorized_keys file on the remote computer machine2.
Add root's private key on machine2 to ssh-agent by running the following command:
ssh-add -t 300 /media/usbdisk/key-rsa-root@machine2
Enter the passphrase when prompted, and ssh-agent returns the message “Identity added: key-rsa-root@machine2 (key-rsa-root@machine2)” when it adds the key. (The -t 300 option limits the lifetime of the cache to 300 seconds, or five minutes. Your keys will remain viable forever if you don't specify the lifetime.)
Log in to the remote machine as a regular user:
ssh -A -i /media/usbdisk/key-rsa-bob@machine2
Enter the passphrase when prompted, and you will log in to machine2. (This command is the same as in Example 1, except we're using the -A option, which turns on agent forwarding.)
Type ssh-add -l on machine2, and you should see the root key you just added to ssh-agent. For example:
2048 fa:5c:4b:73:88:26:..:... /media/usbdisk/key-rsa-root@machine2 (rsa)
Next, su to root (on machine2), and configure the SSH dæmon to allow root logins on the internal loopback interface. Edit the /etc/ssh/sshd_config file and add/modify the following options:
PermitRootLogin yes AllowUsers bob@* AllowUsers root@localhost.*
(Some OpenSSH configurations require you to set the numeric loopback address explicitly: AllowUsers firstname.lastname@example.org.)
Save your changes, and restart the SSH dæmon:
service sshd restart
Log out of the root account, and use OpenSSH to log back in as root:
Now the OpenSSH dæmon on machine2 accepts root logins on the loopback interface but not from the external network. It negotiates with ssh-agent on machine1 to authenticate you as the root user. root's private key never left machine1! Using OpenSSH in this way effectively allows you to replace the su (switch user) and sudo utilities.
But, we're not quite finished. You can increase security further by limiting the su command to locally connected devices. Modify /etc/pam.d/su as shown below to prevent anyone from using su over the network:
auth required pam_securetty.so
The su command will work only from the console and virtual terminals.
Unmount and remove your USB device. Individuals actually will have to steal your USB drive at this point to get your keys. Even then, they have to discover your passphrase or expend lots of computing power and time cracking the key.