From 4bc5ed6c76db7c04eaaa9c22373853034fcd54c4 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 6 Dec 2023 04:31:35 -0800 Subject: [PATCH] support LDAP service accounts via SFTP, FTP logins (#18599) --- cmd/ftp-server-driver.go | 115 ++++++++++++++++++++++---------------- cmd/sftp-server-driver.go | 99 +++++++++++++++++--------------- cmd/sftp-server.go | 40 +++++++++---- 3 files changed, 150 insertions(+), 104 deletions(-) diff --git a/cmd/ftp-server-driver.go b/cmd/ftp-server-driver.go index f62775e19..ebbd07fd6 100644 --- a/cmd/ftp-server-driver.go +++ b/cmd/ftp-server-driver.go @@ -248,12 +248,19 @@ func (driver *ftpDriver) CheckPasswd(c *ftp.Context, username, password string) defer stopFn(err) if globalIAMSys.LDAPConfig.Enabled() { - ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(username, password) - if err != nil { + sa, _, err := globalIAMSys.getServiceAccount(context.Background(), username) + if err != nil && !errors.Is(err, errNoSuchServiceAccount) { return false, err } - ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, groupDistNames...) - return len(ldapPolicies) > 0, nil + if errors.Is(err, errNoSuchServiceAccount) { + ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(username, password) + if err != nil { + return false, err + } + ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, groupDistNames...) + return len(ldapPolicies) > 0, nil + } + return subtle.ConstantTimeCompare([]byte(sa.Credentials.SecretKey), []byte(password)) == 1, nil } ui, ok := globalIAMSys.GetUser(context.Background(), username) @@ -269,58 +276,70 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) return nil, errNoSuchUser } if !ok && globalIAMSys.LDAPConfig.Enabled() { - targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(ctx.Sess.LoginUser()) - if err != nil { - return nil, err - } - ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...) - if len(ldapPolicies) == 0 { - return nil, errAuthentication - } - expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("") - if err != nil { - return nil, err - } - claims := make(map[string]interface{}) - claims[expClaim] = UTCNow().Add(expiryDur).Unix() - claims[ldapUser] = targetUser - claims[ldapUserN] = ctx.Sess.LoginUser() - - cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey) - if err != nil { + sa, _, err := globalIAMSys.getServiceAccount(context.Background(), ctx.Sess.LoginUser()) + if err != nil && !errors.Is(err, errNoSuchServiceAccount) { return nil, err } - // Set the parent of the temporary access key, this is useful - // in obtaining service accounts by this cred. - cred.ParentUser = targetUser + var mcreds *credentials.Credentials + if errors.Is(err, errNoSuchServiceAccount) { + targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(ctx.Sess.LoginUser()) + if err != nil { + return nil, err + } + ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...) + if len(ldapPolicies) == 0 { + return nil, errAuthentication + } + expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("") + if err != nil { + return nil, err + } + claims := make(map[string]interface{}) + claims[expClaim] = UTCNow().Add(expiryDur).Unix() + claims[ldapUser] = targetUser + claims[ldapUserN] = ctx.Sess.LoginUser() - // Set this value to LDAP groups, LDAP user can be part - // of large number of groups - cred.Groups = targetGroups + cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey) + if err != nil { + return nil, err + } - // Set the newly generated credentials, policyName is empty on purpose - // LDAP policies are applied automatically using their ldapUser, ldapGroups - // mapping. - updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "") - if err != nil { - return nil, err + // Set the parent of the temporary access key, this is useful + // in obtaining service accounts by this cred. + cred.ParentUser = targetUser + + // Set this value to LDAP groups, LDAP user can be part + // of large number of groups + cred.Groups = targetGroups + + // Set the newly generated credentials, policyName is empty on purpose + // LDAP policies are applied automatically using their ldapUser, ldapGroups + // mapping. + updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "") + if err != nil { + return nil, err + } + + // Call hook for site replication. + logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{ + Type: madmin.SRIAMItemSTSAcc, + STSCredential: &madmin.SRSTSCredential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + SessionToken: cred.SessionToken, + ParentUser: cred.ParentUser, + }, + UpdatedAt: updatedAt, + })) + + mcreds = credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken) + } else { + mcreds = credentials.NewStaticV4(sa.Credentials.AccessKey, sa.Credentials.SecretKey, "") } - // Call hook for site replication. - logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{ - Type: madmin.SRIAMItemSTSAcc, - STSCredential: &madmin.SRSTSCredential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - SessionToken: cred.SessionToken, - ParentUser: cred.ParentUser, - }, - UpdatedAt: updatedAt, - })) - return minio.New(driver.endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken), + Creds: mcreds, Secure: globalIsTLS, Transport: globalRemoteFTPClientTransport, }) diff --git a/cmd/sftp-server-driver.go b/cmd/sftp-server-driver.go index c39c9b232..9329e35ee 100644 --- a/cmd/sftp-server-driver.go +++ b/cmd/sftp-server-driver.go @@ -93,55 +93,66 @@ func (f *sftpDriver) getMinIOClient() (*minio.Client, error) { return nil, errNoSuchUser } if !ok && globalIAMSys.LDAPConfig.Enabled() { - targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(f.AccessKey()) - if err != nil { + sa, _, err := globalIAMSys.getServiceAccount(context.Background(), f.AccessKey()) + if err != nil && !errors.Is(err, errNoSuchServiceAccount) { return nil, err } - expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("") - if err != nil { - return nil, err + var mcreds *credentials.Credentials + if errors.Is(err, errNoSuchServiceAccount) { + targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(f.AccessKey()) + if err != nil { + return nil, err + } + expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("") + if err != nil { + return nil, err + } + claims := make(map[string]interface{}) + claims[expClaim] = UTCNow().Add(expiryDur).Unix() + for k, v := range f.permissions.CriticalOptions { + claims[k] = v + } + + cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey) + if err != nil { + return nil, err + } + + // Set the parent of the temporary access key, this is useful + // in obtaining service accounts by this cred. + cred.ParentUser = targetUser + + // Set this value to LDAP groups, LDAP user can be part + // of large number of groups + cred.Groups = targetGroups + + // Set the newly generated credentials, policyName is empty on purpose + // LDAP policies are applied automatically using their ldapUser, ldapGroups + // mapping. + updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "") + if err != nil { + return nil, err + } + + // Call hook for site replication. + logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{ + Type: madmin.SRIAMItemSTSAcc, + STSCredential: &madmin.SRSTSCredential{ + AccessKey: cred.AccessKey, + SecretKey: cred.SecretKey, + SessionToken: cred.SessionToken, + ParentUser: cred.ParentUser, + }, + UpdatedAt: updatedAt, + })) + + mcreds = credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken) + } else { + mcreds = credentials.NewStaticV4(sa.Credentials.AccessKey, sa.Credentials.SecretKey, "") } - claims := make(map[string]interface{}) - claims[expClaim] = UTCNow().Add(expiryDur).Unix() - for k, v := range f.permissions.CriticalOptions { - claims[k] = v - } - - cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey) - if err != nil { - return nil, err - } - - // Set the parent of the temporary access key, this is useful - // in obtaining service accounts by this cred. - cred.ParentUser = targetUser - - // Set this value to LDAP groups, LDAP user can be part - // of large number of groups - cred.Groups = targetGroups - - // Set the newly generated credentials, policyName is empty on purpose - // LDAP policies are applied automatically using their ldapUser, ldapGroups - // mapping. - updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "") - if err != nil { - return nil, err - } - - // Call hook for site replication. - logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{ - Type: madmin.SRIAMItemSTSAcc, - STSCredential: &madmin.SRSTSCredential{ - AccessKey: cred.AccessKey, - SecretKey: cred.SecretKey, - SessionToken: cred.SessionToken, - ParentUser: cred.ParentUser, - }, - UpdatedAt: updatedAt, - })) return minio.New(f.endpoint, &minio.Options{ - Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken), + Creds: mcreds, Secure: globalIsTLS, Transport: globalRemoteFTPClientTransport, }) diff --git a/cmd/sftp-server.go b/cmd/sftp-server.go index c5a521e24..fec85d290 100644 --- a/cmd/sftp-server.go +++ b/cmd/sftp-server.go @@ -20,6 +20,7 @@ package cmd import ( "context" "crypto/subtle" + "errors" "fmt" "net" "os" @@ -110,21 +111,36 @@ func startSFTPServer(c *cli.Context) { sshConfig := &ssh.ServerConfig{ PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { if globalIAMSys.LDAPConfig.Enabled() { - targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass)) - if err != nil { + sa, _, err := globalIAMSys.getServiceAccount(context.Background(), c.User()) + if err != nil && !errors.Is(err, errNoSuchServiceAccount) { return nil, err } - ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...) - if len(ldapPolicies) == 0 { - return nil, errAuthentication + if errors.Is(err, errNoSuchServiceAccount) { + targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass)) + if err != nil { + return nil, err + } + ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...) + if len(ldapPolicies) == 0 { + return nil, errAuthentication + } + return &ssh.Permissions{ + CriticalOptions: map[string]string{ + ldapUser: targetUser, + ldapUserN: c.User(), + }, + Extensions: make(map[string]string), + }, nil } - return &ssh.Permissions{ - CriticalOptions: map[string]string{ - ldapUser: targetUser, - ldapUserN: c.User(), - }, - Extensions: make(map[string]string), - }, nil + if subtle.ConstantTimeCompare([]byte(sa.Credentials.SecretKey), pass) == 1 { + return &ssh.Permissions{ + CriticalOptions: map[string]string{ + "accessKey": c.User(), + }, + Extensions: make(map[string]string), + }, nil + } + return nil, errAuthentication } ui, ok := globalIAMSys.GetUser(context.Background(), c.User())