Пример #1
0
/*
 * Returns the DN of the object representing the authenticated user.
 */
func (b *backend) getUserDN(cfg *ConfigEntry, c *ldap.Conn, bindDN string) (string, error) {
	userDN := ""
	if cfg.UPNDomain != "" {
		// Find the distinguished name for the user if userPrincipalName used for login
		filter := fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(bindDN))
		if b.Logger().IsDebug() {
			b.Logger().Debug("auth/ldap: Searching UPN", "userdn", cfg.UserDN, "filter", filter)
		}
		result, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: filter,
		})
		if err != nil {
			return userDN, fmt.Errorf("LDAP search failed for detecting user: %v", err)
		}
		for _, e := range result.Entries {
			userDN = e.DN
		}
	} else {
		userDN = bindDN
	}

	return userDN, nil
}
Пример #2
0
func getBindDN(cfg *ConfigEntry, c *ldap.Conn, username string) (string, error) {
	bindDN := ""
	if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") {
		if err := c.Bind(cfg.BindDN, cfg.BindPassword); err != nil {
			return bindDN, fmt.Errorf("LDAP bind (service) failed: %v", err)
		}
		result, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)),
		})
		if err != nil {
			return bindDN, fmt.Errorf("LDAP search for binddn failed: %v", err)
		}
		if len(result.Entries) != 1 {
			return bindDN, fmt.Errorf("LDAP search for binddn 0 or not unique")
		}
		bindDN = result.Entries[0].DN
	} else {
		if cfg.UPNDomain != "" {
			bindDN = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain)
		} else {
			bindDN = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN)
		}
	}

	return bindDN, nil
}
Пример #3
0
func (ad *ActiveDirectoryClient) getUserSearchFilter(samAccountName string) string {
	userFilter := fmt.Sprintf("(&(%s=%s)%s)",
		AD_USER_ATTRIBUTE, ldap.EscapeFilter(samAccountName), AD_USER_FILTER)

	log.V(11).Infof("Filter: %s\n", userFilter)

	return userFilter
}
Пример #4
0
// buildAttributeQuery builds the query containing a filter that conjoins the common filter given
// in the configuration with the specific attribute filter for which the attribute value is given
func (o *LDAPQueryOnAttribute) buildAttributeQuery(attributeValue string,
	attributes []string) *ldap.SearchRequest {
	specificFilter := fmt.Sprintf("%s=%s",
		ldap.EscapeFilter(o.QueryAttribute),
		ldap.EscapeFilter(attributeValue))

	filter := fmt.Sprintf("(&(%s)(%s))", o.Filter, specificFilter)

	return ldap.NewSearchRequest(
		o.BaseDN,
		int(o.Scope),
		int(o.DerefAliases),
		0, // allowed return size - indicates no limit
		o.TimeLimit,
		false, // not types only
		filter,
		attributes,
		nil, // no controls
	)
}
Пример #5
0
func getUserDN(cfg *ConfigEntry, c *ldap.Conn, bindDN string) (string, error) {
	userDN := ""
	if cfg.UPNDomain != "" {
		// Find the distinguished name for the user if userPrincipalName used for login
		result, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(bindDN)),
		})
		if err != nil {
			return userDN, fmt.Errorf("LDAP search failed for detecting user: %v", err)
		}
		for _, e := range result.Entries {
			userDN = e.DN
		}
	} else {
		userDN = bindDN
	}

	return userDN, nil
}
Пример #6
0
// getIdentity looks up a username in an LDAP server, and attempts to bind to the user's DN using the provided password
func (a *Authenticator) getIdentity(username, password string) (authapi.UserIdentityInfo, bool, error) {
	defer func() {
		if e := recover(); e != nil {
			util.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack()))
		}
	}()

	if len(username) == 0 || len(password) == 0 {
		return nil, false, nil
	}

	// Make the connection
	l, err := a.connect()
	if err != nil {
		return nil, false, err
	}
	defer l.Close()

	// If specified, bind the username/password for search phase
	if len(a.options.BindDN) > 0 {
		if err := l.Bind(a.options.BindDN, a.options.BindPassword); err != nil {
			return nil, false, err
		}
	}

	// & together the filter specified in the LDAP options with the user-specific filter
	filter := fmt.Sprintf("(&%s(%s=%s))",
		a.options.URL.Filter,
		ldap.EscapeFilter(a.options.URL.QueryAttribute),
		ldap.EscapeFilter(username),
	)

	// Build list of attributes to retrieve
	attrs := util.NewStringSet(a.options.URL.QueryAttribute)
	attrs.Insert(a.options.AttributeEmail...)
	attrs.Insert(a.options.AttributeName...)
	attrs.Insert(a.options.AttributePreferredUsername...)
	attrs.Insert(a.options.AttributeID...)

	// Search for LDAP record
	searchRequest := ldap.NewSearchRequest(
		a.options.URL.BaseDN,     // base dn
		int(a.options.URL.Scope), // scope
		ldap.NeverDerefAliases,   // deref
		2,            // size limit, we want to know if this is not unique, but don't want the entire tree
		0,            // no client-specified time limit, determined by LDAP server. TODO: make configurable?
		false,        // not types only
		filter,       // filter
		attrs.List(), // attributes to retrieve
		nil,          // controls
	)

	glog.V(4).Infof("searching for %s", filter)
	results, err := l.Search(searchRequest)
	if err != nil {
		return nil, false, err
	}

	if len(results.Entries) == 0 {
		// 0 results means a missing username, not an error
		glog.V(4).Infof("no entries matching %s", filter)
		return nil, false, nil
	}
	if len(results.Entries) > 1 {
		// More than 1 result means a misconfigured server filter or query parameter
		return nil, false, fmt.Errorf("multiple entries found matching %q", username)
	}

	entry := results.Entries[0]
	glog.V(4).Infof("found dn=%q for %s", entry.DN, filter)

	// Bind with given username and password to attempt to authenticate
	if err := l.Bind(entry.DN, password); err != nil {
		glog.V(4).Infof("error binding password for %q: %v", entry.DN, err)
		if err, ok := err.(*ldap.Error); ok {
			switch err.ResultCode {
			case ldap.LDAPResultInappropriateAuthentication:
				// inappropriateAuthentication (48)
				//    Indicates the server requires the client that had attempted
				//    to bind anonymously or without supplying credentials to
				//    provide some form of credentials.
				fallthrough
			case ldap.LDAPResultInvalidCredentials:
				// invalidCredentials (49)
				//    Indicates that the provided credentials (e.g., the user's name
				//    and password) are invalid.

				// Authentication failed, return false, but no error
				return nil, false, nil
			}
		}
		return nil, false, err
	}

	// Build the identity
	uid := getAttributeValue(entry, a.options.AttributeID)
	if uid == "" {
		return nil, false, fmt.Errorf("Could not retrieve a non-empty value from %v attributes for dn=%q", a.options.AttributeID, entry.DN)
	}
	identity := authapi.NewDefaultUserIdentityInfo(a.providerName, uid)

	// Add optional extra attributes if present
	for k, attrs := range map[string][]string{
		authapi.IdentityPreferredUsernameKey: a.options.AttributePreferredUsername,
		authapi.IdentityEmailKey:             a.options.AttributeEmail,
		authapi.IdentityDisplayNameKey:       a.options.AttributeName,
	} {
		if v := getAttributeValue(entry, attrs); len(v) != 0 {
			identity.Extra[k] = v
		}
	}

	return identity, true, nil
}
Пример #7
0
func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
	var searchResult *ldap.SearchResult
	var err error

	for _, searchBase := range a.server.SearchBaseDNs {
		searchReq := ldap.SearchRequest{
			BaseDN:       searchBase,
			Scope:        ldap.ScopeWholeSubtree,
			DerefAliases: ldap.NeverDerefAliases,
			Attributes: []string{
				a.server.Attr.Username,
				a.server.Attr.Surname,
				a.server.Attr.Email,
				a.server.Attr.Name,
				a.server.Attr.MemberOf,
			},
			Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1),
		}

		searchResult, err = a.conn.Search(&searchReq)
		if err != nil {
			return nil, err
		}

		if len(searchResult.Entries) > 0 {
			break
		}
	}

	if len(searchResult.Entries) == 0 {
		return nil, ErrInvalidCredentials
	}

	if len(searchResult.Entries) > 1 {
		return nil, errors.New("Ldap search matched more than one entry, please review your filter setting")
	}

	var memberOf []string
	if a.server.GroupSearchFilter == "" {
		memberOf = getLdapAttrArray(a.server.Attr.MemberOf, searchResult)
	} else {
		// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
		var groupSearchResult *ldap.SearchResult
		for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
			var filter_replace string
			filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult)
			if a.server.GroupSearchFilterUserAttribute == "" {
				filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
			}
			filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)

			if ldapCfg.VerboseLogging {
				log.Info("LDAP: Searching for user's groups: %s", filter)
			}

			groupSearchReq := ldap.SearchRequest{
				BaseDN:       groupSearchBase,
				Scope:        ldap.ScopeWholeSubtree,
				DerefAliases: ldap.NeverDerefAliases,
				Attributes: []string{
					// Here MemberOf would be the thing that identifies the group, which is normally 'cn'
					a.server.Attr.MemberOf,
				},
				Filter: filter,
			}

			groupSearchResult, err = a.conn.Search(&groupSearchReq)
			if err != nil {
				return nil, err
			}

			if len(groupSearchResult.Entries) > 0 {
				for i := range groupSearchResult.Entries {
					memberOf = append(memberOf, getLdapAttrN(a.server.Attr.MemberOf, groupSearchResult, i))
				}
				break
			}
		}
	}

	return &ldapUserInfo{
		DN:        searchResult.Entries[0].DN,
		LastName:  getLdapAttr(a.server.Attr.Surname, searchResult),
		FirstName: getLdapAttr(a.server.Attr.Name, searchResult),
		Username:  getLdapAttr(a.server.Attr.Username, searchResult),
		Email:     getLdapAttr(a.server.Attr.Email, searchResult),
		MemberOf:  memberOf,
	}, nil
}
Пример #8
0
func getLdapGroups(cfg *ConfigEntry, c *ldap.Conn, userDN string, username string) ([]string, error) {
	// retrieve the groups in a string/bool map as a structure to avoid duplicates inside
	ldapMap := make(map[string]bool)
	// Fetch the optional memberOf property values on the user object
	// This is the most common method used in Active Directory setup to retrieve the groups
	result, err := c.Search(&ldap.SearchRequest{
		BaseDN: userDN,
		Scope:  0,        // base scope to fetch only the userDN
		Filter: "(cn=*)", // bogus filter, required to fetch the CN from userDN
		Attributes: []string{
			"memberOf",
		},
	})
	// this check remains in case something happens with the ldap query or connection
	if err != nil {
		return nil, fmt.Errorf("LDAP fetch of distinguishedName=%s failed: %v", userDN, err)
	}
	// if there are more than one entry, we consider the results irrelevant and ignore them
	if len(result.Entries) == 1 {
		for _, attr := range result.Entries[0].Attributes {
			// Find the groups the user is member of from the 'memberOf' attribute extracting the CN
			if attr.Name == "memberOf" {
				for _, value := range attr.Values {
					memberOfDN, err := ldap.ParseDN(value)
					if err != nil || len(memberOfDN.RDNs) == 0 {
						continue
					}

					for _, rdn := range memberOfDN.RDNs {
						for _, rdnTypeAndValue := range rdn.Attributes {
							if strings.EqualFold(rdnTypeAndValue.Type, "CN") {
								ldapMap[rdnTypeAndValue.Value] = true
							}
						}
					}
				}
			}
		}
	}

	// Find groups by searching in groupDN for any of the memberUid, member or uniqueMember attributes
	// and retrieving the CN in the DN result
	if cfg.GroupDN != "" {
		result, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.GroupDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userDN), ldap.EscapeFilter(userDN)),
		})
		if err != nil {
			return nil, fmt.Errorf("LDAP search failed: %v", err)
		}

		for _, e := range result.Entries {
			dn, err := ldap.ParseDN(e.DN)
			if err != nil || len(dn.RDNs) == 0 {
				continue
			}
			for _, rdn := range dn.RDNs {
				for _, rdnTypeAndValue := range rdn.Attributes {
					if strings.EqualFold(rdnTypeAndValue.Type, "CN") {
						ldapMap[rdnTypeAndValue.Value] = true
					}
				}
			}
		}
	}

	ldapGroups := make([]string, len(ldapMap))
	for key, _ := range ldapMap {
		ldapGroups = append(ldapGroups, key)
	}
	return ldapGroups, nil
}
Пример #9
0
// getIdentity looks up a username in an LDAP server, and attempts to bind to the user's DN using the provided password
func (a *Authenticator) getIdentity(username, password string) (authapi.UserIdentityInfo, bool, error) {
	defer func() {
		if e := recover(); e != nil {
			util.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack()))
		}
	}()

	if len(username) == 0 || len(password) == 0 {
		return nil, false, nil
	}

	// Make the connection and bind to it if a bind DN and password were given
	l, err := a.options.ClientConfig.Connect()
	if err != nil {
		return nil, false, err
	}
	defer l.Close()

	if _, err := a.options.ClientConfig.Bind(l); err != nil {
		return nil, false, err
	}

	// & together the filter specified in the LDAP options with the user-specific filter
	filter := fmt.Sprintf("(&%s(%s=%s))",
		a.options.URL.Filter,
		ldap.EscapeFilter(a.options.URL.QueryAttribute),
		ldap.EscapeFilter(username),
	)

	// Build list of attributes to retrieve
	attrs := sets.NewString(a.options.URL.QueryAttribute)
	attrs.Insert(a.options.UserAttributeDefiner.AllAttributes().List()...)

	// Search for LDAP record
	searchRequest := ldap.NewSearchRequest(
		a.options.URL.BaseDN,     // base dn
		int(a.options.URL.Scope), // scope
		ldap.NeverDerefAliases,   // deref
		2,            // size limit, we want to know if this is not unique, but don't want the entire tree
		0,            // no client-specified time limit, determined by LDAP server. TODO: make configurable?
		false,        // not types only
		filter,       // filter
		attrs.List(), // attributes to retrieve
		nil,          // controls
	)

	glog.V(4).Infof("searching for %s", filter)
	results, err := l.Search(searchRequest)
	if err != nil {
		return nil, false, err
	}

	if len(results.Entries) == 0 {
		// 0 results means a missing username, not an error
		glog.V(4).Infof("no entries matching %s", filter)
		return nil, false, nil
	}
	if len(results.Entries) > 1 {
		// More than 1 result means a misconfigured server filter or query parameter
		return nil, false, fmt.Errorf("multiple entries found matching %q", username)
	}

	entry := results.Entries[0]
	glog.V(4).Infof("found dn=%q for %s", entry.DN, filter)

	// Bind with given username and password to attempt to authenticate
	if err := l.Bind(entry.DN, password); err != nil {
		glog.V(4).Infof("error binding password for %q: %v", entry.DN, err)
		if err, ok := err.(*ldap.Error); ok {
			switch err.ResultCode {
			case ldap.LDAPResultInappropriateAuthentication:
				// inappropriateAuthentication (48)
				//    Indicates the server requires the client that had attempted
				//    to bind anonymously or without supplying credentials to
				//    provide some form of credentials.
				fallthrough
			case ldap.LDAPResultInvalidCredentials:
				// invalidCredentials (49)
				//    Indicates that the provided credentials (e.g., the user's name
				//    and password) are invalid.

				// Authentication failed, return false, but no error
				return nil, false, nil
			}
		}
		return nil, false, err
	}

	// Build the identity
	identity, err := a.identityFactory.IdentityFor(entry)
	if err != nil {
		return nil, false, err
	}
	return identity, true, nil
}
Пример #10
0
/*
 * getLdapGroups queries LDAP and returns a slice describing the set of groups the authenticated user is a member of.
 *
 * The search query is constructed according to cfg.GroupFilter, and run in context of cfg.GroupDN.
 * Groups will be resolved from the query results by following the attribute defined in cfg.GroupAttr.
 *
 * cfg.GroupFilter is a go template and is compiled with the following context: [UserDN, Username]
 *    UserDN - The DN of the authenticated user
 *    Username - The Username of the authenticated user
 *
 * Example:
 *   cfg.GroupFilter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))"
 *   cfg.GroupDN     = "OU=Groups,DC=myorg,DC=com"
 *   cfg.GroupAttr   = "cn"
 *
 * NOTE - If cfg.GroupFilter is empty, no query is performed and an empty result slice is returned.
 *
 */
