package rfc2307 import ( "fmt" "gopkg.in/ldap.v2" "k8s.io/kubernetes/pkg/util/sets" "github.com/openshift/origin/pkg/auth/ldaputil" "github.com/openshift/origin/pkg/auth/ldaputil/ldapclient" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/groupdetector" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/interfaces" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/syncerror" ) // NewLDAPInterface builds a new LDAPInterface using a schema-appropriate config func NewLDAPInterface(clientConfig ldapclient.Config, groupQuery ldaputil.LDAPQueryOnAttribute, groupNameAttributes []string, groupMembershipAttributes []string, userQuery ldaputil.LDAPQueryOnAttribute, userNameAttributes []string, errorHandler syncerror.Handler) *LDAPInterface { return &LDAPInterface{ clientConfig: clientConfig, groupQuery: groupQuery, groupNameAttributes: groupNameAttributes, groupMembershipAttributes: groupMembershipAttributes, userQuery: userQuery, userNameAttributes: userNameAttributes, cachedUsers: map[string]*ldap.Entry{}, cachedGroups: map[string]*ldap.Entry{}, errorHandler: errorHandler, } } // LDAPInterface extracts the member list of an LDAP group entry from an LDAP server // with first-class LDAP entries for groups. The LDAPInterface is *NOT* thread-safe. type LDAPInterface struct { // clientConfig holds LDAP connection information clientConfig ldapclient.Config // groupQuery holds the information necessary to make an LDAP query for a specific first-class group entry on the LDAP server groupQuery ldaputil.LDAPQueryOnAttribute // groupNameAttributes defines which attributes on an LDAP group entry will be interpreted as its name to use for an OpenShift group groupNameAttributes []string // groupMembershipAttributes defines which attributes on an LDAP group entry will be interpreted as its members ldapUserUID groupMembershipAttributes []string // userQuery holds the information necessary to make an LDAP query for a specific first-class user entry on the LDAP server userQuery ldaputil.LDAPQueryOnAttribute // UserNameAttributes defines which attributes on an LDAP user entry will be interpreted as its' name userNameAttributes []string // cachedGroups holds the result of group queries for later reference, indexed on group UID // e.g. this will map an LDAP group UID to the LDAP entry returned from the query made using it cachedGroups map[string]*ldap.Entry // cachedUsers holds the result of user queries for later reference, indexed on user UID // e.g. this will map an LDAP user UID to the LDAP entry returned from the query made using it cachedUsers map[string]*ldap.Entry // errorHandler handles errors that occur errorHandler syncerror.Handler } // The LDAPInterface must conform to the following interfaces var _ interfaces.LDAPMemberExtractor = &LDAPInterface{} var _ interfaces.LDAPGroupGetter = &LDAPInterface{} var _ interfaces.LDAPGroupLister = &LDAPInterface{} // ExtractMembers returns the LDAP member entries for a group specified with a ldapGroupUID func (e *LDAPInterface) ExtractMembers(ldapGroupUID string) ([]*ldap.Entry, error) { // get group entry from LDAP group, err := e.GroupEntryFor(ldapGroupUID) if err != nil { return nil, err } // extract member UIDs from group entry var ldapMemberUIDs []string for _, attribute := range e.groupMembershipAttributes { ldapMemberUIDs = append(ldapMemberUIDs, group.GetAttributeValues(attribute)...) } members := []*ldap.Entry{} // find members on LDAP server or in cache for _, ldapMemberUID := range ldapMemberUIDs { memberEntry, err := e.userEntryFor(ldapMemberUID) if err == nil { members = append(members, memberEntry) continue } err = syncerror.NewMemberLookupError(ldapGroupUID, ldapMemberUID, err) handled, fatalErr := e.errorHandler.HandleError(err) if fatalErr != nil { return nil, fatalErr } if !handled { return nil, err } } return members, nil } // GroupEntryFor returns an LDAP group entry for the given group UID by searching the internal cache // of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry. // This also satisfies the LDAPGroupGetter interface func (e *LDAPInterface) GroupEntryFor(ldapGroupUID string) (*ldap.Entry, error) { group, exists := e.cachedGroups[ldapGroupUID] if exists { return group, nil } searchRequest, err := e.groupQuery.NewSearchRequest(ldapGroupUID, e.requiredGroupAttributes()) if err != nil { return nil, err } group, err = ldaputil.QueryForUniqueEntry(e.clientConfig, searchRequest) if err != nil { return nil, err } e.cachedGroups[ldapGroupUID] = group return group, nil } // ListGroups queries for all groups as configured with the common group filter and returns their // LDAP group UIDs. This also satisfies the LDAPGroupLister interface func (e *LDAPInterface) ListGroups() ([]string, error) { searchRequest := e.groupQuery.LDAPQuery.NewSearchRequest(e.requiredGroupAttributes()) groups, err := ldaputil.QueryForEntries(e.clientConfig, searchRequest) if err != nil { return nil, err } ldapGroupUIDs := []string{} for _, group := range groups { ldapGroupUID := ldaputil.GetAttributeValue(group, []string{e.groupQuery.QueryAttribute}) if len(ldapGroupUID) == 0 { return nil, fmt.Errorf("unable to find LDAP group UID for %s", group) } e.cachedGroups[ldapGroupUID] = group ldapGroupUIDs = append(ldapGroupUIDs, ldapGroupUID) } return ldapGroupUIDs, nil } func (e *LDAPInterface) requiredGroupAttributes() []string { allAttributes := sets.NewString(e.groupNameAttributes...) // these attributes will be used for a future openshift group name mapping allAttributes.Insert(e.groupMembershipAttributes...) // these attribute are used for finding group members allAttributes.Insert(e.groupQuery.QueryAttribute) // this is used for extracting the group UID (otherwise an entry isn't self-describing) return allAttributes.List() } // userEntryFor returns an LDAP group entry for the given group UID by searching the internal cache // of the LDAPInterface first, then sending an LDAP query if the cache did not contain the entry func (e *LDAPInterface) userEntryFor(ldapUserUID string) (user *ldap.Entry, err error) { user, exists := e.cachedUsers[ldapUserUID] if exists { return user, nil } searchRequest, err := e.userQuery.NewSearchRequest(ldapUserUID, e.requiredUserAttributes()) if err != nil { return nil, err } user, err = ldaputil.QueryForUniqueEntry(e.clientConfig, searchRequest) if err != nil { return nil, err } e.cachedUsers[ldapUserUID] = user return user, nil } func (e *LDAPInterface) requiredUserAttributes() []string { allAttributes := sets.NewString(e.userNameAttributes...) // these attributes will be used for a future openshift user name mapping allAttributes.Insert(e.userQuery.QueryAttribute) // this is used for extracting the user UID (otherwise an entry isn't self-describing) return allAttributes.List() } // Exists determines if a group idenified with its LDAP group UID exists on the LDAP server func (e *LDAPInterface) Exists(ldapGroupUID string) (bool, error) { return groupdetector.NewGroupBasedDetector(e).Exists(ldapGroupUID) }