mirror of
https://github.com/minio/minio.git
synced 2025-11-07 21:02:58 -05:00
fix: ldap: use validated base DNs (#19406)
This fixes a regression from #19358 which prevents policy mappings created in the latest release from being displayed in policy entity listing APIs. This is due to the possibility that the base DNs in the LDAP config are not in a normalized form and #19358 introduced normalized of mapping keys (user DNs and group DNs). When listing, we check if the policy mappings are on entities that parse as valid DNs that are descendants of the base DNs in the config. Test added that demonstrates a failure without this fix.
This commit is contained in:
committed by
GitHub
parent
272367ccd2
commit
c9e9a8e2b9
@@ -30,7 +30,8 @@ import (
|
||||
xldap "github.com/minio/pkg/v2/ldap"
|
||||
)
|
||||
|
||||
// LookupUserDN searches for the full DN and groups of a given username
|
||||
// LookupUserDN searches for the full DN and groups of a given short/login
|
||||
// username.
|
||||
func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
@@ -80,10 +81,9 @@ func (l *Config) GetValidatedDNForUsername(username string) (string, error) {
|
||||
}
|
||||
|
||||
// Check if the passed in username is a valid DN.
|
||||
parsedUsernameDN, err := ldap.ParseDN(username)
|
||||
if err != nil {
|
||||
// Since the passed in username was not a DN, we consider it as a login
|
||||
// username and attempt to check it exists in the directory.
|
||||
if !l.ParsesAsDN(username) {
|
||||
// We consider it as a login username and attempt to check it exists in
|
||||
// the directory.
|
||||
bindDN, err := l.LDAP.LookupUserDN(conn, username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
@@ -96,41 +96,28 @@ func (l *Config) GetValidatedDNForUsername(username string) (string, error) {
|
||||
|
||||
// Since the username is a valid DN, check that it is under a configured
|
||||
// base DN in the LDAP directory.
|
||||
var foundDistName []string
|
||||
|
||||
// Check that userDN exists in the LDAP directory.
|
||||
validatedUserDN, err := xldap.LookupDN(conn, username)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error looking up user DN %s: %w", username, err)
|
||||
}
|
||||
if validatedUserDN == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// This will return an error as the argument is validated to be a DN.
|
||||
udn, _ := ldap.ParseDN(validatedUserDN)
|
||||
|
||||
// Check that the user DN is under a configured user base DN in the LDAP
|
||||
// directory.
|
||||
for _, baseDN := range l.LDAP.UserDNSearchBaseDistNames {
|
||||
// BaseDN should not fail to parse.
|
||||
baseDNParsed, _ := ldap.ParseDN(baseDN)
|
||||
if baseDNParsed.AncestorOf(parsedUsernameDN) {
|
||||
searchRequest := ldap.NewSearchRequest(username, ldap.ScopeBaseObject, ldap.NeverDerefAliases,
|
||||
0, 0, false, "(objectClass=*)", nil, nil)
|
||||
searchResult, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
// Check if there is no matching result.
|
||||
// Ref: https://ldap.com/ldap-result-code-reference/
|
||||
if ldap.IsErrorWithCode(err, 32) {
|
||||
continue
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
for _, entry := range searchResult.Entries {
|
||||
normDN, err := xldap.NormalizeDN(entry.DN)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
foundDistName = append(foundDistName, normDN)
|
||||
}
|
||||
if baseDN.Parsed.AncestorOf(udn) {
|
||||
return validatedUserDN, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(foundDistName) == 1 {
|
||||
return foundDistName[0], nil
|
||||
} else if len(foundDistName) > 1 {
|
||||
// FIXME: This error would happen if the multiple base DNs are given and
|
||||
// some base DNs are subtrees of other base DNs - we should validate
|
||||
// and error out in such cases.
|
||||
return "", fmt.Errorf("found multiple DNs for the given username")
|
||||
}
|
||||
return "", nil
|
||||
return "", fmt.Errorf("User DN %s is not under any configured user base DN", validatedUserDN)
|
||||
}
|
||||
|
||||
// GetValidatedGroupDN checks if the given group DN exists in the LDAP directory
|
||||
@@ -146,11 +133,6 @@ func (l *Config) GetValidatedGroupDN(groupDN string) (string, error) {
|
||||
return "", errors.New("no group search Base DNs given")
|
||||
}
|
||||
|
||||
gdn, err := ldap.ParseDN(groupDN)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Given group DN could not be parsed: %s", err)
|
||||
}
|
||||
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -162,39 +144,28 @@ func (l *Config) GetValidatedGroupDN(groupDN string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var foundDistName []string
|
||||
// Check that groupDN exists in the LDAP directory.
|
||||
validatedGroupDN, err := xldap.LookupDN(conn, groupDN)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error looking up group DN %s: %w", groupDN, err)
|
||||
}
|
||||
if validatedGroupDN == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
gdn, err := ldap.ParseDN(validatedGroupDN)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Given group DN %s could not be parsed: %w", validatedGroupDN, err)
|
||||
}
|
||||
|
||||
// Check that the group DN is under a configured group base DN in the LDAP
|
||||
// directory.
|
||||
for _, baseDN := range l.LDAP.GroupSearchBaseDistNames {
|
||||
// BaseDN should not fail to parse.
|
||||
baseDNParsed, _ := ldap.ParseDN(baseDN)
|
||||
if baseDNParsed.AncestorOf(gdn) {
|
||||
searchRequest := ldap.NewSearchRequest(groupDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=*)", nil, nil)
|
||||
searchResult, err := conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
// Check if there is no matching result.
|
||||
// Ref: https://ldap.com/ldap-result-code-reference/
|
||||
if ldap.IsErrorWithCode(err, 32) {
|
||||
continue
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
for _, entry := range searchResult.Entries {
|
||||
normDN, err := xldap.NormalizeDN(entry.DN)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
foundDistName = append(foundDistName, normDN)
|
||||
}
|
||||
if baseDN.Parsed.AncestorOf(gdn) {
|
||||
return validatedGroupDN, nil
|
||||
}
|
||||
}
|
||||
if len(foundDistName) == 1 {
|
||||
return foundDistName[0], nil
|
||||
} else if len(foundDistName) > 1 {
|
||||
// FIXME: This error would happen if the multiple base DNs are given and
|
||||
// some base DNs are subtrees of other base DNs - we should validate
|
||||
// and error out in such cases.
|
||||
return "", fmt.Errorf("found multiple DNs for the given group DN")
|
||||
}
|
||||
return "", nil
|
||||
return "", fmt.Errorf("Group DN %s is not under any configured group base DN", validatedGroupDN)
|
||||
}
|
||||
|
||||
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||
@@ -259,10 +230,21 @@ func (l Config) GetExpiryDuration(dsecs string) (time.Duration, error) {
|
||||
return dur, nil
|
||||
}
|
||||
|
||||
// ParsesAsDN determines if the given string could be a valid DN based on
|
||||
// parsing alone.
|
||||
func (l Config) ParsesAsDN(dn string) bool {
|
||||
_, err := ldap.ParseDN(dn)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsLDAPUserDN determines if the given string could be a user DN from LDAP.
|
||||
func (l Config) IsLDAPUserDN(user string) bool {
|
||||
udn, err := ldap.ParseDN(user)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, baseDN := range l.LDAP.UserDNSearchBaseDistNames {
|
||||
if strings.HasSuffix(user, ","+baseDN) {
|
||||
if baseDN.Parsed.AncestorOf(udn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -270,9 +252,13 @@ func (l Config) IsLDAPUserDN(user string) bool {
|
||||
}
|
||||
|
||||
// IsLDAPGroupDN determines if the given string could be a group DN from LDAP.
|
||||
func (l Config) IsLDAPGroupDN(user string) bool {
|
||||
func (l Config) IsLDAPGroupDN(group string) bool {
|
||||
gdn, err := ldap.ParseDN(group)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, baseDN := range l.LDAP.GroupSearchBaseDistNames {
|
||||
if strings.HasSuffix(user, ","+baseDN) {
|
||||
if baseDN.Parsed.AncestorOf(gdn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user