Support user certificate based authentication on SFTP (#19650)

This commit is contained in:
Olli Janatuinen 2024-05-07 08:41:25 +02:00 committed by GitHub
parent 6a15580817
commit b413ff9fdb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 0 deletions

View File

@ -18,6 +18,7 @@
package cmd
import (
"bytes"
"context"
"crypto/subtle"
"errors"
@ -162,6 +163,7 @@ func startSFTPServer(args []string) {
port int
publicIP string
sshPrivateKey string
userCaKeyFile string
)
allowPubKeys := supportedPubKeyAuthAlgos
allowKexAlgos := preferredKexAlgos
@ -197,6 +199,8 @@ func startSFTPServer(args []string) {
allowCiphers = filterAlgos(arg, strings.Split(tokens[1], ","), supportedCiphers)
case "mac-algos":
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
case "trusted-user-ca-key":
userCaKeyFile = tokens[1]
}
}
@ -278,6 +282,56 @@ func startSFTPServer(args []string) {
},
}
if userCaKeyFile != "" {
keyBytes, err := os.ReadFile(userCaKeyFile)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not accessible: %v", err), "unable to start SFTP server")
}
caPublicKey, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
if err != nil {
logger.Fatal(fmt.Errorf("invalid arguments passed, trusted user certificate authority public key file is not parseable: %v", err), "unable to start SFTP server")
}
sshConfig.PublicKeyCallback = func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
_, ok := globalIAMSys.GetUser(context.Background(), c.User())
if !ok {
return nil, errNoSuchUser
}
// Verify that client provided certificate, not only public key.
cert, ok := key.(*ssh.Certificate)
if !ok {
return nil, errSftpPublicKeyWithoutCert
}
// ssh.CheckCert called by ssh.Authenticate accepts certificates
// with empty principles list so we block those in here.
if len(cert.ValidPrincipals) == 0 {
return nil, errSftpCertWithoutPrincipals
}
// Verify that certificate provided by user is issued by trusted CA,
// username in authentication request matches to identities in certificate
// and that certificate type is correct.
checker := ssh.CertChecker{}
checker.IsUserAuthority = func(k ssh.PublicKey) bool {
return bytes.Equal(k.Marshal(), caPublicKey.Marshal())
}
_, err = checker.Authenticate(c, key)
if err != nil {
return nil, err
}
return &ssh.Permissions{
CriticalOptions: map[string]string{
"accessKey": c.User(),
},
Extensions: make(map[string]string),
}, nil
}
}
sshConfig.AddHostKey(private)
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {

View File

@ -119,3 +119,9 @@ var errInvalidMaxParts = errors.New("Part number is greater than the maximum all
// error returned for session policies > 2048
var errSessionPolicyTooLarge = errors.New("Session policy should not exceed 2048 characters")
// error returned in SFTP when user used public key without certificate
var errSftpPublicKeyWithoutCert = errors.New("public key authentication without certificate is not accepted")
// error returned in SFTP when user used certificate which does not contain principal(s)
var errSftpCertWithoutPrincipals = errors.New("certificates without principal(s) are not accepted")

View File

@ -242,3 +242,16 @@ hmac-sha1
hmac-sha1-96
```
### Certificate-based authentication
`--sftp=trusted-user-ca-key=...` specifies a file containing public key of certificate authority that is trusted
to sign user certificates for authentication.
Implementation is identical with "TrustedUserCAKeys" setting in OpenSSH server with exception that only one CA
key can be defined.
If a certificate is presented for authentication and has its signing CA key is in this file, then it may be
used for authentication for any user listed in the certificate's principals list.
Note that certificates that lack a list of principals will not be permitted for authentication using trusted-user-ca-key.
For more details on certificates, see the CERTIFICATES section in ssh-keygen(1).