Add DecodeDN and QuickNormalizeDN functions to LDAP config (#20076)

This commit is contained in:
Taran Pelkey 2024-07-11 21:04:53 -04:00 committed by GitHub
parent e139673969
commit f5d2fbc84c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 88 additions and 36 deletions

View File

@ -552,7 +552,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
dnList = append(dnList, selfDN) dnList = append(dnList, selfDN)
} }
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp) var ldapUserList []string
if isAll { if isAll {
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx) ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
if err != nil { if err != nil {
@ -560,7 +560,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
return return
} }
for user := range ldapUsers { for user := range ldapUsers {
accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{} ldapUserList = append(ldapUserList, user)
} }
} else { } else {
for _, userDN := range dnList { for _, userDN := range dnList {
@ -573,7 +573,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
if foundResult == nil { if foundResult == nil {
continue 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 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 { if listSTSKeys {
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn) stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, internalDN)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return 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 only STS keys, skip if user has no STS keys
if !listServiceAccounts && len(stsKeys) == 0 { if !listServiceAccounts && len(stsKeys) == 0 {
delete(accessKeyMap, dn)
continue continue
} }
} }
if listServiceAccounts { if listServiceAccounts {
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, dn) serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, internalDN)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return 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 only service accounts, skip if user has no service accounts
if !listSTSKeys && len(serviceAccounts) == 0 { if !listSTSKeys && len(serviceAccounts) == 0 {
delete(accessKeyMap, dn)
continue continue
} }
} }
accessKeyMap[dn] = accessKeys accessKeyMap[externalDN] = accessKeys
} }
data, err := json.Marshal(accessKeyMap) data, err := json.Marshal(accessKeyMap)

View File

