From 947ecec02b7c3b630bfbcd35ca2d9de41b5ce925 Mon Sep 17 00:00:00 2001 From: Ilya Kogan <1840337+ikogan@users.noreply.github.com> Date: Tue, 25 Jan 2022 05:27:27 -0500 Subject: [PATCH] outposts/ldap: Fix more case sensitivity issues. (#2144) --- internal/outpost/ldap/bind/direct/direct.go | 3 ++- internal/outpost/ldap/instance.go | 18 +++++++------- internal/outpost/ldap/search/direct/direct.go | 24 +++++++++---------- internal/outpost/ldap/search/memory/memory.go | 24 +++++++++---------- internal/outpost/ldap/search/request.go | 1 - internal/outpost/ldap/utils/utils.go | 5 ++++ internal/outpost/ldap/utils/utils_group.go | 6 +++-- 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/internal/outpost/ldap/bind/direct/direct.go b/internal/outpost/ldap/bind/direct/direct.go index 4ddf4f257..7574aa4a7 100644 --- a/internal/outpost/ldap/bind/direct/direct.go +++ b/internal/outpost/ldap/bind/direct/direct.go @@ -16,6 +16,7 @@ import ( "goauthentik.io/internal/outpost/ldap/flags" "goauthentik.io/internal/outpost/ldap/metrics" "goauthentik.io/internal/outpost/ldap/server" + "goauthentik.io/internal/outpost/ldap/utils" ) const ContextUserKey = "ak_user" @@ -35,7 +36,7 @@ func NewDirectBinder(si server.LDAPServerInstance) *DirectBinder { } func (db *DirectBinder) GetUsername(dn string) (string, error) { - if !strings.HasSuffix(strings.ToLower(dn), strings.ToLower(db.si.GetBaseDN())) { + if !utils.HasSuffixNoCase(dn, db.si.GetBaseDN()) { return "", errors.New("invalid base DN") } dns, err := goldap.ParseDN(dn) diff --git a/internal/outpost/ldap/instance.go b/internal/outpost/ldap/instance.go index 606f476f2..b08ff5abf 100644 --- a/internal/outpost/ldap/instance.go +++ b/internal/outpost/ldap/instance.go @@ -140,26 +140,26 @@ func (pi *ProviderInstance) GetNeededObjects(scope int, baseDN string, filterOC // If our requested base DN doesn't match any of the container DNs, then // we're probably loading a user or group. If it does, then make sure our // scope will eventually take us to users or groups. - if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) { + if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.UserDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetUserOCs()) { if baseDN != pi.UserDN && baseDN != pi.BaseDN || - baseDN == pi.BaseDN && scope > 1 || - baseDN == pi.UserDN && scope > 0 { + strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || + strings.EqualFold(baseDN, pi.UserDN) && scope > 0 { needUsers = true } } - if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) { + if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.GroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetGroupOCs()) { if baseDN != pi.GroupDN && baseDN != pi.BaseDN || - baseDN == pi.BaseDN && scope > 1 || - baseDN == pi.GroupDN && scope > 0 { + strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || + strings.EqualFold(baseDN, pi.GroupDN) && scope > 0 { needGroups = true } } - if (baseDN == pi.BaseDN || strings.HasSuffix(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) { + if (strings.EqualFold(baseDN, pi.BaseDN) || utils.HasSuffixNoCase(baseDN, pi.VirtualGroupDN)) && utils.IncludeObjectClass(filterOC, ldapConstants.GetVirtualGroupOCs()) { if baseDN != pi.VirtualGroupDN && baseDN != pi.BaseDN || - baseDN == pi.BaseDN && scope > 1 || - baseDN == pi.VirtualGroupDN && scope > 0 { + strings.EqualFold(baseDN, pi.BaseDN) && scope > 1 || + strings.EqualFold(baseDN, pi.VirtualGroupDN) && scope > 0 { needUsers = true } } diff --git a/internal/outpost/ldap/search/direct/direct.go b/internal/outpost/ldap/search/direct/direct.go index 40efcee65..52769dde0 100644 --- a/internal/outpost/ldap/search/direct/direct.go +++ b/internal/outpost/ldap/search/direct/direct.go @@ -36,7 +36,7 @@ func NewDirectSearcher(si server.LDAPServerInstance) *DirectSearcher { func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) { accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access") - baseDN := strings.ToLower(ds.si.GetBaseDN()) + baseDN := ds.si.GetBaseDN() filterOC, err := ldap.GetFilterObjectClass(req.Filter) if err != nil { @@ -59,7 +59,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, }).Inc() return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) } - if !strings.HasSuffix(req.BindDN, ","+baseDN) { + if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { metrics.RequestsRejected.With(prometheus.Labels{ "outpost_name": ds.si.GetOutpostName(), "type": "search", @@ -105,7 +105,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope := req.SearchRequest.Scope needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, filterOC) - if scope >= 0 && req.BaseDN == baseDN { + if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) { if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) { entries = append(entries, ds.si.GetBaseEntry()) } @@ -209,8 +209,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err } - if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseUserDN())) { - singleu := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseUserDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseUserDN())) { + singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseUserDN()) if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseUserDN(), constants.OUUsers)) @@ -220,7 +220,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) { for _, u := range *users { entry := ds.si.UserEntry(u) - if req.BaseDN == entry.DN || !singleu { + if strings.EqualFold(req.BaseDN, entry.DN) || !singleu { entries = append(entries, entry) } } @@ -229,8 +229,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope += 1 // Return the scope to what it was before we descended } - if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseGroupDN())) { - singleg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseGroupDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseGroupDN())) { + singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseGroupDN()) if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseGroupDN(), constants.OUGroups)) @@ -240,7 +240,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) { for _, g := range *groups { entry := group.FromAPIGroup(g, ds.si).Entry() - if req.BaseDN == entry.DN || !singleg { + if strings.EqualFold(req.BaseDN, entry.DN) || !singleg { entries = append(entries, entry) } } @@ -249,8 +249,8 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope += 1 // Return the scope to what it was before we descended } - if scope >= 0 && (req.BaseDN == ds.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) { - singlevg := strings.HasSuffix(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ds.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ds.si.GetBaseVirtualGroupDN())) { + singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ds.si.GetBaseVirtualGroupDN()) if !singlevg || utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ds.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups)) @@ -260,7 +260,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) { for _, u := range *users { entry := group.FromAPIUser(u, ds.si).Entry() - if req.BaseDN == entry.DN || !singlevg { + if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg { entries = append(entries, entry) } } diff --git a/internal/outpost/ldap/search/memory/memory.go b/internal/outpost/ldap/search/memory/memory.go index fdb81588c..3abb14e00 100644 --- a/internal/outpost/ldap/search/memory/memory.go +++ b/internal/outpost/ldap/search/memory/memory.go @@ -39,7 +39,7 @@ func NewMemorySearcher(si server.LDAPServerInstance) *MemorySearcher { func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, error) { accsp := sentry.StartSpan(req.Context(), "authentik.providers.ldap.search.check_access") - baseDN := strings.ToLower(ms.si.GetBaseDN()) + baseDN := ms.si.GetBaseDN() filterOC, err := ldap.GetFilterObjectClass(req.Filter) if err != nil { @@ -62,7 +62,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, }).Inc() return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", req.BindDN) } - if !strings.HasSuffix(req.BindDN, ","+baseDN) { + if !utils.HasSuffixNoCase(req.BindDN, ","+baseDN) { metrics.RequestsRejected.With(prometheus.Labels{ "outpost_name": ms.si.GetOutpostName(), "type": "search", @@ -92,7 +92,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope := req.SearchRequest.Scope needUsers, needGroups := ms.si.GetNeededObjects(scope, req.BaseDN, filterOC) - if scope >= 0 && req.BaseDN == baseDN { + if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) { if utils.IncludeObjectClass(filterOC, constants.GetDomainOCs()) { entries = append(entries, ms.si.GetBaseEntry()) } @@ -155,8 +155,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err } - if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseUserDN())) { - singleu := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseUserDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseUserDN())) { + singleu := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseUserDN()) if !singleu && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseUserDN(), constants.OUUsers)) @@ -166,7 +166,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetUserOCs()) { for _, u := range *users { entry := ms.si.UserEntry(u) - if req.BaseDN == entry.DN || !singleu { + if strings.EqualFold(req.BaseDN, entry.DN) || !singleu { entries = append(entries, entry) } } @@ -175,8 +175,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope += 1 // Return the scope to what it was before we descended } - if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseGroupDN())) { - singleg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseGroupDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseGroupDN())) { + singleg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseGroupDN()) if !singleg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseGroupDN(), constants.OUGroups)) @@ -185,7 +185,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && groups != nil && utils.IncludeObjectClass(filterOC, constants.GetGroupOCs()) { for _, g := range groups { - if req.BaseDN == g.DN || !singleg { + if strings.EqualFold(req.BaseDN, g.DN) || !singleg { entries = append(entries, g.Entry()) } } @@ -194,8 +194,8 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, scope += 1 // Return the scope to what it was before we descended } - if scope >= 0 && (req.BaseDN == ms.si.GetBaseDN() || strings.HasSuffix(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) { - singlevg := strings.HasSuffix(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN()) + if scope >= 0 && (strings.EqualFold(req.BaseDN, ms.si.GetBaseDN()) || utils.HasSuffixNoCase(req.BaseDN, ms.si.GetBaseVirtualGroupDN())) { + singlevg := utils.HasSuffixNoCase(req.BaseDN, ","+ms.si.GetBaseVirtualGroupDN()) if !singlevg && utils.IncludeObjectClass(filterOC, constants.GetContainerOCs()) { entries = append(entries, utils.GetContainerEntry(filterOC, ms.si.GetBaseVirtualGroupDN(), constants.OUVirtualGroups)) @@ -205,7 +205,7 @@ func (ms *MemorySearcher) Search(req *search.Request) (ldap.ServerSearchResult, if scope >= 0 && users != nil && utils.IncludeObjectClass(filterOC, constants.GetVirtualGroupOCs()) { for _, u := range *users { entry := group.FromAPIUser(u, ms.si).Entry() - if req.BaseDN == entry.DN || !singlevg { + if strings.EqualFold(req.BaseDN, entry.DN) || !singlevg { entries = append(entries, entry) } } diff --git a/internal/outpost/ldap/search/request.go b/internal/outpost/ldap/search/request.go index 5f7e9af90..fb7a12379 100644 --- a/internal/outpost/ldap/search/request.go +++ b/internal/outpost/ldap/search/request.go @@ -26,7 +26,6 @@ type Request struct { func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Request, *sentry.Span) { rid := uuid.New().String() bindDN = strings.ToLower(bindDN) - searchReq.BaseDN = strings.ToLower(searchReq.BaseDN) span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search")) span.Description = fmt.Sprintf("%s (%s)", searchReq.BaseDN, ldap.ScopeMap[searchReq.Scope]) span.SetTag("request_uid", rid) diff --git a/internal/outpost/ldap/utils/utils.go b/internal/outpost/ldap/utils/utils.go index 7bcba5f02..1b33d7f9b 100644 --- a/internal/outpost/ldap/utils/utils.go +++ b/internal/outpost/ldap/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "reflect" + "strings" "github.com/nmcclain/ldap" log "github.com/sirupsen/logrus" @@ -117,3 +118,7 @@ func GetContainerEntry(filterOC string, dn string, ou string) *ldap.Entry { return nil } + +func HasSuffixNoCase(s1 string, s2 string) bool { + return strings.HasSuffix(strings.ToLower(s1), strings.ToLower(s2)) +} diff --git a/internal/outpost/ldap/utils/utils_group.go b/internal/outpost/ldap/utils/utils_group.go index 158e3e114..c0a159973 100644 --- a/internal/outpost/ldap/utils/utils_group.go +++ b/internal/outpost/ldap/utils/utils_group.go @@ -1,6 +1,8 @@ package utils import ( + "strings" + goldap "github.com/go-ldap/ldap/v3" ber "github.com/nmcclain/asn1-ber" "github.com/nmcclain/ldap" @@ -41,7 +43,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet) // Switch on type of the value, then check the key switch vv := v.(type) { case string: - switch k { + switch strings.ToLower(k.(string)) { case "cn": return req.Name(vv), false case "member": @@ -54,7 +56,7 @@ func parseFilterForGroupSingle(req api.ApiCoreGroupsListRequest, f *ber.Packet) username := userDN.RDNs[0].Attributes[0].Value // If the DN's first ou is virtual-groups, ignore this filter if len(userDN.RDNs) > 1 { - if userDN.RDNs[1].Attributes[0].Value == constants.OUVirtualGroups || userDN.RDNs[1].Attributes[0].Value == constants.OUGroups { + if strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUVirtualGroups) || strings.EqualFold(userDN.RDNs[1].Attributes[0].Value, constants.OUGroups) { // Since we know we're not filtering anything, skip this request return req, true }