From 48f9d323fa04e8aaa0f755badd99c8378e4b156d Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 4 Apr 2020 11:33:43 +0200 Subject: [PATCH] lib/api: Add LDAP search filters (fixes #5376) (#6488) This adds the functionality to run a user search with a filter for LDAP authentication. The search is done after successful bind, as the binding user. The typical use case is to limit authentication to users who are member of a group or under a certain OU. For example, to only match users in the "Syncthing" group in otherwise default Active Directory set up for example.com: CN=Users,DC=example,DC=com (&(sAMAccountName=%s)(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com)) The search filter is an "and" of two criteria (with the ampersand being XML quoted), - "(sAMAccountName=%s)" matches the user logging in - "(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com)" matches members of the group in question. Authentication will only proceed if the search filter matches precisely one user. --- lib/api/api_auth.go | 29 +++++++++++++++++++++++++++++ lib/config/ldapconfiguration.go | 2 ++ 2 files changed, 31 insertions(+) diff --git a/lib/api/api_auth.go b/lib/api/api_auth.go index 9a623e93a..415cf7b43 100644 --- a/lib/api/api_auth.go +++ b/lib/api/api_auth.go @@ -166,6 +166,35 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo return false } + if cfg.SearchFilter == "" && cfg.SearchBaseDN == "" { + // We're done here. + return true + } + + if cfg.SearchFilter == "" || cfg.SearchBaseDN == "" { + l.Warnln("LDAP configuration: both searchFilter and searchBaseDN must be set, or neither.") + return false + } + + // If a search filter and search base is set we do an LDAP search for + // the user. If this matches precisely one user then we are good to go. + // The search filter uses the same %s interpolation as the bind DN. + + searchString := fmt.Sprintf(cfg.SearchFilter, username) + const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure. + const timeLimit = 60 // Search for up to a minute... + searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil) + + res, err := connection.Search(searchReq) + if err != nil { + l.Warnln("LDAP Search:", err) + return false + } + if len(res.Entries) != 1 { + l.Infof("Wrong number of LDAP search results, %d != 1", len(res.Entries)) + return false + } + return true } diff --git a/lib/config/ldapconfiguration.go b/lib/config/ldapconfiguration.go index c233e66bf..9a3fca7e4 100644 --- a/lib/config/ldapconfiguration.go +++ b/lib/config/ldapconfiguration.go @@ -11,6 +11,8 @@ type LDAPConfiguration struct { BindDN string `xml:"bindDN,omitempty" json:"bindDN"` Transport LDAPTransport `xml:"transport,omitempty" json:"transport"` InsecureSkipVerify bool `xml:"insecureSkipVerify,omitempty" json:"insecureSkipVerify" default:"false"` + SearchBaseDN string `xml:"searchBaseDN,omitempty" json:"searchBaseDN"` + SearchFilter string `xml:"searchFilter,omitempty" json:"searchFilter"` } func (c LDAPConfiguration) Copy() LDAPConfiguration {