ssh/tailssh: try out new AuthBanner API

Uses https://go-review.googlesource.com/c/crypto/+/613856

DO NOT MERGE

Change-Id: I0083fe34015e2ba39374ee58deae68c112b24750
Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann
2024-11-01 08:48:09 -05:00
committed by Brad Fitzpatrick
parent 49de23cf1b
commit 487470ea47
26 changed files with 176 additions and 124 deletions

View File

@@ -7,7 +7,7 @@ import (
"path"
"sync"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
const (

View File

@@ -6,7 +6,7 @@ import (
"net"
"sync"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
// contextKey is a value for use with context.WithValue. It's used as
@@ -121,7 +121,6 @@ func applyConnMetadata(ctx Context, conn gossh.ConnMetadata) {
ctx.SetValue(ContextKeyUser, conn.User())
ctx.SetValue(ContextKeyLocalAddr, conn.LocalAddr())
ctx.SetValue(ContextKeyRemoteAddr, conn.RemoteAddr())
ctx.SetValue(ContextKeySendAuthBanner, conn.SendAuthBanner)
}
func (ctx *sshContext) SetValue(key, value interface{}) {

View File

@@ -3,7 +3,7 @@ package ssh
import (
"os"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
// PasswordAuth returns a functional option that sets PasswordHandler on the server.

View File

@@ -8,7 +8,7 @@ import (
"sync/atomic"
"testing"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
func newTestSessionWithOptions(t *testing.T, srv *Server, cfg *gossh.ClientConfig, options ...Option) (*gossh.Session, *gossh.Client, func()) {

View File

@@ -8,7 +8,7 @@ import (
"sync"
"time"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
// ErrServerClosed is returned by the Server's Serve, ListenAndServe,
@@ -134,45 +134,97 @@ func (srv *Server) config(ctx Context) *gossh.ServerConfig {
config.ServerVersion = "SSH-2.0-" + srv.Version
}
if srv.PasswordHandler != nil {
config.PasswordCallback = func(conn gossh.ConnMetadata, password []byte) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.PasswordHandler(ctx, string(password)); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
config.PasswordCallback = passwordCallback(ctx, srv.PasswordHandler)
}
if srv.PublicKeyHandler != nil {
config.PublicKeyCallback = func(conn gossh.ConnMetadata, key gossh.PublicKey) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if err := srv.PublicKeyHandler(ctx, key); err != nil {
return ctx.Permissions().Permissions, err
}
ctx.SetValue(ContextKeyPublicKey, key)
return ctx.Permissions().Permissions, nil
}
config.PublicKeyCallback = publicKeyCallback(ctx, srv.PublicKeyHandler)
}
if srv.KeyboardInteractiveHandler != nil {
config.KeyboardInteractiveCallback = func(conn gossh.ConnMetadata, challenger gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := srv.KeyboardInteractiveHandler(ctx, challenger); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
config.KeyboardInteractiveCallback = keyboardInteractiveCallback(ctx, srv.KeyboardInteractiveHandler)
}
if srv.NoClientAuthHandler != nil {
config.NoClientAuthCallback = func(conn gossh.ConnMetadata) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if err := srv.NoClientAuthHandler(ctx); err != nil {
return ctx.Permissions().Permissions, err
}
return ctx.Permissions().Permissions, nil
}
config.NoClientAuthCallback = noClientAuthCallback(ctx, srv.NoClientAuthHandler)
}
config.PreAuthConnCallback = func(pac gossh.ServerPreAuthConn) {
ctx.SetValue(ContextKeySendAuthBanner, pac.SendAuthBanner)
}
return config
}
func passwordCallback(ctx Context, h PasswordHandler) func(gossh.ConnMetadata, []byte) (*gossh.Permissions, error) {
return func(conn gossh.ConnMetadata, password []byte) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := h(ctx, string(password)); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
}
func publicKeyCallback(ctx Context, h PublicKeyHandler) func(gossh.ConnMetadata, gossh.PublicKey) (*gossh.Permissions, error) {
return func(conn gossh.ConnMetadata, key gossh.PublicKey) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if err := h(ctx, key); err != nil {
return ctx.Permissions().Permissions, adaptPartialSuccessError(err)
}
ctx.SetValue(ContextKeyPublicKey, key)
return ctx.Permissions().Permissions, nil
}
}
func keyboardInteractiveCallback(ctx Context, h KeyboardInteractiveHandler) func(conn gossh.ConnMetadata, client gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) {
return func(conn gossh.ConnMetadata, challenger gossh.KeyboardInteractiveChallenge) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if ok := h(ctx, challenger); !ok {
return ctx.Permissions().Permissions, fmt.Errorf("permission denied")
}
return ctx.Permissions().Permissions, nil
}
}
func noClientAuthCallback(ctx Context, h NoClientAuthHandler) func(gossh.ConnMetadata) (*gossh.Permissions, error) {
return func(conn gossh.ConnMetadata) (*gossh.Permissions, error) {
applyConnMetadata(ctx, conn)
if err := h(ctx); err != nil {
return ctx.Permissions().Permissions, adaptPartialSuccessError(err)
}
return ctx.Permissions().Permissions, nil
}
}
func adaptPartialSuccessError(err error) error {
fmt.Printf("Adapt? error %q of type %T\n", err, err)
if err == nil {
return nil
}
pse := &PartialSuccessError{}
if errors.As(err, &pse) {
adapted := &gossh.PartialSuccessError{}
if pse.PasswordHandler != nil {
adapted.Next.PasswordCallback = passwordCallback(pse.Context, pse.PasswordHandler)
}
if pse.PublicKeyHandler != nil {
adapted.Next.PublicKeyCallback = publicKeyCallback(pse.Context, pse.PublicKeyHandler)
}
if pse.KeyboardInteractiveHandler != nil {
adapted.Next.KeyboardInteractiveCallback = keyboardInteractiveCallback(pse.Context, pse.KeyboardInteractiveHandler)
}
return adapted
}
return err
}
type PartialSuccessError struct {
Context Context
PasswordHandler PasswordHandler
PublicKeyHandler PublicKeyHandler
KeyboardInteractiveHandler KeyboardInteractiveHandler
}
func (p *PartialSuccessError) Error() string {
return "ssh: authenticated with partial success"
}
// Handle sets the Handler for the server.
func (srv *Server) Handle(fn Handler) {
srv.mu.Lock()

View File

@@ -9,7 +9,7 @@ import (
"sync"
"github.com/anmitsu/go-shlex"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
// Session provides access to information about an SSH session and methods

View File

@@ -9,7 +9,7 @@ import (
"net"
"testing"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
func (srv *Server) serveOnce(l net.Listener) error {

View File

@@ -4,7 +4,7 @@ import (
"crypto/subtle"
"net"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
type Signal string
@@ -105,7 +105,7 @@ type Pty struct {
// requested by the client as part of the pty-req. These are outlined as
// part of https://datatracker.ietf.org/doc/html/rfc4254#section-8.
//
// The opcodes are defined as constants in github.com/tailscale/golang-x-crypto/ssh (VINTR,VQUIT,etc.).
// The opcodes are defined as constants in golang.org/x/crypto/ssh (VINTR,VQUIT,etc.).
// Boolean opcodes have values 0 or 1.
Modes gossh.TerminalModes
}

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"sync"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
const (

View File

@@ -10,7 +10,7 @@ import (
"strings"
"testing"
gossh "github.com/tailscale/golang-x-crypto/ssh"
gossh "golang.org/x/crypto/ssh"
)
var sampleServerResponse = []byte("Hello world")

View File

@@ -5,7 +5,7 @@ import (
"crypto/rsa"
"encoding/binary"
"github.com/tailscale/golang-x-crypto/ssh"
"golang.org/x/crypto/ssh"
)
func generateSigner() (ssh.Signer, error) {

View File

@@ -1,6 +1,6 @@
package ssh
import gossh "github.com/tailscale/golang-x-crypto/ssh"
import gossh "golang.org/x/crypto/ssh"
// PublicKey is an abstraction of different types of public keys.
type PublicKey interface {