I was involved in the creation of the sshephalopod project, which was an attempt to build an enterprise level authentication framework for SSH authentication using the SSH CA feature.
The project is based on a wrapper script that signs a user via a SAML identity provider and gets user’s public key signed for the further usage.
In one of the discussions I pointed out that such a wrapper script is not good for the end user experience and I proposed to provide the users with an excerpt for their ssh config file, so the functionality of sshephalopod would be transparent to the general usage scenario of the ssh tool.
The response was that ProxyCommand do not support interactivity. Well, as they say: The challenge is accepted :)
The following is my train of thoughts before I came up with a general solution on how to allow an interactive command to be used as the ProxyCommand in the ssh config file.
Before we start solving the problem at hand we need to create a test environment, so we would be able to confirm when we reached success. The task itself was very simple: we needed a host we could ssh into (an sshd daemon running on the local host would be sufficient), then we needed an interactive script, and a configuration block for the connection.
The configuration block is pretty simple (%h expands to localhost and %p expands to the port specified on the command line or to “22” otherwise):
Since most of our research is going to be inside the interactive script you will see several incarnations of script’s body. The very first one was the following:
At this point we just need to confirm that our test environment works as expected – the ssh session should be proxied through the nc command and we should be able to login under our own account via ssh to the localhost (my private key was added to the key manager with ssh-add, hence no password prompt was displayed):
OK, we confirmed that we can establish a proxied connection and tunnel our ssh session through it.
Each interactive script or program relies on the communication channel with the user otherwise it could not be interactive. This channel comprises at least of two file descriptors: one for standard input and the other for standard output, so let’s check what descriptors are available for our script:
If we try to connect now we should see something like the following (I am writing this article on an OS X machine so I provide the output from OS X, however this also works for Linux):
We are interested in file descriptors 0 (standard input), 1 (standard output), and 2 (standard error). As you can see the standard input and output are part of the pipes (presumably linking them to the parent ssh process) and standard error is pointing to our terminal session.
I could have occupied a bit more of the page space showcasing that if you try to communicate on standard input and/or output the ssh client will terminate since you will be messing with the SSH protocol flow, but I believe you will trust me on this :).
What can we do to interact with the user, yet to preserve the channel with the parent ssh process? Well, the answer is quite obvious:
- we have a pointer to the terminal session (file descriptor 2, the standard error, points to the terminal),
- so we just need to save pointers to the pipes’ ends,
- re-open standard input and output with the terminal before we interact with the user,
- and restore these file descriptors back once we are ready to hand over the ssh session.
The following version of the script demonstrates the implementation of the above logic:
I think it is time to test it :) :
Mission accomplished! :)
I hope this small article would help somebody to design better wrappers around SSH. Keep in mind that you could always optimise it further. For example, recent versions of OpenSSH support passing of a file descriptor from the ProxyCommand script, so if you have a decent netcat tool that supports the “-F” option (fdpass) you could get native performance for the ssh communication link with no proxy process hanging around.
P.S. if you have any questions do not hesitate to comment.