Hacker News new | ask | show | jobs
by perbu 615 days ago
Well done.

The ease of use and high quality of the Go SSH libraries (golang.org/x/crypto/ssh) is a killer feature of Go, imho.

Also, there is a high level abstraction, github.com/gliderlabs/ssh, which makes it completely trivial to embed an ssh server into an application, giving you a nice way to inspect counters and flip feature flags and tuneables.

5 comments

The only major downside to golang.org/x/crypto/ssh is that open issues seem to linger for years lately, even when people try to submit patches. So it's often necessary to look for third-party solutions.

The knownhosts handling in particular has a bunch of common land-mines. I'm the maintainer of a wrapper package https://github.com/skeema/knownhosts/ which solves some of them, without having to re-implement the core knownhosts logic from x/crypto/ssh.

Just to illustrate how common these land-mines are, my wrapper package is imported by 8000 other repos on GitHub, although most of these are indirect dependencies: https://github.com/skeema/knownhosts/network/dependents

Another thing I want but is completely missing from golang.org/x/crypto/ssh is compression support: https://github.com/golang/go/issues/31369
I think in an ideal world, this would be the normal case. A hierarchy of packages, maintained by many independent parties, that extend useful base functionality, without too much logic being put in any one package. If one thing doesn't work well you can just create a new package to replace the one part. And building on top of simpler, smaller modules allows you to keep code DRY, reduce maintenance burden (like the 1000 open PRs...), and easily extend functionality by simply making a new package.

That was my experience with CPAN, anyway. It's not perfect but it's miles above other language module cultures.

The base functionality isn't always terribly extensible, though. And Go isn't like Perl or Ruby where you can monkey-patch arbitrary logic in a pinch.

I originally created my knownhosts wrapper to solve the problem of populating the list of host key algorithms based on the knownhosts content. Go's x/crypto/ssh provides no straightforward way to do this, as it keeps its host lookup logic largely internal, with no exported host lookup methods or interfaces. I had to find a slightly hacky and very counter-intuitive approach to get x/crypto/ssh to return that information without re-implementing it.

And to be clear, re-implementing core logic in x/crypto/ssh is very undesirable because this is security-related code.

Sometimes the hierarchy can be used without directly/perfectly extending the code. For example, in the CPAN world, you might publish your own module as "x/crypto/ssh/knownhosts/client". You don't even have to use the "x/crypto/ssh/knownhosts" code at all, it just looks like a similar namespace. (IIRC, CPAN requires a human in the loop who's moderating what new packages are listed; none of the craziness of PyPI where any insane person can release thousands of typosquatting malware modules)

You would hope a new module would reuse as much previous base modules as they can, but sometimes it's enough to just put some new code in that namespace, with the intent then that someone will find it easier, and build off of it. The hierarchy is for organization, discovery and distribution, as much as it is about good software development practice. The goal being to improve the overall software development ecosystem.

For critical security-related code, I'd argue that's not a good property at all for module namespacing! Quite the opposite. Even with a human in the loop.

(and I was a professional Perl programmer for the first 5 years of my career, so I'm not asserting this out of lack of familiarity with CPAN!)

That all said: I don't even think what you're saying about CPAN is terribly similar to the situation being discussed here, since Go's x/crypto/ssh (and all other x/ packages) are officially part of the Go Project and are maintained by the Go core maintainers. See https://pkg.go.dev/golang.org/x. Third-party Go developers cannot add new packages to this namespace at all.

I do not mean this as a loaded question, but what happens in this model when maintainers die?

Everything you've said sounds great, with the assumption that the maintainers can maintain their pieces indefinitely and independently. But we're mortal. And I know the independent maintainers in places like CPAN are humans, not companies.

I guess it's a sign you're getting old when you start worrying about this kind of thing

Assuming people want to keep using/maintaining the code, you just prove the original maintainer has either abandoned it or died, and then you contact the repository admins (i.e. CPAN). Make your case that the original maintainer is gone and they'll probably make you the new maintainer.

If nobody wants to maintain the old code, or the design wasn't ideal, often times people will create a "v2" or "-ng" rewrite of it and try to keep backwards compatibility. Then the people who made sub-modules can simply publish their modules on top of the new base module. Old code continues running with the old dependencies until somebody links the old code to the new base module.

How is performance?

We found the native Go SSL libraries (as used in, e.g. the http package natively) to add many ms to web api calls. We eventually substituted OpenSSL (despite not really wanting to). It significantly sped up the app.

YMMV, this is for ARM 32-bit targets.

I highly doubt that claim, maybe it's an ARM thing but there is no way that using the TLS package from Go add ms of processing on requests.

Did you tried with GOEXPERIMENT=boringcrypto ?

It is pretty good. Most of the CPU is spent on crypto, which is what you'd expect. The overhead is low enough that I've had no problems having rather meager machines handling thousands of concurrent connections.

If you're having performance issues with TLS I would look at what sort of crypto you're using. At least for SSH, RSA is dog slow. It wouldn't surprise me if you can irk out quite a bit of performance by switching to ed25519.

Agreed. There's also cool apps you can build with things like https://github.com/charmbracelet/wish
Definitely... first became roughly aware of it with the doorparty connector service[1]. Which is a niche fit, but definitely was cool to see how it worked.

1. https://github.com/echicken/dpc2/

I'm curious what are some prototypical use cases for you to embed an ssh sever into an application?
I work for a C++ company but the game we work on has a debug telnet server. It’s super useful to inspect state or even run automation scripts. Also has a bunch of useful debug commands like the ability to live reload shaders or change how various subsystems work.
[redacted for accuracy]
Going through the code, I couldn't find a server but only usage of ssh client. May be I missed it. But I think GP was looking for usecases where its helpful to run an embedded ssh server using a go binary.

Ansible facts can probably be a cross platform way to collect most of the information you need. For the usecases where scp'ng the binary is needed, I think ansible supports jumphost config too. But I agree that for one off tasks, running a single binary is convenient compared to setting up ansible.

Oop - you're right, I missed that they wanted server examples specifically. Thanks for the save.