ssh for Programmers

Overview

Way back when, just after the dot-com-bomb, I found myself working for a media company named Metapa. We made mp3s and 30 second preview clips from boxes of CDs that tech-clueless record companies snail-mailed us. Metapa was staffed with a ton of Unix-heads and I received a great schooling in the ways of the Secure Shell, ssh. You could not get to a server without sshing into it. Our “VPN” was a server running ssh, configured to only accept key-based login. There was no way to log in with just a password. You would connect and use ssh’s built-in port forwarding to route more fancy connections, as required. I got to know ssh incredibly well.

At more recent jobs, people have used ssh to manually connect to boxes and to manually issue commands, but they never did anything more advanced. I never understood exactly why. I’m sure some of this is that these have been mixed Windows/Linux environments with heavier reliance on Windows, so the “culture” of using ssh for everything just isn’t present. Part of it may also be lack of experience with ssh.

This article attempts to touch on the slightly more advanced methods of working with ssh. I am not going to get into port forwarding, since that requires a much more advanced mental model and a good grasp of how to configure the local and remote software. I also will not get into rsync, which is a common tool that piggybacks atop ssh to synchronize folders. What I will discus is a few of the more common advanced uses:

  • copying files
  • logging in without keys
  • aliases for server names
  • differing usernames
  • logging in to the cloud (combination aliases, usernames, and alternate keys)
  • a quick fix for changed host identities

Copying Files

ssh has a companion app named scp, or Secure Copy. It works exactly like the regular Unix cp command except the source and destination can take ssh file paths in the form of username@machine:path/to/file.txt.

For instance, if I wanted to push my “.vimrc” file to a remote system named frodo, I could run any one of the following commands:

# a technically correct, but wordy way to do it
scp .vimrc briane@frodo:.vimrc

# destination file is the same name, so no need to specify
scp .vimrc briane@frodo:

# assumes my username is the same on frodo, so no need to specify
scp .vimrc frodo:

Note that when scp connects, it is in your home directory. These destinations use relative paths (because the paths do not start with a slash) so they are relative to your home folder. If you wanted to copy a log file, you might use an absolute path such as:

# copy the Apache error log to the current folder
scp briane@frodo:/var/log/apache/error.log .

Logging in Without Keys

By default ssh (as well as scp) uses your Unix password to access the remote system. This is all well and good, but it also offers another way to identify yourself that allows for a bit more convenience and a lot more security. This method is dual-key cryptography. Put simply, this is a whole bunch of math shared between two files, a “public” key and a “private” key. The private key is, effectively, a ludicrously large number — like a password — that you share with nobody. The public key is mathematically derived from the private key and can be used to check the authenticity of the private key. You can share the public key far and wide. When using keys with ssh, you store a copy of your public key on the remote server. Every time you log in to it, your machine and the remote machine do a little dance involving the public and private keys, and you’re instantly logged in — no password required. It effectively shifts the authentication mechanism from “what you know” (a password) to “what you have” (a data file with that long number).

In concept, this is all well and good, but it is sometimes a little painful and tedious to set up by hand. You have to ensure the correct file is in the correct place. The permissions have to be perfect or the ssh server will refuse your login. After all, if your public key is writable by other people, then someone with access to the system can overwrite it with their public key and log in as you, with your full permissions and credentials. Fortunately, if there is one thing that computers are good at automating, it is simple, annoying, sometimes painful tasks.

First, you will need to create a key (if you have not done so already). It’s a simple command:

ssh-keygen

Just keep hitting Return to answer all the questions with their defaults, including an empty password. Next, you can use a script like the following, named colonize.sh, that will send your key to a remote machine over a regular ssh-with-password connection and set up the permissions.

#!/bin/bash
if [ -z "$1" ]; then
    echo "Function: copies ssh public keys to remote machines for passwordless login"
    echo "Usage: colonize.sh <username>@<servername or ip address>"
    exit 1
fi
KEY=id_rsa.pub
STORE=authorized_keys
CONTENT=`cat ~/.ssh/$KEY`
echo "Attaching key to authorized_keys file"
ssh $1 "mkdir -p .ssh && chmod 700 .ssh && touch .ssh/$STORE \
    && chmod 644 .ssh/$STORE \
    && echo '$CONTENT' >> .ssh/$STORE"
echo "Complete!  You should be able to log in without a password now!"

Now if you want to colonize a remote machine with your ssh keys, you’d just do something like:

colonize.sh briane@bilbo

Enter your password (for the last time ever!) and next time you ssh or scp with that machine, it will use your keys instead of your password.

Server Aliases

