mirror of https://github.com/minio/minio.git
Support user certificate based authentication on SFTP (#19650)
This commit is contained in:
parent
6a15580817
commit
b413ff9fdb
|
@ -18,6 +18,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -162,6 +163,7 @@ func startSFTPServer(args []string) {
|
||||||
port int
|
port int
|
||||||
publicIP string
|
publicIP string
|
||||||
sshPrivateKey string
|
sshPrivateKey string
|
||||||
|
userCaKeyFile string
|
||||||
)
|
)
|
||||||
allowPubKeys := supportedPubKeyAuthAlgos
|
allowPubKeys := supportedPubKeyAuthAlgos
|
||||||
allowKexAlgos := preferredKexAlgos
|
allowKexAlgos := preferredKexAlgos
|
||||||
|
@ -197,6 +199,8 @@ func startSFTPServer(args []string) {
|
||||||
allowCiphers = filterAlgos(arg, strings.Split(tokens[1], ","), supportedCiphers)
|
allowCiphers = filterAlgos(arg, strings.Split(tokens[1], ","), supportedCiphers)
|
||||||
case "mac-algos":
|
case "mac-algos":
|
||||||
allowMACs = filterAlgos(arg, strings.Split(tokens[1], ","), supportedMACs)
|
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)
|
sshConfig.AddHostKey(private)
|
||||||
|
|
||||||
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
|
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
|
||||||
|
|
|
@ -119,3 +119,9 @@ var errInvalidMaxParts = errors.New("Part number is greater than the maximum all
|
||||||
|
|
||||||
// error returned for session policies > 2048
|
// error returned for session policies > 2048
|
||||||
var errSessionPolicyTooLarge = errors.New("Session policy should not exceed 2048 characters")
|
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")
|
||||||
|
|
|
@ -242,3 +242,16 @@ hmac-sha1
|
||||||
hmac-sha1-96
|
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).
|
||||||
|
|
Loading…
Reference in New Issue