outposts/ldap: add more tests (#6188)

* outposts/ldap: add tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing posixAccount

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* attempt to expand attributes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix routing without base DN

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more logging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove our custom attribute filtering since this is done by the ldap library

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add test for schema

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-07-08 20:51:05 +02:00 committed by GitHub
parent 0c917ac3ed
commit ae7ea4dd11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 56 deletions

View File

@ -40,7 +40,13 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
"name": {u.Name}, "name": {u.Name},
"displayName": {u.Name}, "displayName": {u.Name},
"mail": {*u.Email}, "mail": {*u.Email},
"objectClass": {constants.OCUser, constants.OCOrgPerson, constants.OCInetOrgPerson, constants.OCAKUser}, "objectClass": {
constants.OCUser,
constants.OCOrgPerson,
constants.OCInetOrgPerson,
constants.OCAKUser,
constants.OCPosixAccount,
},
"uidNumber": {pi.GetUidNumber(u)}, "uidNumber": {pi.GetUidNumber(u)},
"gidNumber": {pi.GetUidNumber(u)}, "gidNumber": {pi.GetUidNumber(u)},
"homeDirectory": {fmt.Sprintf("/home/%s", u.Username)}, "homeDirectory": {fmt.Sprintf("/home/%s", u.Username)},

View File

@ -0,0 +1,27 @@
package ldap_test
import (
"testing"
"beryju.io/ldap"
"github.com/stretchr/testify/assert"
"goauthentik.io/api/v3"
)
func Test_UserEntry(t *testing.T) {
pi := ProviderInstance()
u := api.User{
Username: "foo",
Name: "bar",
}
entry := pi.UserEntry(u)
assert.Equal(t, "cn=foo,ou=users,dc=ldap,dc=goauthentik,dc=io", entry.DN)
assert.Contains(t, entry.Attributes, &ldap.EntryAttribute{
Name: "cn",
Values: []string{u.Username},
})
assert.Contains(t, entry.Attributes, &ldap.EntryAttribute{
Name: "displayName",
Values: []string{u.Name},
})
}

View File

@ -0,0 +1,31 @@
package ldap_test
import (
"testing"
"beryju.io/ldap"
"github.com/stretchr/testify/assert"
oldap "goauthentik.io/internal/outpost/ldap"
)
func ProviderInstance() *oldap.ProviderInstance {
return &oldap.ProviderInstance{
BaseDN: "dc=ldap,dc=goauthentik,dc=io",
UserDN: "ou=users,dc=ldap,dc=goauthentik,dc=io",
VirtualGroupDN: "ou=virtual-groups,dc=ldap,dc=goauthentik,dc=io",
GroupDN: "ou=groups,dc=ldap,dc=goauthentik,dc=io",
}
}
func AssertLDAPAttributes(t *testing.T, attrs []*ldap.EntryAttribute, expected *ldap.EntryAttribute) {
found := false
for _, attr := range attrs {
if attr.Name == expected.Name {
assert.Equal(t, expected.Values, attr.Values)
found = true
}
}
if !found {
t.Fatalf("Key %s not found in ldap attributes", expected.Name)
}
}

View File

@ -22,7 +22,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
"type": "search", "type": "search",
"app": selectedApp, "app": selectedApp,
}).Observe(float64(span.EndTime.Sub(span.StartTime))) }).Observe(float64(span.EndTime.Sub(span.StartTime)))
req.Log().WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request") req.Log().WithField("attributes", searchReq.Attributes).WithField("took-ms", span.EndTime.Sub(span.StartTime).Milliseconds()).Info("Search request")
}() }()
defer func() { defer func() {
@ -40,10 +40,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
} }
selectedApp = selectedProvider.GetAppSlug() selectedApp = selectedProvider.GetAppSlug()
result, err := ls.searchRoute(req, selectedProvider) result, err := ls.searchRoute(req, selectedProvider)
if err != nil { return result, err
return result, nil
}
return ls.filterResultAttributes(req, result), nil
} }
func (ls *LDAPServer) fallbackRootDSE(req *search.Request) (ldap.ServerSearchResult, error) { func (ls *LDAPServer) fallbackRootDSE(req *search.Request) (ldap.ServerSearchResult, error) {

View File

@ -87,7 +87,7 @@ func (ds *DirectSearcher) Search(req *search.Request) (ldap.ServerSearchResult,
c := api.NewAPIClient(ds.si.GetAPIClient().GetConfig()) c := api.NewAPIClient(ds.si.GetAPIClient().GetConfig())
c.GetConfig().AddDefaultHeader("X-authentik-outpost-ldap-query", req.Filter) c.GetConfig().AddDefaultHeader("X-authentik-outpost-ldap-query", req.Filter)
scope := req.SearchRequest.Scope scope := req.Scope
needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, req.FilterObjectClass) needUsers, needGroups := ds.si.GetNeededObjects(scope, req.BaseDN, req.FilterObjectClass)
if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) { if scope >= 0 && strings.EqualFold(req.BaseDN, baseDN) {

View File

@ -75,19 +75,3 @@ func (r *Request) Log() *log.Entry {
func (r *Request) RemoteAddr() string { func (r *Request) RemoteAddr() string {
return utils.GetIP(r.conn.RemoteAddr()) return utils.GetIP(r.conn.RemoteAddr())
} }
func (r *Request) FilterLDAPAttributes(res ldap.ServerSearchResult, cb func(attr *ldap.EntryAttribute) bool) ldap.ServerSearchResult {
for _, e := range res.Entries {
newAttrs := []*ldap.EntryAttribute{}
for _, attr := range e.Attributes {
include := cb(attr)
if include {
newAttrs = append(newAttrs, attr)
} else {
r.Log().WithField("key", attr.Name).Trace("filtering out field based on LDAP request")
}
}
e.Attributes = newAttrs
}
return res
}

View File

@ -53,32 +53,3 @@ func (ls *LDAPServer) searchRoute(req *search.Request, pi *ProviderInstance) (ld
req.Log().Trace("routing to default") req.Log().Trace("routing to default")
return pi.searcher.Search(req) return pi.searcher.Search(req)
} }
func (ls *LDAPServer) filterResultAttributes(req *search.Request, result ldap.ServerSearchResult) ldap.ServerSearchResult {
allowedAttributes := []string{}
if len(req.Attributes) == 1 && req.Attributes[0] == constants.SearchAttributeNone {
allowedAttributes = []string{"objectClass"}
}
if len(req.Attributes) > 0 {
// Only strictly filter allowed attributes if we haven't already narrowed the attributes
// down
if len(allowedAttributes) < 1 {
allowedAttributes = req.Attributes
}
// Filter LDAP returned attributes by search requested attributes, taking "1.1"
// into consideration
return req.FilterLDAPAttributes(result, func(attr *ldap.EntryAttribute) bool {
for _, allowed := range allowedAttributes {
if allowed == constants.SearchAttributeAllUser ||
allowed == constants.SearchAttributeAllOperational {
return true
}
if strings.EqualFold(allowed, attr.Name) {
return true
}
}
return false
})
}
return result
}

View File

@ -250,6 +250,7 @@ class TestProviderLDAP(SeleniumTestCase):
"organizationalPerson", "organizationalPerson",
"inetOrgPerson", "inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
"posixAccount",
], ],
"uidNumber": 2000 + o_user.pk, "uidNumber": 2000 + o_user.pk,
"gidNumber": 2000 + o_user.pk, "gidNumber": 2000 + o_user.pk,
@ -277,6 +278,7 @@ class TestProviderLDAP(SeleniumTestCase):
"organizationalPerson", "organizationalPerson",
"inetOrgPerson", "inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
"posixAccount",
], ],
"uidNumber": 2000 + embedded_account.pk, "uidNumber": 2000 + embedded_account.pk,
"gidNumber": 2000 + embedded_account.pk, "gidNumber": 2000 + embedded_account.pk,
@ -304,6 +306,7 @@ class TestProviderLDAP(SeleniumTestCase):
"organizationalPerson", "organizationalPerson",
"inetOrgPerson", "inetOrgPerson",
"goauthentik.io/ldap/user", "goauthentik.io/ldap/user",
"posixAccount",
], ],
"uidNumber": 2000 + self.user.pk, "uidNumber": 2000 + self.user.pk,
"gidNumber": 2000 + self.user.pk, "gidNumber": 2000 + self.user.pk,
@ -320,3 +323,24 @@ class TestProviderLDAP(SeleniumTestCase):
}, },
], ],
) )
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@reconcile_app("authentik_outposts")
def test_ldap_schema(self):
"""Test LDAP Schema"""
self._prepare()
server = Server("ldap://localhost:3389", get_info=ALL)
_connection = Connection(
server,
raise_exceptions=True,
user=f"cn={self.user.username},ou=users,dc=ldap,dc=goauthentik,dc=io",
password=self.user.username,
)
_connection.bind()
self.assertIsNotNone(server.schema)
self.assertTrue(server.schema.is_valid())
self.assertIsNotNone(server.schema.object_classes["goauthentik.io/ldap/user"])