update ldap
continuous-integration/drone/push Build is passing

This commit is contained in:
Nathan Coad
2026-04-21 11:00:40 +10:00
parent 361ba7719b
commit 4b1b985862
4 changed files with 174 additions and 39 deletions
+113 -35
View File
@@ -107,13 +107,24 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
Username: username,
UserDN: username,
}
if whoami, err := conn.WhoAmI(nil); err != nil {
identity.Diagnostics = append(identity.Diagnostics, fmt.Sprintf("whoami_failed:%v", err))
} else if boundDN := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(whoami.AuthzID), "dn:")); boundDN != "" {
identity.UserDN = boundDN
identity.Diagnostics = append(identity.Diagnostics, "whoami_dn_resolved")
} else {
identity.Diagnostics = append(identity.Diagnostics, "whoami_dn_empty")
}
entry, err := a.lookupUserEntry(conn, username)
entry, lookupStrategy, err := a.lookupUserEntry(conn, username, identity.UserDN)
if err != nil {
return LDAPIdentity{}, err
}
if entry != nil {
identity.Diagnostics = append(identity.Diagnostics, "user_entry_found")
if lookupStrategy == "" {
lookupStrategy = "unknown"
}
identity.Diagnostics = append(identity.Diagnostics, "user_entry_found:"+lookupStrategy)
if strings.TrimSpace(entry.DN) != "" {
identity.UserDN = entry.DN
}
@@ -140,6 +151,7 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
}
}
groupFilter := buildGroupMembershipFilter(identity.UserDN, principalCandidates(username))
groupEntries, err := conn.Search(ldap.NewSearchRequest(
a.baseDN,
ldap.ScopeWholeSubtree,
@@ -147,11 +159,7 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
0,
0,
false,
fmt.Sprintf("(|(member=%s)(uniqueMember=%s)(memberUid=%s))",
ldap.EscapeFilter(identity.UserDN),
ldap.EscapeFilter(identity.UserDN),
ldap.EscapeFilter(username),
),
groupFilter,
[]string{"dn"},
nil,
))
@@ -272,10 +280,24 @@ func (a *LDAPAuthenticator) buildTLSConfig() (*tls.Config, error) {
return tlsConfig, nil
}
func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string) (*ldap.Entry, error) {
func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string, userDNHint string) (*ldap.Entry, string, error) {
dnCandidates := compactTrimmedStrings([]string{userDNHint})
if looksLikeDN(username) {
dnCandidates = append(dnCandidates, strings.TrimSpace(username))
}
seenDN := make(map[string]struct{}, len(dnCandidates))
for _, dn := range dnCandidates {
key := normalizeDN(dn)
if key == "" {
continue
}
if _, ok := seenDN[key]; ok {
continue
}
seenDN[key] = struct{}{}
searchRes, err := conn.Search(ldap.NewSearchRequest(
username,
dn,
ldap.ScopeBaseObject,
ldap.NeverDerefAliases,
1,
@@ -286,37 +308,41 @@ func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string) (*
nil,
))
if err != nil {
return nil, fmt.Errorf("%w: unable to load user entry: %v", ErrLDAPOperationFailed, err)
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
continue
}
return nil, "", fmt.Errorf("%w: unable to load user entry by dn: %v", ErrLDAPOperationFailed, err)
}
if len(searchRes.Entries) == 0 {
return nil, nil
if len(searchRes.Entries) > 0 {
return searchRes.Entries[0], "dn", nil
}
return searchRes.Entries[0], nil
}
searchRes, err := conn.Search(ldap.NewSearchRequest(
a.baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
2,
0,
false,
fmt.Sprintf("(|(uid=%s)(cn=%s)(sAMAccountName=%s)(userPrincipalName=%s))",
ldap.EscapeFilter(username),
ldap.EscapeFilter(username),
ldap.EscapeFilter(username),
ldap.EscapeFilter(username),
),
[]string{"uid", "sAMAccountName", "userPrincipalName", "cn", "memberOf"},
nil,
))
if err != nil {
return nil, fmt.Errorf("%w: user lookup failed: %v", ErrLDAPOperationFailed, err)
for _, principal := range principalCandidates(username) {
searchRes, err := conn.Search(ldap.NewSearchRequest(
a.baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
2,
0,
false,
fmt.Sprintf("(|(uid=%s)(cn=%s)(sAMAccountName=%s)(userPrincipalName=%s))",
ldap.EscapeFilter(principal),
ldap.EscapeFilter(principal),
ldap.EscapeFilter(principal),
ldap.EscapeFilter(principal),
),
[]string{"uid", "sAMAccountName", "userPrincipalName", "cn", "memberOf"},
nil,
))
if err != nil {
return nil, "", fmt.Errorf("%w: user lookup failed: %v", ErrLDAPOperationFailed, err)
}
if len(searchRes.Entries) > 0 {
return searchRes.Entries[0], "principal", nil
}
}
if len(searchRes.Entries) == 0 {
return nil, nil
}
return searchRes.Entries[0], nil
return nil, "", nil
}
func normalizeDN(value string) string {
@@ -352,6 +378,58 @@ func looksLikeDN(value string) bool {
return strings.Contains(value, "=") && strings.Contains(value, ",")
}
func principalCandidates(username string) []string {
username = strings.TrimSpace(username)
if username == "" {
return nil
}
seen := make(map[string]struct{}, 4)
candidates := make([]string, 0, 4)
add := func(value string) {
value = strings.TrimSpace(value)
if value == "" {
return
}
key := strings.ToLower(value)
if _, ok := seen[key]; ok {
return
}
seen[key] = struct{}{}
candidates = append(candidates, value)
}
add(username)
if idx := strings.LastIndex(username, `\`); idx >= 0 && idx < len(username)-1 {
add(username[idx+1:])
}
if idx := strings.Index(username, "@"); idx > 0 {
add(username[:idx])
}
return candidates
}
func buildGroupMembershipFilter(userDN string, principals []string) string {
clauses := make([]string, 0, 2+len(principals))
userDN = strings.TrimSpace(userDN)
if userDN != "" {
escapedDN := ldap.EscapeFilter(userDN)
clauses = append(clauses, "(member="+escapedDN+")", "(uniqueMember="+escapedDN+")")
}
for _, principal := range principals {
principal = strings.TrimSpace(principal)
if principal == "" {
continue
}
clauses = append(clauses, "(memberUid="+ldap.EscapeFilter(principal)+")")
}
if len(clauses) == 0 {
return "(objectClass=group)"
}
return "(|" + strings.Join(clauses, "") + ")"
}
func ctxErr(ctx context.Context) error {
if ctx == nil {
return nil