From 46673e83c749b27b8412eab1fbadd6b7f7233d5b Mon Sep 17 00:00:00 2001 From: Vladimir Ermakov Date: Thu, 30 Jan 2025 15:39:02 +0100 Subject: [PATCH] feat(ldap): allow to customize user filter Fix #2924 Signed-off-by: Vladimir Ermakov --- examples/config-ldap.json | 3 ++- pkg/api/authn.go | 3 ++- pkg/api/config/config.go | 1 + pkg/api/controller_test.go | 53 ++++++++++++++++++++++++++++---------- pkg/api/ldap.go | 15 +++++++++-- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/examples/config-ldap.json b/examples/config-ldap.json index d7bc0f7fd..f649073f8 100644 --- a/examples/config-ldap.json +++ b/examples/config-ldap.json @@ -19,6 +19,7 @@ "startTLS": false, "baseDN":"ou=Users,dc=example,dc=org", "userAttribute": "uid", + "userFilter": "(!(nsaccountlock=TRUE))", "userGroupAttribute": "memberOf", "skipVerify": true, "subtreeSearch": true @@ -69,4 +70,4 @@ "output": "/tmp/zot.log", "audit": "/tmp/zot-audit.log" } -} \ No newline at end of file +} diff --git a/pkg/api/authn.go b/pkg/api/authn.go index 5558c54dc..5f14f6c1d 100644 --- a/pkg/api/authn.go +++ b/pkg/api/authn.go @@ -272,7 +272,8 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun BindDN: ldapConfig.BindDN(), BindPassword: ldapConfig.BindPassword(), UserGroupAttribute: ldapConfig.UserGroupAttribute, // from config - UserFilter: fmt.Sprintf("(%s=%%s)", ldapConfig.UserAttribute), + UserAttribute: ldapConfig.UserAttribute, + UserFilter: ldapConfig.UserFilter, InsecureSkipVerify: ldapConfig.SkipVerify, ServerName: ldapConfig.Address, Log: ctlr.Log, diff --git a/pkg/api/config/config.go b/pkg/api/config/config.go index 07929c21f..1fe1b3101 100644 --- a/pkg/api/config/config.go +++ b/pkg/api/config/config.go @@ -175,6 +175,7 @@ type LDAPConfig struct { UserGroupAttribute string BaseDN string UserAttribute string + UserFilter string CACert string } diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index a61de5fb3..4adecc985 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -90,6 +90,7 @@ var ( LDAPBaseDN = "ou=" + username //nolint: gochecknoglobals LDAPBindDN = "cn=reader," + LDAPBaseDN //nolint: gochecknoglobals LDAPBindPassword = "ldappass" //nolint: gochecknoglobals + LDAPUserAttr = "uid" //nolint: gochecknoglobals ) func TestNew(t *testing.T) { @@ -2974,6 +2975,13 @@ func (l *testLDAPServer) Search(boundDN string, req vldap.SearchRequest, }, nil } + if req.Filter == "(&(uid=locked-user)((!(nsaccountlock=TRUE))))" { + return vldap.ServerSearchResult{ + Entries: []*vldap.Entry{}, + ResultCode: vldap.LDAPResultSuccess, + }, nil + } + check := fmt.Sprintf("(uid=%s)", username) if check == req.Filter { @@ -3769,13 +3777,14 @@ func TestLDAPClient(t *testing.T) { // bad user credentials with anonymous authentication lClient = &api.LDAPClient{ - Host: LDAPAddress, - Port: ldapPort, - BindDN: LDAPBindDN, - BindPassword: LDAPBindPassword, - Base: LDAPBaseDN, - UserFilter: "(uid=%s)", - SkipTLS: true, + Host: LDAPAddress, + Port: ldapPort, + BindDN: LDAPBindDN, + BindPassword: LDAPBindPassword, + Base: LDAPBaseDN, + UserAttribute: LDAPUserAttr, + UserFilter: "", + SkipTLS: true, } _, _, _, err = lClient.Authenticate("fail-user-bind", "") @@ -3783,17 +3792,33 @@ func TestLDAPClient(t *testing.T) { // bad user credentials with anonymous authentication lClient = &api.LDAPClient{ - Host: LDAPAddress, - Port: ldapPort, - BindDN: LDAPBindDN, - BindPassword: LDAPBindPassword, - Base: LDAPBaseDN, - UserFilter: "(uid=%s)", - SkipTLS: true, + Host: LDAPAddress, + Port: ldapPort, + BindDN: LDAPBindDN, + BindPassword: LDAPBindPassword, + Base: LDAPBaseDN, + UserAttribute: LDAPUserAttr, + UserFilter: "", + SkipTLS: true, } _, _, _, err = lClient.Authenticate("fail-user-bind", "pass") So(err, ShouldNotBeNil) + + // user filtered by additional filter (disabled account in FreeIPA) + lClient = &api.LDAPClient{ + Host: LDAPAddress, + Port: ldapPort, + BindDN: LDAPBindDN, + BindPassword: LDAPBindPassword, + Base: LDAPBaseDN, + UserAttribute: LDAPUserAttr, + UserFilter: "(!(nsaccountlock=TRUE))", + SkipTLS: true, + } + + _, _, _, err = lClient.Authenticate("locked-user", "pass") + So(err, ShouldNotBeNil) }) } diff --git a/pkg/api/ldap.go b/pkg/api/ldap.go index bd9ac45dc..ebd9d430d 100644 --- a/pkg/api/ldap.go +++ b/pkg/api/ldap.go @@ -29,7 +29,8 @@ type LDAPClient struct { UserGroupAttribute string // e.g. "memberOf" Host string ServerName string - UserFilter string // e.g. "(uid=%s)" + UserFilter string // e.g. "(!(nsaccountlock=TRUE))" + UserAttribute string // e.g. "uid" Conn *ldap.Conn ClientCertificates []tls.Certificate // Adding client certificates ClientCAs *x509.CertPool @@ -187,7 +188,7 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string] searchRequest := ldap.NewSearchRequest( lc.Base, searchScope, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf(lc.UserFilter, username), + lc.userFilter(username), attributes, nil, ) @@ -242,3 +243,13 @@ func (lc *LDAPClient) Authenticate(username, password string) (bool, map[string] return true, user, userGroups, nil } + +func (lc *LDAPClient) userFilter(username string) string { + filter := fmt.Sprintf("(%s=%s)", lc.UserAttribute, ldap.EscapeFilter(username)) + + if lc.UserFilter != "" { + filter = fmt.Sprintf("(&(%s)(%s))", filter, lc.UserFilter) + } + + return filter +}