Close

SSH for fun and profit

ssh is a tool that many of us use in our daily work lives. Probably for most of us, it is primarily a means to get a shell on a remote computer. This post is all about exploring the power of ssh and making its daily use a little more seamless. Also, I’ll talk a bit about some of the more powerful features hidden away in this ubiquitous utility.

History

ssh originated in 1995 and was designed to replace remote login utilities (like telnet) which transmitted everything, including passwords, in plain text. In 1999 the OpenSSH project created an open source version of the original ssh project and continues to be actively developed to this day. When you use ssh today, you are almost certainly using OpenSSH (unless you are on windows … I don’t think that Putty uses OpenSSH … let me know in the comments if you know).

One of the main advantages of ssh is that all data transmitted over an ssh connection is encrypted. With ssh, it is possible to wrap unsecure protocols in an ’ssh tunnel’ so that all any middle-men see is encrypted traffic.

Remote Login

This is what most people use ssh for. The typical usage is:

$ ssh user@host

user@host's password:

Although you can drop the user part if the local username is the same as the username on the remote machine. The next step is to provide a password to authenticate.

There are a few things that we can do to make this a bit more seamless.

RSA Keys for Authentication

First, we can simultaneously enhance security and ease of use by using key authentication. To do that, we’ll use the ssh-keygen command like this:

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/path/to/your/home-dir/.ssh/id_rsa):

It will first ask you to specify a name for the private key. The default is 'id_rsa'. If you end up making several keys for different servers you will want to have some sort of sane naming convention, but for now accepting the default is just fine. (Note: by default ssh-keygen will generate RSA key- pairs but you may specify other algorithms using the -t flag.)

Next it will ask you to enter a passphrase which you will use to unlock the key:

Enter passphrase (empty for no passphrase):

I always protect my keys with a password, but they work fine even if you don’t. Most operating systems offer some sort of a keychain for managing passwords, including these RSA keys. I’m usually on OS X and the command to make the OS handle the password management for the keys is:

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

I’m not sure off the top what the linux equivalent would be for any given distro. (If you know, feel free to leave a note in the comments.)

Once ssh-keygen is done running, you will find two new files in ~/.ssh/. If you accepted the default name for the key you will see id_rsa and id_rsa.pub. These are RSA public/private keypairs. Without going into a lengthy cryptography discussion, suffice it to say that you distribute your public key the places you want to authenticate to and you use your private key to do the authentication.

So, to use these shiny new keys to do ssh authentication you need to put your public key in a special place on the remote computer. When you ssh into a remote host, the host ssh daemon will check the contents of a file called ~/.ssh/authorized_keys for public keys to authenticate against.

This command will do the trick:

$ cat ~/.ssh/id_rsa.pub | ssh user@host \
    "mkdir .ssh 2>/dev/null; cat >> .ssh/authorized_keys"

There’s a lot going on in that command. Lets break it down into digestible chunks.

First, the cat ~/.ssh/id_rsa.pub portion will output the contents of the public key that we just generated. The output is then piped into the ssh command.

The next part introduces a new ssh feature—remote command execution. The general form is:

$ ssh user@host command

This ends up being roughly equivalent to logging into the remote host via ssh and then issuing the command and exiting. The difference is that the input and output associated with the command is bound to the local host rather than the remote host. That is the key element here.

The output of the cat command on the local host is directed to the input of

"mkdir .ssh 2>/dev/null; cat >> .ssh/authorized_keys"

First we are making sure that the ~/.ssh directory exists. Then, the output of cat ~/.ssh/id_rsa.pub is directed into ~/.ssh/authorized_keys on the remote host.

I do this often enough that I have a function defined in my .bashrc file so I don’t have to type that long command. Here it is:

key () {
    if [ -z $1 ]
    then
        printf "USAGE: key [user@]host\n"
    else
        cat ~/.ssh/id_rsa.pub | \
            ssh $1 "mkdir .ssh 2>/dev/null; cat >> .ssh/authorized_keys"
        printf "put public key in .ssh/authorized_keys on $1\n"
    fi
}

With this function available the task of putting a key on the remote host is a simple as

$ key user@host

Once that is done you can now do:

$ ssh user@host

and you don’t have to type your password. Instead, the ssh program negotiates the authentication process for you by using your private key.