func (b *backend) getLdapGroups(cfg *ConfigEntry, c *ldap.Conn, userDN string, username string) ([]string, error) {
	// retrieve the groups in a string/bool map as a structure to avoid duplicates inside
	ldapMap := make(map[string]bool)

	if cfg.GroupFilter == "" {
		b.Logger().Warn("auth/ldap: GroupFilter is empty, will not query server")
		return make([]string, 0), nil
	}

	if cfg.GroupDN == "" {
		b.Logger().Warn("auth/ldap: GroupDN is empty, will not query server")
		return make([]string, 0), nil
	}

	// If groupfilter was defined, resolve it as a Go template and use the query for
	// returning the user's groups
	if b.Logger().IsDebug() {
		b.Logger().Debug("auth/ldap: Compiling group filter", "group_filter", cfg.GroupFilter)
	}

	// Parse the configuration as a template.
	// Example template "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))"
	t, err := template.New("queryTemplate").Parse(cfg.GroupFilter)
	if err != nil {
		return nil, fmt.Errorf("LDAP search failed due to template compilation error: %v", err)
	}

	// Build context to pass to template - we will be exposing UserDn and Username.
	context := struct {
		UserDN   string
		Username string
	}{
		ldap.EscapeFilter(userDN),
		ldap.EscapeFilter(username),
	}

	var renderedQuery bytes.Buffer
	t.Execute(&renderedQuery, context)

	if b.Logger().IsDebug() {
		b.Logger().Debug("auth/ldap: Searching", "groupdn", cfg.GroupDN, "rendered_query", renderedQuery.String())
	}

	result, err := c.Search(&ldap.SearchRequest{
		BaseDN: cfg.GroupDN,
		Scope:  2, // subtree
		Filter: renderedQuery.String(),
		Attributes: []string{
			cfg.GroupAttr,
		},
	})
	if err != nil {
		return nil, fmt.Errorf("LDAP search failed: %v", err)
	}

	for _, e := range result.Entries {
		dn, err := ldap.ParseDN(e.DN)
		if err != nil || len(dn.RDNs) == 0 {
			continue
		}

		// Enumerate attributes of each result, parse out CN and add as group
		values := e.GetAttributeValues(cfg.GroupAttr)
		if len(values) > 0 {
			for _, val := range values {
				groupCN := b.getCN(val)
				ldapMap[groupCN] = true
			}
		} else {
			// If groupattr didn't resolve, use self (enumerating group objects)
			groupCN := b.getCN(e.DN)
			ldapMap[groupCN] = true
		}
	}

	ldapGroups := make([]string, 0, len(ldapMap))
	for key, _ := range ldapMap {
		ldapGroups = append(ldapGroups, key)
	}

	return ldapGroups, nil
}
Пример #11
0
func (lc *LDAPClient) getUserSearchFilter(samAccountName string) string {
	userFilter := fmt.Sprintf("(&(%s=%s)%s)",
		AD_USER_ATTRIBUTE, ldap.EscapeFilter(samAccountName), AD_USER_FILTER)
	return userFilter
}
Пример #12
0
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) {

	cfg, err := b.Config(req)
	if err != nil {
		return nil, nil, err
	}
	if cfg == nil {
		return nil, logical.ErrorResponse("ldap backend not configured"), nil
	}

	c, err := cfg.DialLDAP()
	if err != nil {
		return nil, logical.ErrorResponse(err.Error()), nil
	}
	binddn := ""
	if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") {
		if err = c.Bind(cfg.BindDN, cfg.BindPassword); err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind (service) failed: %v", err)), nil
		}
		sresult, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)),
		})
		if err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search for binddn failed: %v", err)), nil
		}
		if len(sresult.Entries) != 1 {
			return nil, logical.ErrorResponse("LDAP search for binddn 0 or not uniq"), nil
		}
		binddn = sresult.Entries[0].DN
	} else {
		if cfg.UPNDomain != "" {
			binddn = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain)
		} else {
			binddn = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN)
		}
	}
	if err = c.Bind(binddn, password); err != nil {
		return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil
	}

	userdn := ""
	if cfg.UPNDomain != "" {
		// Find the distinguished name for the user if userPrincipalName used for login
		sresult, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(binddn)),
		})
		if err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil
		}
		for _, e := range sresult.Entries {
			userdn = e.DN
		}
	} else {
		userdn = binddn
	}

	// Enumerate all groups the user is member of. The search filter should
	// work with both openldap and MS AD standard schemas.
	sresult, err := c.Search(&ldap.SearchRequest{
		BaseDN: cfg.GroupDN,
		Scope:  2, // subtree
		Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userdn), ldap.EscapeFilter(userdn)),
	})
	if err != nil {
		return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil
	}

	var allgroups []string
	var policies []string

	user, err := b.User(req.Storage, username)
	if err == nil && user != nil {
		allgroups = append(allgroups, user.Groups...)
	}

	for _, e := range sresult.Entries {
		dn, err := ldap.ParseDN(e.DN)
		if err != nil || len(dn.RDNs) == 0 || len(dn.RDNs[0].Attributes) == 0 {
			continue
		}
		gname := dn.RDNs[0].Attributes[0].Value
		allgroups = append(allgroups, gname)
	}

	for _, gname := range allgroups {
		group, err := b.Group(req.Storage, gname)
		if err == nil && group != nil {
			policies = append(policies, group.Policies...)
		}
	}

	if len(policies) == 0 {
		return nil, logical.ErrorResponse("user is not member of any authorized group"), nil
	}

	return policies, nil, nil
}
Пример #13
0
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) {

	cfg, err := b.Config(req)
	if err != nil {
		return nil, nil, err
	}
	if cfg == nil {
		return nil, logical.ErrorResponse("ldap backend not configured"), nil
	}

	c, err := cfg.DialLDAP()
	if err != nil {
		return nil, logical.ErrorResponse(err.Error()), nil
	}
	if c == nil {
		return nil, logical.ErrorResponse("invalid connection returned from LDAP dial"), nil
	}
	binddn := ""
	if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") {
		if err = c.Bind(cfg.BindDN, cfg.BindPassword); err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind (service) failed: %v", err)), nil
		}
		sresult, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)),
		})
		if err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search for binddn failed: %v", err)), nil
		}
		if len(sresult.Entries) != 1 {
			return nil, logical.ErrorResponse("LDAP search for binddn 0 or not uniq"), nil
		}
		binddn = sresult.Entries[0].DN
	} else {
		if cfg.UPNDomain != "" {
			binddn = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain)
		} else {
			binddn = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN)
		}
	}
	if err = c.Bind(binddn, password); err != nil {
		return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil
	}

	userdn := ""
	if cfg.UPNDomain != "" {
		// Find the distinguished name for the user if userPrincipalName used for login
		sresult, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.UserDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(binddn)),
		})
		if err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil
		}
		for _, e := range sresult.Entries {
			userdn = e.DN
		}
	} else {
		userdn = binddn
	}

	var allgroups []string
	var policies []string
	resp := &logical.Response{
		Data: map[string]interface{}{},
	}

	// Fetch custom (local) groups the user has been added to
	user, err := b.User(req.Storage, username)
	if err == nil && user != nil {
		allgroups = append(allgroups, user.Groups...)
	}

	if cfg.GroupDN != "" {
		// Enumerate all groups the user is member of. The search filter should
		// work with both openldap and MS AD standard schemas.
		sresult, err := c.Search(&ldap.SearchRequest{
			BaseDN: cfg.GroupDN,
			Scope:  2, // subtree
			Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userdn), ldap.EscapeFilter(userdn)),
		})
		if err != nil {
			return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil
		}

		for _, e := range sresult.Entries {
			dn, err := ldap.ParseDN(e.DN)
			if err != nil || len(dn.RDNs) == 0 || len(dn.RDNs[0].Attributes) == 0 {
				continue
			}
			gname := dn.RDNs[0].Attributes[0].Value
			allgroups = append(allgroups, gname)
		}
	} else {
		resp.AddWarning("no group DN configured; only policies from locally-defined groups available")
	}

	for _, gname := range allgroups {
		group, err := b.Group(req.Storage, gname)
		if err == nil && group != nil {
			policies = append(policies, group.Policies...)
		}
	}

	if len(policies) == 0 {
		errStr := "user is not a member of any authorized group"
		if len(resp.Warnings()) > 0 {
			errStr = fmt.Sprintf("%s; additionally, %s", errStr, resp.Warnings()[0])
		}

		resp.Data["error"] = errStr
		return nil, resp, nil
	}

	return policies, resp, nil
}