
Building Interactive SSH Applications - stargrave
https://drewdevault.com/2019/09/02/Interactive-SSH-programs.html
======
tastroder
Neat. I was always wary about unintended side-effects of doing something like
this in the main openssh server instance because I did not trust myself to
configure everything securely enough without accidentally exposing more than I
wanted. While more work than the solution in the blog post, what worked well
for me was using [0] to implement a separate server that only exposes whatever
you explicitly want it to. It turned out to be pretty feature complete for my
use cases (including certificate logins, SFTP, exec and working fine with
curses-like applications).

[0] [https://github.com/mscdex/ssh2](https://github.com/mscdex/ssh2)

~~~
unixhero
Nice github username :-)

~~~
tastroder
Just for the record, that's not my library, I just used it in a few projects.
The author is on HN though.

------
spin
An interesting, useful real-world example of this is Gitolite [0]. Gitolite
requires no background daemons or anything -- just a normal unix/ssh
environment.

[0]
[https://gitolite.com/gitolite/index.html](https://gitolite.com/gitolite/index.html)

~~~
mobilio
I'm using gitolite from years and works flawless.

~~~
brachi
I just started using it and I love it. Simple, easy to configure, and it just
works.

------
jrockway
Interesting approach. I have had good luck with the go ssh library; and that
server doesn't require you to deal with any UNIX internals. You get a channel
that you can read and write to; so if you just want to implement nethack, and
your nethack library has something like "func (*nethack) Command(string)
string", then you feed their lines (perhaps parsed with bufio.Scanner) into
that, and send the results back to the channel. Thus, very little can go
wrong. Some system configuration changes, and there is no way that access can
escalate to shell access.

The reason I'm familiar with the client was to automate connections to network
hardware. Basically, we have a rotated or per-user password stored somewhere
secure; the ssh client contacts that server, gets the credentials, and then
creates a connection to the actual device. The ssh client believes that
passwords are insecure and should never be used, so doesn't let you
programatically pipe one in. My solution? Just rewrite the ssh client to do
what I want. golang.org/x/crypto/ssh made it simple, and users don't even know
that they're using a fake SSH client, as I copied the text from the real SSH
client verbatim (for accepting host keys and whatnot). As for ssh servers; I
had to write one to test the client.

The only thing to beware of is that you need to manage the user's tty; if you
haven't used a tty in raw mode before it will be a new experience. And the
library has a bunch of places where waits are unbounded. So you need to do
anything that blocks in its own goroutine that kills the connection when
<-ctx.Done returns. Or accept indefinite blocking, as most programs seem to be
happy with. Other than that, I like it a lot. Seems safer to me to just write
the program you want, rather than to deal with 9000 historical edge cases in
the opensshd+unix combination. PAM is also not involved ever, which is a great
improvement to everyone's life.

------
mbreese
I want to say that prgmr.com used (uses?) something like this for out of band
access to VMs for serial console access or reboots, etc [0]. It certainly can
be a powerful tool to give someone limited access to infrastructure.

I swear I read about this in a blog post, but that was many years ago and I
couldn't quickly find it.

[0]
[https://wiki.prgmr.com/mediawiki/index.php/Management_Consol...](https://wiki.prgmr.com/mediawiki/index.php/Management_Console)

~~~
sn
We still do this, though the implementation has had a complete rewrite. It's
very accessible.

~~~
peatfreak
What do you mean by "very accessible"? Are you referring to the design and
implementation of this interaction technique? Or the accessibility of the UI?

~~~
mbreese
Probably meaning the amount of access you get to configure your VM. I’ve never
had a need for one from them, but the amount of configuration they describe in
their documentation from an SSH/CLI to your VM is pretty comprehensive. It’s a
slick way to avoid the overhead and infrastructure needed to support a big web
GUI.

You can pretty much see the entire interface from the link above.

------
muxator
> def tail(job_id, info):

> logs = os.path.join(cfg("builds.sr.ht::worker", "buildlogs"), str(job_id))

Depending on where your job_id comes from, you may want to ensure "logs"
variable still ends up pointing a subdirectory of buildlogs. You could use a
combination of os.path.abspath() and various methods for checking the common
prefix.

Maybe this is only a simplified snippet, but concatenating values with
external parameters without checking ng the end result can lead to unpleasant
surprises in production code.

~~~
emersion
I believe job_id is an int in this case. Buy yeah, it doesn't hurt to double-
check, just in case it somehow gets called with a user-supplied variable at
some point.

------
fimdomeio
So can we have a BBS revival now? :) I wonder what are the differences that
make vinyl have a revival but not bbs via ssh.

~~~
snazz
A wrote a little one in Perl a few years ago. The primary challenges were the
same ones you would have with any curses application and annoying input
latency. The users were also constantly trying to escape to a shell, but I
don’t remember any of them succeeding.

If you thought websites were centralized, try a system where every keystroke
gets mirrored to the server!

~~~
icedchai
To think, interactive curses apps like that used to be the norm. It was much
simpler building apps that way, compared to web apps. I rarely used curses
when I built my own stuff in the 90's. I generated ANSI escape sequences
myself...

------
jandeboevrie
Be careful with the authorized keys file approach. If the commands allow the
user to append content to it, the commands can be changed. Most of the time
the file is writable by the user (unless you specify otherwise in the opensshd
configuration)

------
megous
Possibly a lot of shell command interpolation going on there.

What if I try to login as "bob'; rm -rf /" or some such? Is the system robust
against that?

~~~
mbrock
Where do you see the problematic interpolation?

~~~
targonca

        keys = (f"command=\"buildsrht-shell '{b64key}'\",restrict,pty " +
                f"{key_type} {b64key} somebody\n")
    

Now it depends on how b64key is sanitized.

------
gumby
Note that if your process is the root of the process tree controlling a
terminal, init will prepend a '-' to argv[0] (e.g. instead of being "bash" it
will be "-bash".

I used this to disable c-X c-c (the exit command) when running gnu emacs as my
login shell in the mid 1980s.

------
zokier
It'd be great to have more discussion how to build applications to be exposed
like this, especially the security aspect; what sort of sandboxing to use etc
to minimize attack surface. Basically how to avoid giving users shell, and how
to restrict what they can do even if they did gain code execution of some
sort.

The web application security practices are pretty well known and lot of that
does apply here too. But lot is different too, crucially I imagine each user
session is it's own process which means you can apply more OS level primitives
to them. chroot would be an obvious example, but there is tons of more things
available.

~~~
CameronNemo
Yes all container technologies are at play, including namespaces, cgroups (for
resource quotas), seccomp, Linux Security Modules, and probably some odds and
ends I am forgetting.

Of course e.g. FreeBSD is a different bag.

------
FreeHugs

        Try to avoid the use of ‘IN’ or ‘NOT IN’. By
        doing this you are performing a full table scan
        as the query engine looks through every row to
        check if the condition is met.
    

Is that really true for PostgreSQL? Take this query for example:

    
    
       SELECT * FROM t WHERE id IN (1,2,3)
    

I do this often in MySql DBs. I am pretty sure it uses an index on "id" if one
exists. EXPLAIN confirms this. Why would a DB engine _not_ use an index?

~~~
nyuszika7h
How is that relevant to this post? I don't see the text you quoted there.

~~~
kuriho
Most likely meant as a comment for
[https://news.ycombinator.com/item?id=20855441](https://news.ycombinator.com/item?id=20855441)