ssh configuration file

This is a good time to talk about some of the awesome things you can do with the ssh config file. Pretty much any of the settings you can specify in an ssh command can be permanently cached in a config file for specific hosts (or for all hosts if you like).

When you issue an ssh command, the program will look for a file called ~/.ssh/config and apply any specified options for the applicable host before the command executes.

Anatomy of an ssh config file:

Let’s start with an example:

Host host
     User username
     Hostname hostname.with.a.bunch.of.qualifiers.com
     IdentityFile ~/.ssh/hostname_key
     Port 2222
     AddressFamily inet

If that is in your ~/.ssh/config file, you can now do:

$ ssh host

and it will be equivalent to doing:

$ ssh -4 -p 2222 -i ~/.ssh/hostname_key \
    username@hostname.with.a.bunch.of.qualifiers.com

The sky is the limit when it comes to setting up config options. Take a look at the documentation for a full listing of the available options.

X11 Forwarding

Some programs available on remote host have a graphical user interface. To use those programs remotely it is usually a good idea to set up a remote desktop environment (VNC and NX are a couple examples). However, sometimes you may wish to have quick and easy access to the graphical window of a particular program.

Fortunately, ssh supports this with no ahead of time setup.

Lets say you wanted to launch matlab on some host. The command:

$ ssh -X host matlab

will launch matlab on the remote host and display in the local X11 window.

There are security implications associated with allowing a remote program access to a local X-window. For this reason, you may find that the -X flag will fail to perform as expected. This is because X11 forwarding with the -X flag is “subjected to X11 SECURITY extension restrictions by default” (according to the man page).

You can get around these restrictions by using the -Y flag instead. Just remember that you may be opening yourself to risk if you do so. Never use the -Y flag unless you really trust the remote host and the remote program!

The good stuff (here there be dragons):

We’ve barely scratched the surface of the capabilities of the ssh utility, and I don’t intend to talk about every available feature. There are three more features that I will discuss before calling it a day. They are ‘local port tunneling’, ‘remote port tunneling’, and ‘Dynamic port tunneling’.

WARNING: The following sections could be used to circumvent network policy implementation dictated by your local IT/IA/network policy. Just because you can do a thing does not mean you should do that thing. Using the following information in an irresponsible way may result in a violation of policy or could potentially be a crime if used maliciously!

Local port tunneling

So called ‘local port tunneling’ makes it so that connections to a specific port on your local machine get forwarded to a port on a remote machine. This is the syntax:

$ ssh -L[bind_address:]port:remote_host_b:hostport remote_host_a

Lets consider an example.

  • Suppose you have an account on ‘server_a’ and so you are able to ssh to it.
  • Let’s also suppose that there is a service running on ‘server_b’ on port 12345
    and you want to connect to it. Unfortunately the firewall rules on your network
    prevent connections to ‘server_b’ from your workstation.
  • Suppose that ‘server_a’ can connect to ‘server_b’.
  • Finally, suppose port 12345 is not available on your workstation, but 9999 is.

The following command will do the trick:

$ ssh -L9999:server_b:12345 server_a

Now you can connect to localhost on port 9999 and you will actually be connecting to ‘server_b’ on port 12345. As an added bonus, all of the traffic is encrypted along the way.

Often times I will end up doing something more like:

$ ssh -L12345:localhost:12345 host

This will set up a local tunnel to host and make it so connections to my localhost:12345 get forwarded to host. Note the localhost argument where server_b was in the last example. This makes the connection to port 12345 over the loopback interface on the remote host. This is useful when a service running on host does not accept incoming connections, but does serve local traffic.

Before moving on, you may be asking about the optional bind_address mentioned at the beginning of this section. Be careful with this one. If you specify an address (i.e. an IP address of one of your interfaces) it will bind the tunnel to that interface. The effect is that your machine will now accept incoming connections to the local port and they will be forwarded through your tunnel to the remote host. Unless you specifically want to enable this behavior, you should probably shy away from this.

Unless you change the GatewayPorts option, the default behavior will be to bind to localhost if bind_address is not set.

If you want to, you can also specify bind_address to be * if you really want to bind to all interfaces, but you will probably need to either escape it (\*) or quote it ("*") due to globbing.

