mirror of
https://github.com/minio/minio.git
synced 2025-11-10 05:59:43 -05:00
ldap: Add user DN attributes list config param (#19758)
This change uses the updated ldap library in minio/pkg (bumped up to v3). A new config parameter is added for LDAP configuration to specify extra user attributes to load from the LDAP server and to store them as additional claims for the user. A test is added in sts_handlers.go that shows how to access the LDAP attributes as a claim. This is in preparation for adding SSH pubkey authentication to MinIO's SFTP integration.
This commit is contained in:
committed by
GitHub
parent
a591e06ae5
commit
5f78691fcf
@@ -27,36 +27,36 @@ import (
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
xldap "github.com/minio/pkg/v2/ldap"
|
||||
xldap "github.com/minio/pkg/v3/ldap"
|
||||
)
|
||||
|
||||
// LookupUserDN searches for the full DN and groups of a given short/login
|
||||
// username.
|
||||
func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
||||
func (l *Config) LookupUserDN(username string) (*xldap.DNSearchResult, []string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Lookup user DN
|
||||
bindDN, err := l.LDAP.LookupUserDN(conn, username)
|
||||
lookupRes, err := l.LDAP.LookupUsername(conn, username)
|
||||
if err != nil {
|
||||
errRet := fmt.Errorf("Unable to find user DN: %w", err)
|
||||
return "", nil, errRet
|
||||
return nil, nil, errRet
|
||||
}
|
||||
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, username, bindDN)
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, username, lookupRes.NormDN)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return bindDN, groups, nil
|
||||
return lookupRes, groups, nil
|
||||
}
|
||||
|
||||
// GetValidatedDNForUsername checks if the given username exists in the LDAP directory.
|
||||
@@ -68,28 +68,28 @@ func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
||||
// LDAP specific normalization (including Unicode normalization).
|
||||
//
|
||||
// If the user is not found, err = nil, otherwise, err != nil.
|
||||
func (l *Config) GetValidatedDNForUsername(username string) (string, error) {
|
||||
func (l *Config) GetValidatedDNForUsername(username string) (*xldap.DNSearchResult, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the passed in username is a valid DN.
|
||||
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)
|
||||
bindDN, err := l.LDAP.LookupUsername(conn, username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "User DN not found for") {
|
||||
return "", nil
|
||||
return nil, nil
|
||||
}
|
||||
return "", fmt.Errorf("Unable to find user DN: %w", err)
|
||||
return nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
return bindDN, nil
|
||||
}
|
||||
@@ -98,124 +98,126 @@ func (l *Config) GetValidatedDNForUsername(username string) (string, error) {
|
||||
// under a configured base DN in the LDAP directory.
|
||||
validDN, isUnderBaseDN, err := l.GetValidatedUserDN(conn, username)
|
||||
if err == nil && !isUnderBaseDN {
|
||||
return "", fmt.Errorf("Unable to find user DN: %w", err)
|
||||
return nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
return validDN, err
|
||||
}
|
||||
|
||||
// GetValidatedUserDN validates the given user DN. Will error out if conn is nil. The returned
|
||||
// boolean is true iff the user DN is found under one of the LDAP user base DNs.
|
||||
func (l *Config) GetValidatedUserDN(conn *ldap.Conn, userDN string) (string, bool, error) {
|
||||
return l.GetValidatedDNUnderBaseDN(conn, userDN, l.LDAP.UserDNSearchBaseDistNames)
|
||||
func (l *Config) GetValidatedUserDN(conn *ldap.Conn, userDN string) (*xldap.DNSearchResult, bool, error) {
|
||||
return l.GetValidatedDNUnderBaseDN(conn, userDN,
|
||||
l.LDAP.GetUserDNSearchBaseDistNames(), l.LDAP.GetUserDNAttributesList())
|
||||
}
|
||||
|
||||
// GetValidatedGroupDN validates the given group DN. If conn is nil, creates a
|
||||
// connection. The returned boolean is true iff the group DN is found under one
|
||||
// of the configured LDAP base DNs.
|
||||
func (l *Config) GetValidatedGroupDN(conn *ldap.Conn, groupDN string) (string, bool, error) {
|
||||
func (l *Config) GetValidatedGroupDN(conn *ldap.Conn, groupDN string) (*xldap.DNSearchResult, bool, error) {
|
||||
if conn == nil {
|
||||
var err error
|
||||
conn, err = l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
return nil, false, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", false, err
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
return l.GetValidatedDNUnderBaseDN(conn, groupDN, l.LDAP.GroupSearchBaseDistNames)
|
||||
return l.GetValidatedDNUnderBaseDN(conn, groupDN,
|
||||
l.LDAP.GetGroupSearchBaseDistNames(), nil)
|
||||
}
|
||||
|
||||
// GetValidatedDNUnderBaseDN checks if the given DN exists in the LDAP directory
|
||||
// and returns the DN value sent by the LDAP server. The value returned by the
|
||||
// server may not be equal to the input DN, as LDAP equality is not a simple
|
||||
// Golang string equality. However, we assume the value returned by the LDAP
|
||||
// server is canonical. Additionally, the attribute type names in the DN are
|
||||
// lower-cased.
|
||||
// GetValidatedDNUnderBaseDN checks if the given DN exists in the LDAP
|
||||
// directory.
|
||||
//
|
||||
// The `NormDN` value returned here in the search result may not be equal to the
|
||||
// input DN, as LDAP equality is not a simple Golang string equality. However,
|
||||
// we assume the value returned by the LDAP server is canonical. Additionally,
|
||||
// the attribute type names in the DN are lower-cased.
|
||||
//
|
||||
// Return values:
|
||||
//
|
||||
// If the DN is found, the normalized (string) value is returned and error is
|
||||
// nil.
|
||||
// If the DN is found, the normalized (string) value and any requested
|
||||
// attributes are returned and error is nil.
|
||||
//
|
||||
// If the DN is not found, the string returned is empty and the error is nil.
|
||||
// If the DN is not found, a nil result and error are returned.
|
||||
//
|
||||
// The returned boolean is true iff the DN is found under one of the LDAP
|
||||
// subtrees listed in `baseDNList`.
|
||||
func (l *Config) GetValidatedDNUnderBaseDN(conn *ldap.Conn, dn string, baseDNList []xldap.BaseDNInfo) (string, bool, error) {
|
||||
func (l *Config) GetValidatedDNUnderBaseDN(conn *ldap.Conn, dn string, baseDNList []xldap.BaseDNInfo, attrs []string) (*xldap.DNSearchResult, bool, error) {
|
||||
if len(baseDNList) == 0 {
|
||||
return "", false, errors.New("no Base DNs given")
|
||||
return nil, false, errors.New("no Base DNs given")
|
||||
}
|
||||
|
||||
// Check that DN exists in the LDAP directory.
|
||||
validatedDN, err := xldap.LookupDN(conn, dn)
|
||||
searchRes, err := xldap.LookupDN(conn, dn, attrs)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("Error looking up DN %s: %w", dn, err)
|
||||
return nil, false, fmt.Errorf("Error looking up DN %s: %w", dn, err)
|
||||
}
|
||||
if validatedDN == "" {
|
||||
return "", false, nil
|
||||
if searchRes == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// This will not return an error as the argument is validated to be a DN.
|
||||
pdn, _ := ldap.ParseDN(validatedDN)
|
||||
pdn, _ := ldap.ParseDN(searchRes.NormDN)
|
||||
|
||||
// Check that the DN is under a configured base DN in the LDAP
|
||||
// directory.
|
||||
for _, baseDN := range baseDNList {
|
||||
if baseDN.Parsed.AncestorOf(pdn) {
|
||||
return validatedDN, true, nil
|
||||
return searchRes, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Not under any configured base DN so return false.
|
||||
return validatedDN, false, nil
|
||||
return searchRes, false, nil
|
||||
}
|
||||
|
||||
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||
// user and the list of groups.
|
||||
func (l *Config) Bind(username, password string) (string, []string, error) {
|
||||
func (l *Config) Bind(username, password string) (*xldap.DNSearchResult, []string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var bindDN string
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Lookup user DN
|
||||
bindDN, err = l.LDAP.LookupUserDN(conn, username)
|
||||
lookupResult, err := l.LDAP.LookupUsername(conn, username)
|
||||
if err != nil {
|
||||
errRet := fmt.Errorf("Unable to find user DN: %w", err)
|
||||
return "", nil, errRet
|
||||
return nil, nil, errRet
|
||||
}
|
||||
|
||||
// Authenticate the user credentials.
|
||||
err = conn.Bind(bindDN, password)
|
||||
err = conn.Bind(lookupResult.NormDN, password)
|
||||
if err != nil {
|
||||
errRet := fmt.Errorf("LDAP auth failed for DN %s: %w", bindDN, err)
|
||||
return "", nil, errRet
|
||||
errRet := fmt.Errorf("LDAP auth failed for DN %s: %w", lookupResult.NormDN, err)
|
||||
return nil, nil, errRet
|
||||
}
|
||||
|
||||
// Bind to the lookup user account again to perform group search.
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// User groups lookup.
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, username, bindDN)
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, username, lookupResult.NormDN)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return bindDN, groups, nil
|
||||
return lookupResult, groups, nil
|
||||
}
|
||||
|
||||
// GetExpiryDuration - return parsed expiry duration.
|
||||
@@ -250,7 +252,7 @@ func (l Config) IsLDAPUserDN(user string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, baseDN := range l.LDAP.UserDNSearchBaseDistNames {
|
||||
for _, baseDN := range l.LDAP.GetUserDNSearchBaseDistNames() {
|
||||
if baseDN.Parsed.AncestorOf(udn) {
|
||||
return true
|
||||
}
|
||||
@@ -264,7 +266,7 @@ func (l Config) IsLDAPGroupDN(group string) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, baseDN := range l.LDAP.GroupSearchBaseDistNames {
|
||||
for _, baseDN := range l.LDAP.GetGroupSearchBaseDistNames() {
|
||||
if baseDN.Parsed.AncestorOf(gdn) {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user