Services over SSH
2024-10-21
Update 2024-10-23: My ISP at home decided to screw my IPv6 setup and now they don't know (or don't care) to fix it, so the examples on this article will be offline for the time being.
Intro
Most people use SSH for two main applications, maybe three. That is remote login and, to a lesser extent, remote command execution and secure tunneling. But the main implementation of the protocol, OpenSSH, has a lot more features and capabilities.
I had this idea long time ago of providing services over SSH. This is not new,
of course, The OpenBSD project does it for their anonymous CVS. Git
(and the plethora of software forges that support it) have the capability of
use SSH as a transport protocol. Same goes for Mercurial and many others.
I guess we're so used to that, that we don't even think about it.
Chances are that, if you transfered files via rsync
lately, it was over SSH
too.
SSH provides traffic encryption, but also authentication and identification which could be useful in some scenarios when providing a service.
So I though of a couple example applications:
- A sort of comment system over SSH. It could be used as a geeky way of having comments on a blog, for instance.
- A crude API, where each SSH call could resemble a call to a traditional REST API over HTTPS.
- An info panel for public transport time tables, announces, etc.
But any program that interacts with stdin
and stdout
could be set up as
a service to run over SSH.
Again, this is not new, there are BBSs running over SSH. And I know the people at Bitreich use that all the time for their fine services, to name some.
This article just provides examples of what can be done, so you can crate your own stuff.
Are they useful ? Probably not.
Would they have performance issues at scale ? For sure.
But this is just for fun :-)
Implementation
The key ingredient here is the ForceCommand
configuration keyword for the SSH
server.
According to the man page:
ForceCommand
Forces the execution of the command specified by ForceCommand
ignoring any command supplied by the client and ~/.ssh/rc if
present ...
The idea would be to have a user per service, and a block like this on
sshd_config
:
Match User anonsvc
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
AllowAgentForwarding no
PermitEmptyPasswords yes
PasswordAuthentication yes
ForceCommand /usr/local/bin/test_ssh_service
That would allow connection with an empty password. Useful for a public,
"anonymous" service. At the same time, it restricts what this user can do to
only execute the command referenced in ForceCommand
.
One could even pass "commands" or "instructions" to the service program. Again, according to the man page:
The command originally supplied by the client is available in the
SSH_ORIGINAL_COMMAND environment variable.
So one could parse that environment variable to get all sorts of data from it.
Stretching a bit the silly idea I pointed out before of a crude API, a client
could execute ssh srv1@myhost.tld getItem 1234
.
The SSH_ORIGINAL_COMMAND
environment variable when the SSH server executes
the ForceCommand
process, would contain getItem 1234
. The process could
parse that data and return to stdout
the information about that item.
If, instead of PermitEmptyPasswords yes
, we go with the default, or even
allow only connections with SSH keys, one can have an authenticated
service. Using the AuthorizedKeysCommand
keyword, one can define an alternate
command to get the authorized SSH keys, instead of the traditional
$HOME/.ssh/authorized_keys
file. That program could look up on a relational
database or whatever you fancy. This is kind of important when one has a large
number of SSH keys to authorize, as the traditional authorized keys file would
grow quite large, and it would be a pain to maintain.
The program referenced on AuthorizedKeysCommand
only has to return zero or
more SSH keys one per line. One neat trick is to pass the fingerprint of the
key offered by the SSH client to the server to the command, so it can filter
out the key we're interested in, so the command returns just one key instead of
the potentially many that would be stored on the database or what would be read
from a file. To accomplish that, one can pass the %f
token to the command
(which will expand to the key fingerprint) in combination with the %u
token
(which expands to the user name). Like so:
AuthorizedKeysCommand /usr/local/bin/my_auth_command %u %f
In order for the ForceCommand
to work, the user must have a valid shell, as
the SSH server will execute the shell to force the command execution.
As we're exposing this to the internet, it's always nice to add a little bit of
extra protection.
This can be done via chroot(2)
. The SSH server has a config keyword for
that, ChrootDirectory
. The user will be locked up on the directory specified
by that config keyword.
In order to do this correctly, a set of requisites have to be met though.
- All components of the path will have to be owned by root and not writable by group or others.
- You'll most likely need some functioning devices inside the chroot. Check the man page for a list of those. How to generate them will depend on the OS you're using. On OpenBSD, use the MAKEDEV script.
- And, also, you'll need a working shell. As this is a jail with no access to
the system's shared libraries, the easiest thing to do is to get a statically
linked copy of your shell of choice. On OpenBSD,
ksh(1)
in base is statically linked, so just copy it into the jail.
Finally, add the necessary config to the Match
block from before:
ChrootDirectory /data/ssh_chroot
(of course, set there the path for the
chroot jail you prepared).
Keep in mind that all paths from that point are relative to the root of the
jail, not the root of the system, so you'll have to copy the command from
ForceCommand
into the jail too. And you'll need to compile it statically or
add to the jail any library or shared object your program expects to find.
The authorized_keys
file goes on the actual home of the user, not into the
jail, as the chroot happens AFTER authentication. Something to have in mind
too if one uses that for authentication.
Examples
Those are offline at the moment, see above.
I've prepared a couple of silly test services to demostrate all this. I'm not sure for how long I'll keep them online. They will fall victim of all kinds of attacks for sure, so if you find they don't work is because I got tired of blocking abusers.
The first one is as basic as it gets. Anonymous service, no password or key required. It prints a message and closes the connection:
ssh anonsvc@playground.e1e0.net
(IPv6 only. Sorry, not sorry).
You should see something like this:
Hi stranger !
I see you're calling from xxxx:xxxx:xxxx:xxxx::xxxx
Is it nice over there ?
Here, there's a UUID for you. Have fun.
912daa33-34c8-49fa-96f1-28d8858c8d96
Connection to playground.e1e0.net closed.
Try to send also some commands when making the connection. The message will change a bit.
The other one is a bit more elaborate. It requires SSH key authentication so, if you want to try it, send me an email with an SSH public key and I'll authorize you. But basically is the minimum implementation of the comment service I mentioned before. It goes something like this:
$ ssh comments@playground.e1e0.net j2afx8 my_nick
Hi my_nick !
Write here your message.
Lines should be less than 256 chars long and there's a max of 50 lines
To end the comment. Insert a line with a single period '.'
Hey !
This is quite silly !
.
Your comment has been saved. Thanks !
The program expects an "article ID" as the first argument and a nick as the second one. After that, interatively asks for a message. End with a line starting with a dot.
The comment will be added to the article in a text file:
---
Host: xxxx:xxxx:xxxx:xxxx::xxxx
Author: my_nick
Date: 2024-10-21 17:27:29 UTC
Hey !
This is quite silly !
I did not bother to fully implement this and publish the comments on the web, but you can see how it could work.
Conclusion
And there you have it. A way to expend your afternoon :-)
Please be aware of what you expose to the internet and the consequences of it.
Have fun.
Have any comments ? Send an email to the comments address.