One final thought. When I set up tunnels, I always use the -f and -N flags. -f puts ssh into the background just prior to remote command execution. -N prevents remote command execution. In combination, these flags make it so the tunnel is setup and you are returned to your shell.

I set up local tunnels so often that I have this convenience function defined in my .bashrc:

tunnel () {
    if [[ -z $1 || -z $2 ]]
    then
        echo "USAGE: $0 <local-port> <host> [remote-port]"
        echo "       if remote-port is not supplied, the"
        echo "       remote-port will be set to local-port"
    else
        LOCALPORT=$1
        HOST=$2
        REMOTEPORT=$3
        [ -z $REMOTEPORT ] && REMOTEPORT=$LOCALPORT
        ssh -fNL${LOCALPORT}:localhost:${REMOTEPORT} ${HOST}
    fi
}

Forwarding privileged ports (i.e. less than 1024) requires root privileges.

Remote port tunneling

Remote port tunneling is pretty much the inverse operation of local port tunneling. Instead of setting up a tunnel which will forward connections made to the local host to the remote host, a remote tunnel take connections made to the remote host and forward them to the local host.

The structure of the command is just like the local tunnel except that instead of -L the flag is -R:

$ ssh -R[bind_address:]port:remote_host_b:hostport remote_host_a

This will make it so that connections made to remote_host_a on hostport will be forwarded to remote_host_b on port via the local machine.

If we wanted to grab all of the traffic on a remote host:port and handle it locally, we could set up a remote tunnel and set remote_host_b to localhost.

Another common use case for remote tunnels is this: let’s say you have a computer that has ssh, but doesn’t have an ssh server. If you want to temporarily provide access to it you can make a ‘reverse tunnel’. If you have a VNC server running on localhost:5900 you could provide remote GUI access by doing:

$ ssh -fNR5900:localhost:5901 remotehost

Then a user on remotehost could connect a VNC server to localhost:5901. This will result in a VNC connection to the computer that set up the remote tunnel.

NOTE: Forwarding privileged ports (i.e. less than 1024) requires the user for remote_host_a to be root.

Dynamic port tunneling

In the previous sections we talked about forwarding connections to a specific port to a remote host. In this section, we talk about setting up a way to forward arbitrary ports at the application level to a remote host.

SOCKS Proxy

Many applications support something called a ‘SOCKS Proxy’. This is a protocol which acts as a proxy for applications accessing the internet. Instead of going through the default gateway to resolve a route to an IP address, applications configured to use a SOCKS Proxy will instead direct that traffic to the proxy. The Proxy then does whatever translation is necessary and negotiates the connection on behalf of the application.

The ssh utility supports the creation of a SOCKS Proxy with the -D flag. The command is quite simple:

$ ssh -D[bind_address:]port host

This sets up a proxy which listens on the local machine (again you can optionally bind to a specific device address or *) and dynamically forwards the application data to the host.

There are many use cases for SOCKS Proxies. I won’t go into all of them here, but suffice it to say that it can be used as a “poor mans VPN”.

One last example:

Suppose you are a developer on a project and you are using git for version control. Let’s also say that the git server uses IP filtering. You have access to a large host which you want to do builds and testing on but it isn’t in the IP range that the git server accepts. Let’s also assume that wile you can ssh to the host, the host cannot ssh to your computer.

In order to check out the project on the host we will do some ssh magic.

First we will make a tunnel so the host can connect back to your machine by running this command on your computer:

$ ssh -fNR2222:localhost:22 host

Now, if you are logged into host, you can connect back to your computer by (assume your username is user):

$ ssh -p 2222 user@localhost

Cool huh?

Ok, now that you can get back to your machine, exit that connection and let’s set up a proxy for git

$ ssh -p 2222 -fND1080 user@localhost

The proxy is set up, so it’s time to let the git application know about it:

$ git config --global http.proxy "socks5://localhost:1080"

Now we can do a git clone on the repo despite the fact that the server isn’t on the IP whitelist!

Once the repo is pulled, it is a good idea to convert the global proxy configuration to apply to just the repo by running the git config command in the checked out repo–sans the --global flag. Then edit (or simply remove) ~/.gitconfig.

Leave a Reply

Your email address will not be published. Required fields are marked *