ssh icon indicating copy to clipboard operation
ssh copied to clipboard

Make Context conform to "x/crypto/ssh".ConnMetadata interface

Open ransford-stripe opened this issue 5 years ago • 5 comments

This package's Context interface is almost, but not quite, identical to x/crypto/ssh's ConnMetadata interface. Seeing how similar they are, I wonder if the differences are unintentional.

I'm pretty sure anyone implementing certificate-based user authentication will run into this interface mismatch while trying to plumb connection details through to a CertChecker, because code like the simple example below fails to typecheck:

    // gossh refers to x/crypto/ssh
    // ssh refers to gliderlabs/ssh

    checker := gossh.CertChecker{
        IsUserAuthority: func(pub gossh.PublicKey) bool {
            // our own logic to check whether pub is acceptable
            return true
        }
    }

    func isUserCertOK(ctx ssh.Context, key ssh.PublicKey) bool {
        if cert, ok := key.(*gossh.Certificate); ok {
            // ctx must satisfy ConnMetadata or else this fails to compile
            _, err := checker.Authenticate(ctx, cert)
            return err == nil
        }
        // return isNonCertificateSshKeyOK(ctx, key)
    }

    sshServer := &ssh.Server{
        // ...
        PublicKeyHandler: isUserCertOK,
    }

This PR slightly changes the Context interface so that an sshContext conforms to ConnMetadata. Specifically:

  • Make ClientVersion(), ServerVersion(), and SessionID() return []byte instead of string
  • Add a type assertion to context_test.go

This is a breaking change, so I'll leave it to the maintainers to decide if and when to merge.

Thanks for this great library!

ransford-stripe avatar Oct 21 '19 21:10 ransford-stripe

Oof, yeah. I'm not sure what to do about this - lots of the things in this library were done for convenience and I assume the []byte to string was intentional, but @progrium may have a better idea.

Good call-out and solid PR, but you're right that it is a breaking change...

belak avatar Oct 21 '19 21:10 belak

Better to have a robust user base and to wring hands about breaking changes than to have no users at all!

FWIW, here is a minimal workaround that wraps this package's Context (as it exists in the latest master) just enough to pass as ConnMetadata:

type gContext ssh.Context
type gContextWrapper struct{ gContext }
func (s *gContextWrapper) ClientVersion() []byte { return []byte(s.gContext.ClientVersion()) }
func (s *gContextWrapper) ServerVersion() []byte { return []byte(s.gContext.ServerVersion()) }
func (s *gContextWrapper) SessionID() []byte {
	dec, _ := hex.DecodeString(s.gContext.SessionID())
	return dec
}
// now when you need a ConnMetadata but have only a ssh.Context, you can call:
// wctx := &gContextWrapper{ctx}
// _, err := checker.Authenticate(wctx, cert)

ransford-stripe avatar Oct 21 '19 21:10 ransford-stripe

One upside of returning a []byte rather than a string is it makes it clearer that the data is not necessarily display-safe. It's easy to inject all kinds of escape codes and whatnot in there, so sanitization is required.

shazow avatar Oct 22 '19 08:10 shazow

@belak this seems like it would pair nicely with #122 in a breaking-changes release tagged 0.3. @progrium did you have an opinion on this PR per Kaleb's comment?

ransford-stripe avatar Dec 16 '19 20:12 ransford-stripe

Yes, that seems reasonable. I’ll try to take another look later today.

belak avatar Dec 16 '19 21:12 belak