From cf5c707e87e05e30e9dcf4b13b615f55c9e7127f Mon Sep 17 00:00:00 2001 From: "Alexey R." Date: Tue, 31 Dec 2024 15:26:18 +0700 Subject: [PATCH 1/4] fix: Add ability to get groups dn from memberof attribute. In the case of using FreeIPA, the user has a set of memberOf attributes that contain the DNs of groups, sudo rules, and HBAC. The ability to use the memberof-dn combination in UserMatchers has been added to directly retrieve groups from the user entity without additional group queries. Additionally, memberOf values that do not have the suffix defined as BaseDN or do not start with the NameAttr ({NameAttr}=groupname,{BaseDN}) are discarded Signed-off-by: Alexey R. --- connector/ldap/ldap.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go index 856949d2..fd7828fc 100644 --- a/connector/ldap/ldap.go +++ b/connector/ldap/ldap.go @@ -62,6 +62,8 @@ import ( type UserMatcher struct { UserAttr string `json:"userAttr"` GroupAttr string `json:"groupAttr"` + // Work only if UserAttr is 'memberOf' and GroupAttr is dn + GroupPrefix string `json:"groupPrefix"` } // Config holds configuration options for LDAP logins. @@ -593,6 +595,49 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, var groups []*ldap.Entry for _, matcher := range c.GroupSearch.UserMatchers { + // When we get groups from memberof user.s entity attribute, we may + // don.t want (Or don.t need) to perform extra search query for each group. + // Also when groupattr is dn (which implies memberof freeipa), we cannot use + // ldapsearch to retrieve group by DN (LDAP restriction) + if strings.ToLower(matcher.UserAttr) == "memberof" && + strings.ToLower(matcher.GroupAttr) == "dn" { + for _, attr := range c.getAttrs(user, matcher.UserAttr) { + // If group dn has no ends with group search base dn - ignore it. + // In FreeIPA case, it also can contain hbac, sudo rules, etc... + if !strings.HasSuffix(attr, c.GroupSearch.BaseDN) { + continue + } + + // Trim {NameAttr}= prefix and baseDN suffix to get group name. + // For NameAttr=cn and BaseDN=cn=groups,dc=example,dc=com : + // cn=groupname,cn=groups,dc=example,dc=com -> groupname + groupName := strings.TrimSuffix( + strings.TrimPrefix(attr, + fmt.Sprintf("%s=", c.GroupSearch.NameAttr)), + fmt.Sprintf(",%s", c.GroupSearch.BaseDN)) + + // Is it needed compability with GroupSearch.Filter? (r9odt) + if !strings.HasPrefix(groupName, matcher.GroupPrefix) { + continue + } + + // Append result to group list as ldap.Entry to process it next wihtout + // extra changes in code. + groups = append(groups, &ldap.Entry{ + DN: attr, + Attributes: []*ldap.EntryAttribute{ + { + Name: c.GroupSearch.NameAttr, + Values: []string{ + groupName, + }, + }, + }, + }) + } + continue + } + for _, attr := range c.getAttrs(user, matcher.UserAttr) { filter := fmt.Sprintf("(%s=%s)", matcher.GroupAttr, ldap.EscapeFilter(attr)) if c.GroupSearch.Filter != "" { From 1a97d72ab1685dd27c372876033a329beb065364 Mon Sep 17 00:00:00 2001 From: Nathan Lacey Date: Wed, 21 May 2025 15:05:42 -0400 Subject: [PATCH 2/4] Resolve CVE by updating gomplate to 4.3.2 Signed-off-by: Nathan Lacey --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9b085e98..3ebb8f93 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ ARG TARGETOS ARG TARGETARCH ARG TARGETVARIANT -ENV GOMPLATE_VERSION=v4.3.0 +ENV GOMPLATE_VERSION=v4.3.2 RUN wget -O /usr/local/bin/gomplate \ "https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \ From 084cf5a90c1bc0c93edc5a0845b769ef9fb579a8 Mon Sep 17 00:00:00 2001 From: "Alexey R." Date: Mon, 11 Aug 2025 15:37:06 +0700 Subject: [PATCH 3/4] fix: rewrite to regex for group filtering Signed-off-by: Alexey R. --- connector/ldap/ldap.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go index fd7828fc..23d974ab 100644 --- a/connector/ldap/ldap.go +++ b/connector/ldap/ldap.go @@ -11,6 +11,7 @@ import ( "net" "net/url" "os" + "regexp" "strings" "github.com/go-ldap/ldap/v3" @@ -63,7 +64,8 @@ type UserMatcher struct { UserAttr string `json:"userAttr"` GroupAttr string `json:"groupAttr"` // Work only if UserAttr is 'memberOf' and GroupAttr is dn - GroupPrefix string `json:"groupPrefix"` + GroupRegexp string `json:"groupRegexp"` + groupMatcher *regexp.Regexp } // Config holds configuration options for LDAP logins. @@ -293,6 +295,13 @@ func (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) { // TODO(nabokihms): remove it after deleting deprecated groupSearch options c.GroupSearch.UserMatchers = userMatchers(c, logger) + for i, _ := range c.GroupSearch.UserMatchers { + c.GroupSearch.UserMatchers[i].groupMatcher, err = regexp.Compile(c.GroupSearch.UserMatchers[i].GroupRegexp) + if err != nil { + logger.Error("Regular expression compilation error", "user_attr", c.GroupSearch.UserMatchers[i].UserAttr, "group_attr", c.GroupSearch.UserMatchers[i].GroupAttr, "err", err.Error()) + c.GroupSearch.UserMatchers[i].groupMatcher, _ = regexp.Compile("") + } + } return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, logger}, nil } @@ -617,7 +626,7 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, fmt.Sprintf(",%s", c.GroupSearch.BaseDN)) // Is it needed compability with GroupSearch.Filter? (r9odt) - if !strings.HasPrefix(groupName, matcher.GroupPrefix) { + if !matcher.groupMatcher.MatchString(groupName) { continue } From 20dfbcc38975c65d1f27c5c5afc3bfef2c2a5d98 Mon Sep 17 00:00:00 2001 From: "Alexey R." Date: Wed, 21 Jan 2026 15:51:50 +0700 Subject: [PATCH 4/4] fix: memberof condition conflict with recursive search Signed-off-by: Alexey R. --- connector/ldap/ldap.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go index 6bf04d5d..f532d351 100644 --- a/connector/ldap/ldap.go +++ b/connector/ldap/ldap.go @@ -652,20 +652,19 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, }, }) } - continue - } - - for _, attr := range c.getAttrs(user, matcher.UserAttr) { - obtained, filter, err := c.queryGroups(ctx, matcher.GroupAttr, attr) - if err != nil { - return nil, err - } - gotGroups := len(obtained) != 0 - if !gotGroups { - // TODO(ericchiang): Is this going to spam the logs? - c.logger.Error("ldap: groups search returned no groups", "filter", filter) + } else { + for _, attr := range c.getAttrs(user, matcher.UserAttr) { + obtained, filter, err := c.queryGroups(ctx, matcher.GroupAttr, attr) + if err != nil { + return nil, err + } + gotGroups := len(obtained) != 0 + if !gotGroups { + // TODO(ericchiang): Is this going to spam the logs? + c.logger.Error("ldap: groups search returned no groups", "filter", filter) + } + groups = append(groups, obtained...) } - groups = append(groups, obtained...) } // If RecursionGroupAttr is not set, convert direct groups into names and return