Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LDAP alias dereferencing support to user and group searches #3713

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 69 additions & 7 deletions connector/ldap/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ type Config struct {
// * "one" - only search one level
Scope string `json:"scope"`

// Can be:
// * "never"
// * "searching"
// * "finding"
// * "always"
// Defaults to "never"
Deref string `json:"deref"`

// A mapping of attributes on the user entry to claims.
IDAttr string `json:"idAttr"` // Defaults to "uid"
EmailAttr string `json:"emailAttr"` // Defaults to "mail"
Expand All @@ -138,6 +146,14 @@ type Config struct {

Scope string `json:"scope"` // Defaults to "sub"

// Can be:
// * "never"
// * "searching"
// * "finding"
// * "always"
// Defaults to "never"
Deref string `json:"deref"`

// DEPRECATED config options. Those are left for backward compatibility.
// See "UserMatchers" below for the current group to user matching implementation
// TODO: should be eventually removed from the code
Expand Down Expand Up @@ -185,6 +201,35 @@ func parseScope(s string) (int, bool) {
return 0, false
}

func derefString(i int) string {
switch i {
case ldap.NeverDerefAliases:
return "never"
case ldap.DerefInSearching:
return "searching"
case ldap.DerefFindingBaseObj:
return "finding"
case ldap.DerefAlways:
return "always"
default:
return ""
}
}

func parseDeref(s string) (int, bool) {
switch s {
case "", "never":
return ldap.NeverDerefAliases, true
case "searching":
return ldap.DerefInSearching, true
case "finding":
return ldap.DerefFindingBaseObj, true
case "always":
return ldap.DerefAlways, true
}
return 0, false
}

// Build a list of group attr name to user attr value matchers.
// Function exists here to allow backward compatibility between old and new
// group to user matching implementations.
Expand Down Expand Up @@ -244,6 +289,11 @@ func (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) {
}
}

// Required: explicit email address OR domain suffix to username
if c.UserSearch.EmailAttr == "" && c.UserSearch.EmailSuffix == "" {
return nil, fmt.Errorf("ldap: either emailAttr or emailSuffix must be provided")
}

var (
host string
err error
Expand Down Expand Up @@ -284,21 +334,31 @@ func (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) {
if !ok {
return nil, fmt.Errorf("userSearch.Scope unknown value %q", c.UserSearch.Scope)
}
userSearchDeref, ok := parseDeref(c.UserSearch.Deref)
if !ok {
return nil, fmt.Errorf("userSearch.Deref unknown value %q", c.UserSearch.Deref)
}
groupSearchScope, ok := parseScope(c.GroupSearch.Scope)
if !ok {
return nil, fmt.Errorf("groupSearch.Scope unknown value %q", c.GroupSearch.Scope)
}
groupSearchDeref, ok := parseDeref(c.GroupSearch.Deref)
if !ok {
return nil, fmt.Errorf("groupSearch.Deref unknown value %q", c.GroupSearch.Deref)
}

// TODO(nabokihms): remove it after deleting deprecated groupSearch options
c.GroupSearch.UserMatchers = userMatchers(c, logger)
return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, logger}, nil
return &ldapConnector{*c, userSearchScope, userSearchDeref, groupSearchScope, groupSearchDeref, tlsConfig, logger}, nil
}

type ldapConnector struct {
Config

userSearchScope int
userSearchDeref int
groupSearchScope int
groupSearchDeref int

tlsConfig *tls.Config

Expand Down Expand Up @@ -424,6 +484,7 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E
BaseDN: c.UserSearch.BaseDN,
Filter: filter,
Scope: c.userSearchScope,
DerefAliases: c.userSearchDeref,
// We only need to search for these specific requests.
Attributes: []string{
c.UserSearch.IDAttr,
Expand All @@ -445,7 +506,7 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E
}

c.logger.Info("performing ldap search",
"base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter)
"base_dn", req.BaseDN, "scope", scopeString(req.Scope), "deref", derefString(req.DerefAliases), "filter", req.Filter)
resp, err := conn.Search(req)
if err != nil {
return ldap.Entry{}, false, fmt.Errorf("ldap: search with filter %q failed: %v", req.Filter, err)
Expand Down Expand Up @@ -600,16 +661,17 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string,
}

req := &ldap.SearchRequest{
BaseDN: c.GroupSearch.BaseDN,
Filter: filter,
Scope: c.groupSearchScope,
Attributes: []string{c.GroupSearch.NameAttr},
BaseDN: c.GroupSearch.BaseDN,
Filter: filter,
Scope: c.groupSearchScope,
DerefAliases: c.groupSearchDeref,
Attributes: []string{c.GroupSearch.NameAttr},
}

gotGroups := false
if err := c.do(ctx, func(conn *ldap.Conn) error {
c.logger.Info("performing ldap search",
"base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter)
"base_dn", req.BaseDN, "scope", scopeString(req.Scope), "deref", derefString(req.DerefAliases), "filter", req.Filter)
resp, err := conn.Search(req)
if err != nil {
return fmt.Errorf("ldap: search failed: %v", err)
Expand Down