From 0db1c94e7dd169772ab0e3710a6b84022b2c5f9c Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Tue, 20 Jul 2021 23:33:12 -0700 Subject: [PATCH] [IDP:LDAP] Cleanup creds for removed LDAP user entries (#12759) --- cmd/iam.go | 60 ++++++++++++++++++++++++- internal/config/identity/ldap/config.go | 56 +++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/cmd/iam.go b/cmd/iam.go index 97001075a..2f296c37a 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -650,14 +650,22 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) { break } - if globalOpenIDConfig.ProviderEnabled() { + // Set up polling for expired accounts and credentials purging. + switch { + case globalOpenIDConfig.ProviderEnabled(): go func() { - // Purge expired credentials for { time.Sleep(globalRefreshIAMInterval) sys.purgeExpiredCredentialsForExternalSSO(ctx) } }() + case globalLDAPConfig.EnabledWithLookupBind(): + go func() { + for { + time.Sleep(globalRefreshIAMInterval) + sys.purgeExpiredCredentialsForLDAP(ctx) + } + }() } go sys.store.watch(ctx, sys) @@ -1571,6 +1579,54 @@ func (sys *IAMSys) purgeExpiredCredentialsForExternalSSO(ctx context.Context) { sys.store.unlock() } +// purgeExpiredCredentialsForLDAP - validates if local credentials are still +// valid by checking LDAP server if the relevant users are still present. +func (sys *IAMSys) purgeExpiredCredentialsForLDAP(ctx context.Context) { + sys.store.lock() + parentUsersMap := make(map[string][]auth.Credentials, len(sys.iamUsersMap)) + parentUsers := make([]string, 0, len(sys.iamUsersMap)) + for _, cred := range sys.iamUsersMap { + if cred.IsServiceAccount() || cred.IsTemp() { + if globalLDAPConfig.IsLDAPUserDN(cred.ParentUser) { + if _, ok := parentUsersMap[cred.ParentUser]; !ok { + parentUsers = append(parentUsers, cred.ParentUser) + } + parentUsersMap[cred.ParentUser] = append(parentUsersMap[cred.ParentUser], cred) + } + } + } + sys.store.unlock() + + expiredUsers, err := globalLDAPConfig.GetNonExistentUserDNS(parentUsers) + if err != nil { + // Log and return on error - perhaps it'll work the next time. + logger.LogIf(GlobalContext, err) + return + } + + for _, expiredUser := range expiredUsers { + for _, cred := range parentUsersMap[expiredUser] { + userType := regUser + if cred.IsServiceAccount() { + userType = svcUser + } else if cred.IsTemp() { + userType = stsUser + } + sys.store.deleteIAMConfig(ctx, getUserIdentityPath(cred.AccessKey, userType)) + sys.store.deleteIAMConfig(ctx, getMappedPolicyPath(cred.AccessKey, userType, false)) + } + } + + sys.store.lock() + for _, user := range expiredUsers { + for _, cred := range parentUsersMap[user] { + delete(sys.iamUsersMap, cred.AccessKey) + delete(sys.iamUserPolicyMap, cred.AccessKey) + } + } + sys.store.unlock() +} + // GetUser - get user credentials func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) { if !sys.Initialized() { diff --git a/internal/config/identity/ldap/config.go b/internal/config/identity/ldap/config.go index 600b48de2..9e40b07b0 100644 --- a/internal/config/identity/ldap/config.go +++ b/internal/config/identity/ldap/config.go @@ -452,6 +452,62 @@ func (l Config) testConnection() error { return fmt.Errorf("LDAP connection test error: %w", err) } +// IsLDAPUserDN determines if the given string could be a user DN from LDAP. +func (l Config) IsLDAPUserDN(user string) bool { + return strings.HasSuffix(user, ","+l.UserDNSearchBaseDN) +} + +// GetNonExistentUserDNS - find user accounts that are no longer present in the +// LDAP server. +func (l *Config) GetNonExistentUserDNS(userDNS []string) ([]string, error) { + if !l.isUsingLookupBind { + return nil, errors.New("current LDAP configuration does not permit looking for expired user accounts") + } + + conn, err := l.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + // Bind to the lookup user account + if err = l.lookupBind(conn); err != nil { + return nil, err + } + + nonExistentUsers := []string{} + for _, dn := range userDNS { + searchRequest := ldap.NewSearchRequest( + dn, + ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, + "(objectclass=*)", + []string{}, // only need DN, so no pass no attributes here + nil, + ) + + searchResult, err := conn.Search(searchRequest) + if err != nil { + // Object does not exist error? + if ldap.IsErrorWithCode(err, 32) { + nonExistentUsers = append(nonExistentUsers, dn) + continue + } + return nil, err + } + if len(searchResult.Entries) == 0 { + // DN was not found - this means this user account is + // expired. + nonExistentUsers = append(nonExistentUsers, dn) + } + } + return nonExistentUsers, nil +} + +// EnabledWithLookupBind - checks if ldap IDP is enabled in lookup bind mode. +func (l Config) EnabledWithLookupBind() bool { + return l.Enabled && l.isUsingLookupBind +} + // Enabled returns if jwks is enabled. func Enabled(kvs config.KVS) bool { return kvs.Get(ServerAddr) != ""