From 7dfa565d005519a4ec41c12df393f9fb3a955c8f Mon Sep 17 00:00:00 2001 From: Aditya Manthramurthy Date: Wed, 26 Jan 2022 15:05:59 -0800 Subject: [PATCH] Identity LDAP: Allow multiple search base DNs (#14191) This change allows the MinIO server to lookup users in different directory sub-trees by allowing specification of multiple search bases separated by semicolons. --- cmd/site-replication.go | 2 +- docs/sts/ldap.md | 2 +- internal/config/identity/ldap/config.go | 64 +++++++++++++++---------- internal/config/identity/ldap/help.go | 4 +- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/cmd/site-replication.go b/cmd/site-replication.go index b4c4f0377..c1c7e17cd 100644 --- a/cmd/site-replication.go +++ b/cmd/site-replication.go @@ -540,7 +540,7 @@ func (c *SiteReplicationSys) GetIDPSettings(ctx context.Context) madmin.IDPSetti s := madmin.IDPSettings{} s.LDAP = madmin.LDAPSettings{ IsLDAPEnabled: globalLDAPConfig.Enabled, - LDAPUserDNSearchBase: globalLDAPConfig.UserDNSearchBaseDN, + LDAPUserDNSearchBase: globalLDAPConfig.UserDNSearchBaseDistName, LDAPUserDNSearchFilter: globalLDAPConfig.UserDNSearchFilter, LDAPGroupSearchBase: globalLDAPConfig.GroupSearchBaseDistName, LDAPGroupSearchFilter: globalLDAPConfig.GroupSearchFilter, diff --git a/docs/sts/ldap.md b/docs/sts/ldap.md index 34afa9a7d..b0b989ab6 100644 --- a/docs/sts/ldap.md +++ b/docs/sts/ldap.md @@ -37,7 +37,7 @@ ARGS: MINIO_IDENTITY_LDAP_SERVER_ADDR* (address) AD/LDAP server address e.g. "myldapserver.com:636" MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN (string) DN for LDAP read-only service account used to perform DN and group lookups MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD (string) Password for LDAP read-only service account used to perform DN and group lookups -MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN (string) Base LDAP DN to search for user DN +MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN (string) ";" separated list of user search base DNs e.g. "dc=myldapserver,dc=com" MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER (string) Search filter to lookup user DN MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER (string) search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))" MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN (list) ";" separated list of group search base DNs e.g. "dc=myldapserver,dc=com" diff --git a/internal/config/identity/ldap/config.go b/internal/config/identity/ldap/config.go index 5bd029215..b80fe3344 100644 --- a/internal/config/identity/ldap/config.go +++ b/internal/config/identity/ldap/config.go @@ -51,8 +51,9 @@ type Config struct { ServerAddr string `json:"serverAddr"` // User DN search parameters - UserDNSearchBaseDN string `json:"userDNSearchBaseDN"` - UserDNSearchFilter string `json:"userDNSearchFilter"` + UserDNSearchBaseDistName string `json:"userDNSearchBaseDN"` + UserDNSearchBaseDistNames []string `json:"-"` + UserDNSearchFilter string `json:"userDNSearchFilter"` // Group search parameters GroupSearchBaseDistName string `json:"groupSearchBaseDN"` @@ -187,25 +188,32 @@ func (l *Config) lookupBind(conn *ldap.Conn) error { // search result in at most one result. func (l *Config) lookupUserDN(conn *ldap.Conn, username string) (string, error) { filter := strings.ReplaceAll(l.UserDNSearchFilter, "%s", ldap.EscapeFilter(username)) - searchRequest := ldap.NewSearchRequest( - l.UserDNSearchBaseDN, - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - filter, - []string{}, // only need DN, so no pass no attributes here - nil, - ) + var foundDistNames []string + for _, userSearchBase := range l.UserDNSearchBaseDistNames { + searchRequest := ldap.NewSearchRequest( + userSearchBase, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + filter, + []string{}, // only need DN, so no pass no attributes here + nil, + ) - searchResult, err := conn.Search(searchRequest) - if err != nil { - return "", err + searchResult, err := conn.Search(searchRequest) + if err != nil { + return "", err + } + + for _, entry := range searchResult.Entries { + foundDistNames = append(foundDistNames, entry.DN) + } } - if len(searchResult.Entries) == 0 { + if len(foundDistNames) == 0 { return "", fmt.Errorf("User DN for %s not found", username) } - if len(searchResult.Entries) != 1 { + if len(foundDistNames) != 1 { return "", fmt.Errorf("Multiple DNs for %s found - please fix the search filter", username) } - return searchResult.Entries[0].DN, nil + return foundDistNames[0], nil } func (l *Config) searchForUserGroups(conn *ldap.Conn, username, bindDN string) ([]string, error) { @@ -375,7 +383,12 @@ func (l Config) testConnection() error { // 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) + for _, baseDN := range l.UserDNSearchBaseDistNames { + if strings.HasSuffix(user, ","+baseDN) { + return true + } + } + return false } // GetNonEligibleUserDistNames - find user accounts (DNs) that are no longer @@ -505,15 +518,6 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) { if lookupBindDN != "" { l.LookupBindDN = lookupBindDN l.LookupBindPassword = lookupBindPassword - - // User DN search configuration - userDNSearchBaseDN := env.Get(EnvUserDNSearchBaseDN, kvs.Get(UserDNSearchBaseDN)) - userDNSearchFilter := env.Get(EnvUserDNSearchFilter, kvs.Get(UserDNSearchFilter)) - if userDNSearchFilter == "" || userDNSearchBaseDN == "" { - return l, errors.New("In lookup bind mode, userDN search base DN and userDN search filter are both required") - } - l.UserDNSearchBaseDN = userDNSearchBaseDN - l.UserDNSearchFilter = userDNSearchFilter } // Test connection to LDAP server. @@ -521,6 +525,16 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) { return l, fmt.Errorf("Connection test for LDAP server failed: %w", err) } + // User DN search configuration + userDNSearchBaseDN := env.Get(EnvUserDNSearchBaseDN, kvs.Get(UserDNSearchBaseDN)) + userDNSearchFilter := env.Get(EnvUserDNSearchFilter, kvs.Get(UserDNSearchFilter)) + if userDNSearchFilter == "" || userDNSearchBaseDN == "" { + return l, errors.New("UserDN search base DN and UserDN search filter are both required") + } + l.UserDNSearchBaseDistName = userDNSearchBaseDN + l.UserDNSearchBaseDistNames = strings.Split(userDNSearchBaseDN, dnDelimiter) + l.UserDNSearchFilter = userDNSearchFilter + // Group search params configuration grpSearchFilter := env.Get(EnvGroupSearchFilter, kvs.Get(GroupSearchFilter)) grpSearchBaseDN := env.Get(EnvGroupSearchBaseDN, kvs.Get(GroupSearchBaseDN)) diff --git a/internal/config/identity/ldap/help.go b/internal/config/identity/ldap/help.go index e2a955d86..a4165e47c 100644 --- a/internal/config/identity/ldap/help.go +++ b/internal/config/identity/ldap/help.go @@ -44,9 +44,9 @@ var ( }, config.HelpKV{ Key: UserDNSearchBaseDN, - Description: `Base LDAP DN to search for user DN`, + Description: `";" separated list of user search base DNs e.g. "dc=myldapserver,dc=com"`, Optional: true, - Type: "string", + Type: "list", }, config.HelpKV{ Key: UserDNSearchFilter,