From f5d2fbc84c6311826b75335b92c24e5227853daa Mon Sep 17 00:00:00 2001 From: Taran Pelkey Date: Thu, 11 Jul 2024 21:04:53 -0400 Subject: [PATCH] Add DecodeDN and QuickNormalizeDN functions to LDAP config (#20076) --- cmd/admin-handlers-idp-ldap.go | 19 +++---- cmd/iam-store.go | 71 ++++++++++++++++++++------- cmd/iam.go | 10 ++-- go.mod | 2 +- go.sum | 4 +- internal/config/identity/ldap/ldap.go | 18 +++++++ 6 files changed, 88 insertions(+), 36 deletions(-) diff --git a/cmd/admin-handlers-idp-ldap.go b/cmd/admin-handlers-idp-ldap.go index 97211bb5d..3a13504cb 100644 --- a/cmd/admin-handlers-idp-ldap.go +++ b/cmd/admin-handlers-idp-ldap.go @@ -552,7 +552,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. dnList = append(dnList, selfDN) } - accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp) + var ldapUserList []string if isAll { ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx) if err != nil { @@ -560,7 +560,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. return } for user := range ldapUsers { - accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{} + ldapUserList = append(ldapUserList, user) } } else { for _, userDN := range dnList { @@ -573,7 +573,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. if foundResult == nil { continue } - accessKeyMap[foundResult.NormDN] = madmin.ListAccessKeysLDAPResp{} + ldapUserList = append(ldapUserList, foundResult.NormDN) } } @@ -598,9 +598,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. return } - for dn, accessKeys := range accessKeyMap { + accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp) + for _, internalDN := range ldapUserList { + externalDN := globalIAMSys.LDAPConfig.DecodeDN(internalDN) + accessKeys := madmin.ListAccessKeysLDAPResp{} if listSTSKeys { - stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn) + stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, internalDN) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -614,13 +617,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. } // if only STS keys, skip if user has no STS keys if !listServiceAccounts && len(stsKeys) == 0 { - delete(accessKeyMap, dn) continue } } if listServiceAccounts { - serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, dn) + serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, internalDN) if err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return @@ -634,11 +636,10 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http. } // if only service accounts, skip if user has no service accounts if !listSTSKeys && len(serviceAccounts) == 0 { - delete(accessKeyMap, dn) continue } } - accessKeyMap[dn] = accessKeys + accessKeyMap[externalDN] = accessKeys } data, err := json.Marshal(accessKeyMap) diff --git a/cmd/iam-store.go b/cmd/iam-store.go index f1f7fab1e..b47d3509e 100644 --- a/cmd/iam-store.go +++ b/cmd/iam-store.go @@ -2090,7 +2090,7 @@ func (store *IAMStoreSys) GetAllSTSUserMappings(userPredicate func(string) bool) // Assumes store is locked by caller. If userMap is empty, returns all user mappings. func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[string]set.StringSet, - userPredicate func(string) bool, + userPredicate func(string) bool, decodeFunc func(string) string, ) []madmin.UserPolicyEntities { stsMap := xsync.NewMapOf[string, MappedPolicy]() resMap := make(map[string]madmin.UserPolicyEntities, len(userMap)) @@ -2098,9 +2098,13 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st for user, groupSet := range userMap { // Attempt to load parent user mapping for STS accounts store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap) - blankEntities := madmin.UserPolicyEntities{User: user} + decodeUser := user + if decodeFunc != nil { + decodeUser = decodeFunc(user) + } + blankEntities := madmin.UserPolicyEntities{User: decodeUser} if !groupSet.IsEmpty() { - blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil) + blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil, decodeFunc) } resMap[user] = blankEntities } @@ -2116,7 +2120,11 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st if len(userMap) > 0 { return true } - entitiesWithMemberOf = madmin.UserPolicyEntities{User: user} + decodeUser := user + if decodeFunc != nil { + decodeUser = decodeFunc(user) + } + entitiesWithMemberOf = madmin.UserPolicyEntities{User: decodeUser} } ps := mappedPolicy.toSlice() @@ -2155,7 +2163,7 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st // Assumes store is locked by caller. If groups is empty, returns all group mappings. func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet, - groupPredicate func(string) bool, + groupPredicate func(string) bool, decodeFunc func(string) string, ) []madmin.GroupPolicyEntities { var r []madmin.GroupPolicyEntities @@ -2168,10 +2176,15 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set return true } + decodeGroup := group + if decodeFunc != nil { + decodeGroup = decodeFunc(group) + } + ps := mappedPolicy.toSlice() sort.Strings(ps) r = append(r, madmin.GroupPolicyEntities{ - Group: group, + Group: decodeGroup, Policies: ps, }) return true @@ -2186,7 +2199,7 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set // Assumes store is locked by caller. If policies is empty, returns all policy mappings. func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet, - userPredicate, groupPredicate func(string) bool, + userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string, ) []madmin.PolicyEntities { policyToUsersMap := make(map[string]set.StringSet) cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool { @@ -2194,6 +2207,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St return true } + decodeUser := user + if decodeFunc != nil { + decodeUser = decodeFunc(user) + } + commonPolicySet := mappedPolicy.policySet() if !queryPolSet.IsEmpty() { commonPolicySet = commonPolicySet.Intersection(queryPolSet) @@ -2201,9 +2219,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St for _, policy := range commonPolicySet.ToSlice() { s, ok := policyToUsersMap[policy] if !ok { - policyToUsersMap[policy] = set.CreateStringSet(user) + policyToUsersMap[policy] = set.CreateStringSet(decodeUser) } else { - s.Add(user) + s.Add(decodeUser) policyToUsersMap[policy] = s } } @@ -2217,6 +2235,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St continue } + decodeUser := user + if decodeFunc != nil { + decodeUser = decodeFunc(user) + } + var mappedPolicy MappedPolicy store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false)) @@ -2227,9 +2250,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St for _, policy := range commonPolicySet.ToSlice() { s, ok := policyToUsersMap[policy] if !ok { - policyToUsersMap[policy] = set.CreateStringSet(user) + policyToUsersMap[policy] = set.CreateStringSet(decodeUser) } else { - s.Add(user) + s.Add(decodeUser) policyToUsersMap[policy] = s } } @@ -2244,6 +2267,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St return true } + decodeUser := user + if decodeFunc != nil { + decodeUser = decodeFunc(user) + } + commonPolicySet := mappedPolicy.policySet() if !queryPolSet.IsEmpty() { commonPolicySet = commonPolicySet.Intersection(queryPolSet) @@ -2251,9 +2279,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St for _, policy := range commonPolicySet.ToSlice() { s, ok := policyToUsersMap[policy] if !ok { - policyToUsersMap[policy] = set.CreateStringSet(user) + policyToUsersMap[policy] = set.CreateStringSet(decodeUser) } else { - s.Add(user) + s.Add(decodeUser) policyToUsersMap[policy] = s } } @@ -2268,6 +2296,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St return true } + decodeGroup := group + if decodeFunc != nil { + decodeGroup = decodeFunc(group) + } + commonPolicySet := mappedPolicy.policySet() if !queryPolSet.IsEmpty() { commonPolicySet = commonPolicySet.Intersection(queryPolSet) @@ -2275,9 +2308,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St for _, policy := range commonPolicySet.ToSlice() { s, ok := policyToGroupsMap[policy] if !ok { - policyToGroupsMap[policy] = set.CreateStringSet(group) + policyToGroupsMap[policy] = set.CreateStringSet(decodeGroup) } else { - s.Add(group) + s.Add(decodeGroup) policyToGroupsMap[policy] = s } } @@ -2318,7 +2351,7 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St // ListPolicyMappings - return users/groups mapped to policies. func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery, - userPredicate, groupPredicate func(string) bool, + userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string, ) madmin.PolicyEntitiesResult { cache := store.rlock() defer store.runlock() @@ -2328,13 +2361,13 @@ func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery, isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0 if len(q.Users) > 0 { - result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate) + result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate, decodeFunc) } if len(q.Groups) > 0 { - result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate) + result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate, decodeFunc) } if len(q.Policies) > 0 || isAllPoliciesQuery { - result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate) + result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate, decodeFunc) } return result } diff --git a/cmd/iam.go b/cmd/iam.go index 899488c4e..9ff9f6919 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -841,7 +841,7 @@ func (sys *IAMSys) createCleanEntitiesQuery(q madmin.PolicyEntitiesQuery, ldap b // Validate and normalize groups. for _, group := range q.Groups { lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group) - if lookupRes != nil && !underDN { + if lookupRes != nil && underDN { cleanQ.Groups.Add(lookupRes.NormDN) } } @@ -871,7 +871,7 @@ func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyE select { case <-sys.configLoaded: cleanQuery := sys.createCleanEntitiesQuery(q, true) - pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN) + pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN, sys.LDAPConfig.DecodeDN) pe.Timestamp = UTCNow() return &pe, nil case <-ctx.Done(): @@ -955,7 +955,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit return !sys.LDAPConfig.IsLDAPGroupDN(s) } } - pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate) + pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate, nil) pe.Timestamp = UTCNow() return &pe, nil case <-ctx.Done(): @@ -2027,7 +2027,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool, if dnResult == nil { // dn not found - still attempt to detach if provided user is a DN. if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) { - dn = r.User + dn = sys.LDAPConfig.QuickNormalizeDN(r.User) } else { err = errNoSuchUser return @@ -2044,7 +2044,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool, } if dnResult == nil || !underBaseDN { if !isAttach { - dn = r.Group + dn = sys.LDAPConfig.QuickNormalizeDN(r.Group) } else { err = errNoSuchGroup return diff --git a/go.mod b/go.mod index f54063680..c41f96d7a 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/minio/madmin-go/v3 v3.0.58 github.com/minio/minio-go/v7 v7.0.73 github.com/minio/mux v1.9.0 - github.com/minio/pkg/v3 v3.0.3 + github.com/minio/pkg/v3 v3.0.7 github.com/minio/selfupdate v0.6.0 github.com/minio/simdjson-go v0.4.5 github.com/minio/sio v0.4.0 diff --git a/go.sum b/go.sum index 35d231e32..6834684bf 100644 --- a/go.sum +++ b/go.sum @@ -471,8 +471,8 @@ github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA= github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ= github.com/minio/pkg/v2 v2.0.19 h1:r187/k/oVH9H0DDwvLY5WipkJaZ4CLd4KI3KgIUExR0= github.com/minio/pkg/v2 v2.0.19/go.mod h1:luK9LAhQlAPzSuF6F326XSCKjMc1G3Tbh+a9JYwqh8M= -github.com/minio/pkg/v3 v3.0.3 h1:PUJVi5a6Hdn5mIhffC24koFMQwucvTyBHsIOjsisI+U= -github.com/minio/pkg/v3 v3.0.3/go.mod h1:53gkSUVHcfYoskOs5YAJ3D99nsd2SKru90rdE9whlXU= +github.com/minio/pkg/v3 v3.0.7 h1:1I2CbFKO+brioB6Pbnw0jLlFxo+YPy6hCTTXTSitgI8= +github.com/minio/pkg/v3 v3.0.7/go.mod h1:njlf539caYrgXqn/CXewqvkqBIMDTQo9oBBEL34LzY0= github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= diff --git a/internal/config/identity/ldap/ldap.go b/internal/config/identity/ldap/ldap.go index a6f71a748..1c1c704c3 100644 --- a/internal/config/identity/ldap/ldap.go +++ b/internal/config/identity/ldap/ldap.go @@ -402,3 +402,21 @@ func (l *Config) LookupGroupMemberships(userDistNames []string, userDNToUsername return res, nil } + +// QuickNormalizeDN - normalizes the given DN without checking if it is valid or +// exists in the LDAP directory. Returns input if error +func (l Config) QuickNormalizeDN(dn string) string { + if normDN, err := xldap.NormalizeDN(dn); err == nil { + return normDN + } + return dn +} + +// DecodeDN - denormalizes the given DN by unescaping any escaped characters. +// Returns input if error +func (l Config) DecodeDN(dn string) string { + if decodedDN, err := xldap.DecodeDN(dn); err == nil { + return decodedDN + } + return dn +}