@ -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. // 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, 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 { ) []madmin.UserPolicyEntities {
stsMap := xsync.NewMapOf[string, MappedPolicy]() stsMap := xsync.NewMapOf[string, MappedPolicy]()
resMap := make(map[string]madmin.UserPolicyEntities, len(userMap)) 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 { for user, groupSet := range userMap {
// Attempt to load parent user mapping for STS accounts // Attempt to load parent user mapping for STS accounts
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap) 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() { if !groupSet.IsEmpty() {
blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil) blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil, decodeFunc)
} }
resMap[user] = blankEntities resMap[user] = blankEntities
} }
@ -2116,7 +2120,11 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[st
if len(userMap) > 0 { if len(userMap) > 0 {
return true return true
} }
entitiesWithMemberOf = madmin.UserPolicyEntities{User: user} decodeUser := user
if decodeFunc != nil {
decodeUser = decodeFunc(user)
}
entitiesWithMemberOf = madmin.UserPolicyEntities{User: decodeUser}
} }
ps := mappedPolicy.toSlice() 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. // Assumes store is locked by caller. If groups is empty, returns all group mappings.
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet, func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet,
groupPredicate func(string) bool, groupPredicate func(string) bool, decodeFunc func(string) string,
) []madmin.GroupPolicyEntities { ) []madmin.GroupPolicyEntities {
var r []madmin.GroupPolicyEntities var r []madmin.GroupPolicyEntities
@ -2168,10 +2176,15 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set
return true return true
} }
decodeGroup := group
if decodeFunc != nil {
decodeGroup = decodeFunc(group)
}
ps := mappedPolicy.toSlice() ps := mappedPolicy.toSlice()
sort.Strings(ps) sort.Strings(ps)
r = append(r, madmin.GroupPolicyEntities{ r = append(r, madmin.GroupPolicyEntities{
Group: group, Group: decodeGroup,
Policies: ps, Policies: ps,
}) })
return true 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. // Assumes store is locked by caller. If policies is empty, returns all policy mappings.
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet, 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 { ) []madmin.PolicyEntities {
policyToUsersMap := make(map[string]set.StringSet) policyToUsersMap := make(map[string]set.StringSet)
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool { cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
@ -2194,6 +2207,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
return true return true
} }
decodeUser := user
if decodeFunc != nil {
decodeUser = decodeFunc(user)
}
commonPolicySet := mappedPolicy.policySet() commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() { if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet) commonPolicySet = commonPolicySet.Intersection(queryPolSet)
@ -2201,9 +2219,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
for _, policy := range commonPolicySet.ToSlice() { for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToUsersMap[policy] s, ok := policyToUsersMap[policy]
if !ok { if !ok {
policyToUsersMap[policy] = set.CreateStringSet(user) policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
} else { } else {
s.Add(user) s.Add(decodeUser)
policyToUsersMap[policy] = s policyToUsersMap[policy] = s
} }
} }
@ -2217,6 +2235,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
continue continue
} }
decodeUser := user
if decodeFunc != nil {
decodeUser = decodeFunc(user)
}
var mappedPolicy MappedPolicy var mappedPolicy MappedPolicy
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false)) 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() { for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToUsersMap[policy] s, ok := policyToUsersMap[policy]
if !ok { if !ok {
policyToUsersMap[policy] = set.CreateStringSet(user) policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
} else { } else {
s.Add(user) s.Add(decodeUser)
policyToUsersMap[policy] = s policyToUsersMap[policy] = s
} }
} }
@ -2244,6 +2267,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
return true return true
} }
decodeUser := user
if decodeFunc != nil {
decodeUser = decodeFunc(user)
}
commonPolicySet := mappedPolicy.policySet() commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() { if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet) commonPolicySet = commonPolicySet.Intersection(queryPolSet)
@ -2251,9 +2279,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
for _, policy := range commonPolicySet.ToSlice() { for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToUsersMap[policy] s, ok := policyToUsersMap[policy]
if !ok { if !ok {
policyToUsersMap[policy] = set.CreateStringSet(user) policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
} else { } else {
s.Add(user) s.Add(decodeUser)
policyToUsersMap[policy] = s policyToUsersMap[policy] = s
} }
} }
@ -2268,6 +2296,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
return true return true
} }
decodeGroup := group
if decodeFunc != nil {
decodeGroup = decodeFunc(group)
}
commonPolicySet := mappedPolicy.policySet() commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() { if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet) commonPolicySet = commonPolicySet.Intersection(queryPolSet)
@ -2275,9 +2308,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
for _, policy := range commonPolicySet.ToSlice() { for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToGroupsMap[policy] s, ok := policyToGroupsMap[policy]
if !ok { if !ok {
policyToGroupsMap[policy] = set.CreateStringSet(group) policyToGroupsMap[policy] = set.CreateStringSet(decodeGroup)
} else { } else {
s.Add(group) s.Add(decodeGroup)
policyToGroupsMap[policy] = s policyToGroupsMap[policy] = s
} }
} }
@ -2318,7 +2351,7 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.St
// ListPolicyMappings - return users/groups mapped to policies. // ListPolicyMappings - return users/groups mapped to policies.
func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery, func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
userPredicate, groupPredicate func(string) bool, userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string,
) madmin.PolicyEntitiesResult { ) madmin.PolicyEntitiesResult {
cache := store.rlock() cache := store.rlock()
defer store.runlock() 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 isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0
if len(q.Users) > 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 { 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 { 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 return result
} }

View File

@ -841,7 +841,7 @@ func (sys *IAMSys) createCleanEntitiesQuery(q madmin.PolicyEntitiesQuery, ldap b
// Validate and normalize groups. // Validate and normalize groups.
for _, group := range q.Groups { for _, group := range q.Groups {
lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group) lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group)
if lookupRes != nil && !underDN { if lookupRes != nil && underDN {
cleanQ.Groups.Add(lookupRes.NormDN) cleanQ.Groups.Add(lookupRes.NormDN)
} }
} }
@ -871,7 +871,7 @@ func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyE
select { select {
case <-sys.configLoaded: case <-sys.configLoaded:
cleanQuery := sys.createCleanEntitiesQuery(q, true) 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() pe.Timestamp = UTCNow()
return &pe, nil return &pe, nil
case <-ctx.Done(): case <-ctx.Done():
@ -955,7 +955,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
return !sys.LDAPConfig.IsLDAPGroupDN(s) return !sys.LDAPConfig.IsLDAPGroupDN(s)
} }
} }
pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate) pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate, nil)
pe.Timestamp = UTCNow() pe.Timestamp = UTCNow()
return &pe, nil return &pe, nil
case <-ctx.Done(): case <-ctx.Done():
@ -2027,7 +2027,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
if dnResult == nil { if dnResult == nil {
// dn not found - still attempt to detach if provided user is a DN. // dn not found - still attempt to detach if provided user is a DN.
if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) { if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) {
dn = r.User dn = sys.LDAPConfig.QuickNormalizeDN(r.User)
} else { } else {
err = errNoSuchUser err = errNoSuchUser
return return
@ -2044,7 +2044,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
} }
if dnResult == nil || !underBaseDN { if dnResult == nil || !underBaseDN {
if !isAttach { if !isAttach {
dn = r.Group dn = sys.LDAPConfig.QuickNormalizeDN(r.Group)
} else { } else {
err = errNoSuchGroup err = errNoSuchGroup
return return

2
go.mod
View File

@ -55,7 +55,7 @@ require (
github.com/minio/madmin-go/v3 v3.0.58 github.com/minio/madmin-go/v3 v3.0.58
github.com/minio/minio-go/v7 v7.0.73 github.com/minio/minio-go/v7 v7.0.73
github.com/minio/mux v1.9.0 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/selfupdate v0.6.0
github.com/minio/simdjson-go v0.4.5 github.com/minio/simdjson-go v0.4.5
github.com/minio/sio v0.4.0 github.com/minio/sio v0.4.0

4
go.sum
View File

@ -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/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 h1:r187/k/oVH9H0DDwvLY5WipkJaZ4CLd4KI3KgIUExR0=
github.com/minio/pkg/v2 v2.0.19/go.mod h1:luK9LAhQlAPzSuF6F326XSCKjMc1G3Tbh+a9JYwqh8M= 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.7 h1:1I2CbFKO+brioB6Pbnw0jLlFxo+YPy6hCTTXTSitgI8=
github.com/minio/pkg/v3 v3.0.3/go.mod h1:53gkSUVHcfYoskOs5YAJ3D99nsd2SKru90rdE9whlXU= 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 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= 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= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=

View File

@ -402,3 +402,21 @@ func (l *Config) LookupGroupMemberships(userDistNames []string, userDNToUsername
return res, nil 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
}