There is a config file in your dot-ssh folder named simply .ssh/config. This holds a wealth of options for custom per-server settings. Long ago, I had a virtual private server with Dreamhost (I’ve since moved to Linode for reliability reasons; if you’d like to try it out, feel free to use my referral URL). This server had the ugly and difficult to remember name of “ps24493.dreamhost.com”. I ssh and scp all the time, but had difficulty remembering that exact server number. Fortunately, there is a better way.

Your .ssh/config file lets you define a Host (what you type to connect) to a HostName (the actual server name). For example, my config had the following two lines:

Host dreamhost
HostName ps24493.dreamhost.com

This means that “dreamhost” ends up as shorthand for the more annoying “ps24493.dreamhost.com”. If I want to log in, I’d just “ssh dreamhost”. If I wanted to copy a file, I could “scp index.html dreamhost:/var/www/my_website”. It made it much easier to access the server.

Differing Usernames

I actually had one more line in that part of config file. The account name I use on most of my local machines is simply “brian”. On that virtual private server, it was the much more verbose but globally-unique “brianenigma”. So technically, I could not connect with a simple “ssh dreamhost” but “ssh brianenigma@dreamhost”. But there is another line that can be added to the .ssh/config to allow the former variant without a username.

You can define a default username for a specific system by putting a “User” entry in the “Host” section. For instance:

Host dreamhost
HostName ps24493.dreamhost.com
User brianenigma

This, then, allows me to type in “ssh dreamhost” instead of the much more wordy “ssh brianenigma@ps24493.dreamhost.com”.

Logging in to the Cloud

Amazon’s Web Services is an interesting beast. You get a machine up in the cloud that you can log into. It typically has an ugly name (which can be nicknamed — see two sections back) and may have a different username than the one you use on your local machine (which can also be redefined — see the previous section). It throws in yet another wrinkle. Password-based login is forbidden and it assigns you a public and private key pair. Because it assigns you a key, it is obviously going to be different than any key you have previously generated for all your other connections.

There is an ugly way to specify the keystore you downloaded from Amazon on the command line, but a far more simply way is to define it in your .ssh/config, such as this entry that combines all of the above and adds the keystore:

Host cloud
HostName ec2-256-301-297-271.us-west-2.compute.amazonaws.com
User administrator
IdentityFile ~/.ssh/my_amazon_key.pem

Now you can log in to your AWS account by just typing “ssh cloud”. ssh will expand that out to the very ugly equivalent:

ssh -i ~/.ssh/my_amazon_key.pem administrator@ec2-256-301-297-271.us-west-2.compute.amazonaws.com

Remote Host Identification has Changed

I work in a lab environment with lots of servers that are frequently reformatted. This means that the servers retain their name and IP address, but the ssh fingerprints that uniquely identify them (to help thwart third parties from posing as the server and stealing your passwords) get wiped out. In this case, if I attempt to log in, I get a big scary message like the following:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    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
f0:d1:77:17:9a:55:de:ad:be:ef:00:00:08:67:53:09.
Please contact your system administrator.
Add correct host key in /home/briane/.ssh/known_hosts to get rid of this message.
Offending key in /home/briane/.ssh/known_hosts: 6
Permission denied (publickey,password).

The fix here is to open up .ssh/known_hosts and delete line 6. Technically, deleting the entire file also works (and I have seen plenty of people do that as a “fix”), but that has the side-effect of forcing you to confirm, all over again, every remote host you connect to as a new host. So: “delete line 6” is simple enough to do, but also annoying. We can automate that. The following script, called fixssh.sh, will retain the past nine backups of your known_hosts file and delete the offending line. In this case, you’d run it with “fixssh.sh 6”.

#!/bin/bash

if [ -z "$1" ]; then
    echo "Put line number of your ~/.ssh/known_hosts file for deletion on the"
    echo "command line."
    exit 1
fi

cd ~/.ssh/
rm -rf known_hosts.9
for NUM in 8 7 6 5 4 3 2 1 ; do 
    ((NUMPLUS=$NUM + 1))
    if [ -d known_hosts.$NUM ]; then 
        mv known_hosts.$NUM known_hosts.$NUMPLUS 
    fi 
done
mv known_hosts known_hosts.1
cat known_hosts.1 | sed "$1 d" > known_hosts
chmod 644 known_hosts

Conclusion

I hope these tips help you become more proficient at ssh usage. Once you have these down, you might search on your own for the more advanced topics like how to use rsync, to keep massive amounts of files in sync, or using ssh port forwarding.

Posted in: Code Software Work

Published by

Brian Enigma

Brian Enigma is a Portlander, manipulator of atoms & bits, minor-league blogger, and all-around great guy. He typically writes about the interesting “maker” projects he's working on, but sometimes veers off into puzzles, software, games, local news, and current events.

Leave a Reply

Your email address will not be published.