package auth import ( "bytes" "errors" "strings" "time" "golang.org/x/crypto/ssh" "git.maze.io/maze/conduit/logger" ) var ErrUnauthorized = errors.New("unauthorized") // Provider implements zero or more of the [Password], [PublicKey] interfaces. type Provider any // Password authenticator. type Password interface { VerifyPassword(meta ssh.ConnMetadata, password string) (Principal, error) } // Token authenticator. type Token interface { Instruction() string Prompt() string // Multiline accepts multiline input and wait for the user to supply // an empty line. Multiline() bool VerifyToken(meta ssh.ConnMetadata, token string) (Principal, error) } // PublicKey authenticator. type PublicKey interface { VerifyPublicKey(meta ssh.ConnMetadata, publicKey ssh.PublicKey) (Principal, error) } type CertificateAuthority struct { checker *ssh.CertChecker keys []ssh.PublicKey blob [][]byte user bool host bool } func NewUserCertificateAuthority(keys ...ssh.PublicKey) *CertificateAuthority { var blob [][]byte for _, key := range keys { blob = append(blob, key.Marshal()) } auth := &CertificateAuthority{ checker: &ssh.CertChecker{ Clock: time.Now, }, keys: keys, blob: blob, user: true, } auth.checker.IsUserAuthority = auth.isUserAuthority return auth } func (auth *CertificateAuthority) isUserAuthority(key ssh.PublicKey) bool { if !auth.user { return false } blob := key.Marshal() for _, other := range auth.blob { if bytes.Equal(blob, other) { return true } } return false } func (auth *CertificateAuthority) VerifyPublicKey(meta ssh.ConnMetadata, publicKey ssh.PublicKey) (Principal, error) { log := logger.StandardLog.Values(logger.Values{ "client": meta.RemoteAddr().String(), "key": strings.TrimSpace(string(ssh.MarshalAuthorizedKey(publicKey))), "user": meta.User(), "version": string(meta.ClientVersion()), }) log.Debug("Verifying user certificate") _, err := auth.checker.Authenticate(meta, publicKey) if err != nil { return nil, err } cert := publicKey.(*ssh.Certificate) return UserCertificatePrincipal{cert}, nil }