ssh shenanigans

I use ssh all the time, from simple terminal connections to VNC sessions controlling telescopes to rsyncing terabytes of data to serving my music and video library. Most of the time, the machine I actually want to connect to is not visible from the open internet, and in some cases there are two layers of machines to go through to get to the one I want. I have used various ad-hoc combinations of ssh connections, but I just found a brilliant library that solves several of my problems: paramiko.

(Also, the name is from Esperanto, and the releases are named after gashlycrumb tinies. This is some serious nerdery, here.)


paramiko is a library written in pure python (using existing C-backed cryptographic routines) that implements SSH2 from scratch. Which is brilliant by itself. But of course, it's written to be a library usable from python programs, which in fact means that you can now avoid complex and fragile piping through ssh subprocesses. In particular it's let me write scripts to solve two problems I've had.

The first is tunneling. The bad way to connect through machines A and B to reach machine C is to run "ssh A" on your home machine, in the terminal on machine A run "ssh B", and on B run "ssh C". This means all your traffic is in plaintext on each of the three machines, it limits the feasible authentication, and you have to do it for every terminal you want to use. A better way is to use ssh's port tunneling: you open an ssh connection to machine A, but use the -n and -f options so that it just sits there. But in the process you also ask ssh to tunnel a local port (say 9234) to port 22 on machine B. Then your second ssh connection you connect to localhost:9234 (telling ssh that the hostname really is B for key purposes) and open up a tunnel from port 9235 to C:22. Now you can ssh to localhost:9235 as many times as you like. Unfortunately, it's a pain to pick out the port numbers and maintain the two outer connections.

There's a second kind of tunneling that's useful: you can use ssh as a SOCKS proxy. That is, SOCKS-aware applications (in particular, Firefox) can ask ssh to open a connection from A to some remote machine (say www.nature.com), and send requests through that. This way your IP address looks like an on-campus address and you can use your institutional subscriptions. With the right Firefox extension (e.g. FoxyProxy) you can even get it to use the tunnel automatically for just those sites you know need it. You set this up in the same basic way, opening a nothing-but-forwardings connection to A. You can also set ssh up to use SOCKS, if you have many behind-a-firewall machines to connect to.

So how to make these more convenient? Well, with paramiko you can (and I did) write a little utility to open and maintain such tunnels, without worrying about allocating port numbers for all the intermediate connections. You still need one local port for other programs to connect to, but the process is much simpler. (Incidentally, this demonstrates the awesomeness of python in another way: I had to implement SOCKS4a myself, but it took maybe an hour or so.)

The second problem I applied paramiko to is a convenience issue. On my laptop, I use an ssh tunnel for the purposes above, plus I use sshfs to mount my fileserver's disks. I'd rather these were mounted automatically when I log in (so that, for example, my music player doesn't get confused because it suddenly can't find half my music) but of course I need to type in a passphrase to unlock my private key. So the solution is to write a little paramiko script that sits and waits for a particular key to become available from the ssh agent.

Of course, like all powerful tools, paramiko can be used to facilitate Evil: if I ever feel the need to read someone's ssh traffic, I can just use DNS poisoning to get them connecting to me, and paramiko to forward their decrypted and re-encrypted connection to where they wanted to go. They'll get a nasty-looking message about host ID changing, but most users have learned to get rid of those by zapping host keys. I think, though my memory is rusty, that this also won't work with public-key authentication (of the user): the key-exchange protocol in that case is able to incorporate a unique connection ID neither host can control. Though, again, falling back to password authentication will probably get no more attention from users than an annoyed grunt. One machine I log in to doesn't even permit public key authentication "for security reasons".

In short, if you use SSH for your network plumbing, paramiko can make things much easier.

2 comments:

mvc said...

I've solved the A->B->C problem and avoided exposing plaintext by using netcat:

Host c.foo.com
ProxyCommand ssh b.foo.com nc -w 1 %h %p

Host b.foo.com
ProxyCommand ssh a.foo.com nc -w 1 %h %p

Unknown said...

That is clever. I particularly like the way it sets up the tunnel on the fly. It should be possible to do something similar that reuses a tunnel by having the ProxyCommand set up a port forwarding and silently fail if it already exists.