outpost/ldap: check access based on Group Membership

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-05 00:03:19 +02:00
parent d84d7c26ca
commit 32934fcd38
4 changed files with 32 additions and 27 deletions

View file

@ -37,7 +37,6 @@ from authentik.outposts.docker_tls import DockerInlineTLS
OUR_VERSION = parse(__version__) OUR_VERSION = parse(__version__)
OUTPOST_HELLO_INTERVAL = 10 OUTPOST_HELLO_INTERVAL = 10
LOGGER = get_logger() LOGGER = get_logger()
USER_ATTRIBUTE_LDAP_CAN_SEARCH = "goauthentik.io/ldap/can-search"
class ServiceConnectionInvalid(SentryIgnoredException): class ServiceConnectionInvalid(SentryIgnoredException):
@ -322,21 +321,13 @@ class Outpost(models.Model):
"""Username for service user""" """Username for service user"""
return f"ak-outpost-{self.uuid.hex}" return f"ak-outpost-{self.uuid.hex}"
@property
def user_attributes(self) -> dict[str, Any]:
"""Attributes that will be set on the service account"""
attrs = {USER_ATTRIBUTE_SA: True}
if self.type == OutpostType.LDAP:
attrs[USER_ATTRIBUTE_LDAP_CAN_SEARCH] = True
return attrs
@property @property
def user(self) -> User: def user(self) -> User:
"""Get/create user with access to all required objects""" """Get/create user with access to all required objects"""
users = User.objects.filter(username=self.user_identifier) users = User.objects.filter(username=self.user_identifier)
if not users.exists(): if not users.exists():
user: User = User.objects.create(username=self.user_identifier) user: User = User.objects.create(username=self.user_identifier)
user.attributes = self.user_attributes user.attributes[USER_ATTRIBUTE_SA] = True
user.set_unusable_password() user.set_unusable_password()
user.save() user.save()
else: else:

View file

@ -4,8 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"sync"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/client/outposts" "goauthentik.io/outpost/pkg/client/outposts"
) )
@ -23,15 +23,14 @@ func (ls *LDAPServer) Refresh() error {
userDN := strings.ToLower(fmt.Sprintf("cn=users,%s", provider.BaseDn)) userDN := strings.ToLower(fmt.Sprintf("cn=users,%s", provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("cn=groups,%s", provider.BaseDn)) groupDN := strings.ToLower(fmt.Sprintf("cn=groups,%s", provider.BaseDn))
providers[idx] = &ProviderInstance{ providers[idx] = &ProviderInstance{
BaseDN: provider.BaseDn, BaseDN: provider.BaseDn,
GroupDN: groupDN, GroupDN: groupDN,
UserDN: userDN, UserDN: userDN,
appSlug: *provider.ApplicationSlug, appSlug: *provider.ApplicationSlug,
flowSlug: *provider.BindFlowSlug, flowSlug: *provider.BindFlowSlug,
s: ls, searchAllowedGroups: []*strfmt.UUID{provider.SearchGroup},
log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name), s: ls,
boundUsersMutex: sync.RWMutex{}, log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name),
boundUsers: make(map[string]UserFlags),
} }
} }
ls.providers = providers ls.providers = providers

View file

@ -15,6 +15,7 @@ import (
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
"goauthentik.io/outpost/pkg/client/core" "goauthentik.io/outpost/pkg/client/core"
"goauthentik.io/outpost/pkg/client/flows" "goauthentik.io/outpost/pkg/client/flows"
"goauthentik.io/outpost/pkg/models"
) )
const ContextUserKey = "ak_user" const ContextUserKey = "ak_user"
@ -88,13 +89,25 @@ func (pi *ProviderInstance) Bind(username string, bindPW string, conn net.Conn)
pi.boundUsersMutex.Lock() pi.boundUsersMutex.Lock()
pi.boundUsers[username] = UserFlags{ pi.boundUsers[username] = UserFlags{
UserInfo: userInfo.Payload.User, UserInfo: userInfo.Payload.User,
CanSearch: userInfo.Payload.User.Attributes.(map[string]bool)["goauthentik.io/ldap/can-search"], CanSearch: pi.SearchAccessCheck(userInfo.Payload.User),
} }
pi.boundUsersMutex.Unlock() pi.boundUsersMutex.Unlock()
pi.delayDeleteUserInfo(username) pi.delayDeleteUserInfo(username)
return ldap.LDAPResultSuccess, nil return ldap.LDAPResultSuccess, nil
} }
// SearchAccessCheck Check if the current user is allowed to search
func (pi *ProviderInstance) SearchAccessCheck(user *models.User) bool {
for _, group := range user.Groups {
for _, allowedGroup := range pi.searchAllowedGroups {
if &group.Pk == allowedGroup {
pi.log.WithField("group", group.Name).Info("Allowed access to search")
return true
}
}
}
return false
}
func (pi *ProviderInstance) delayDeleteUserInfo(dn string) { func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
ticker := time.NewTicker(30 * time.Second) ticker := time.NewTicker(30 * time.Second)
quit := make(chan struct{}) quit := make(chan struct{})

View file

@ -3,6 +3,7 @@ package ldap
import ( import (
"sync" "sync"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak" "goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/models" "goauthentik.io/outpost/pkg/models"
@ -19,13 +20,14 @@ type ProviderInstance struct {
UserDN string UserDN string
GroupDN string GroupDN string
appSlug string appSlug string
flowSlug string flowSlug string
s *LDAPServer s *LDAPServer
log *log.Entry log *log.Entry
boundUsersMutex sync.RWMutex searchAllowedGroups []*strfmt.UUID
boundUsers map[string]UserFlags boundUsersMutex sync.RWMutex
boundUsers map[string]UserFlags
} }
type UserFlags struct { type UserFlags struct {