outposts: initial ldap outpost implementation

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer
2021-04-20 00:30:27 +02:00
parent 15d5b91642
commit 4f5e1fb86b
11 changed files with 281 additions and 1 deletions

View File

@ -31,7 +31,7 @@ func doGlobalSetup(config map[string]interface{}) {
default:
log.SetLevel(log.DebugLevel)
}
log.WithField("version", pkg.VERSION).Info("Starting authentik proxy")
log.WithField("version", pkg.VERSION).Info("Starting authentik outpost")
var dsn string
if config[ConfigErrorReportingEnabled].(bool) {

20
outpost/pkg/ldap/api.go Normal file
View File

@ -0,0 +1,20 @@
package ldap
import (
log "github.com/sirupsen/logrus"
)
func (ls *LDAPServer) Refresh() error {
return nil
}
func (ls *LDAPServer) Start() error {
listen := "0.0.0.0:3389"
log.Debugf("Listening on %s", listen)
err := ls.s.ListenAndServe(listen)
if err != nil {
ls.log.Errorf("LDAP Server Failed: %s", err.Error())
return err
}
return nil
}

12
outpost/pkg/ldap/bind.go Normal file
View File

@ -0,0 +1,12 @@
package ldap
import (
"net"
"github.com/nmcclain/ldap"
)
func (ls *LDAPServer) Bind(bindDN string, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
ls.log.WithField("dn", bindDN).WithField("pw", bindSimplePw).Debug("bind")
return ldap.LDAPResultSuccess, nil
}

42
outpost/pkg/ldap/ldap.go Normal file
View File

@ -0,0 +1,42 @@
package ldap
import (
"fmt"
"strings"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
"github.com/nmcclain/ldap"
)
const GroupObjectClass = "group"
const UserObjectClass = "user"
type LDAPServer struct {
BaseDN string
userDN string
groupDN string
s *ldap.Server
log *log.Entry
ac *ak.APIController
}
func NewServer(ac *ak.APIController) *LDAPServer {
s := ldap.NewServer()
s.EnforceLDAP = true
ls := &LDAPServer{
s: s,
log: log.WithField("logger", "ldap-server"),
ac: ac,
BaseDN: "DC=ldap,DC=goauthentik,DC=io",
}
ls.userDN = strings.ToLower(fmt.Sprintf("cn=users,%s", ls.BaseDN))
ls.groupDN = strings.ToLower(fmt.Sprintf("cn=groups,%s", ls.BaseDN))
s.BindFunc("", ls)
s.SearchFunc("", ls)
return ls
}

115
outpost/pkg/ldap/search.go Normal file
View File

@ -0,0 +1,115 @@
package ldap
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/nmcclain/ldap"
"goauthentik.io/outpost/pkg/client/core"
)
func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + ls.BaseDN)
entries := []*ldap.Entry{}
filterEntity, err := ldap.GetFilterObjectClass(searchReq.Filter)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", searchReq.Filter)
}
if len(bindDN) < 1 {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: Anonymous BindDN not allowed %s", bindDN)
}
if !strings.HasSuffix(bindDN, baseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("Search Error: BindDN %s not in our BaseDN %s", bindDN, ls.BaseDN)
}
switch filterEntity {
default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
case GroupObjectClass:
groups, err := ls.ac.Client.Core.CoreGroupsList(core.NewCoreGroupsListParams(), ls.ac.Auth)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
for _, g := range groups.Payload.Results {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{*g.Name},
},
{
Name: "uid",
Values: []string{strconv.Itoa(int(g.Pk))},
},
{
Name: "objectClass",
Values: []string{GroupObjectClass, "goauthentik.io/ldap/group"},
},
}
attrs = append(attrs, AKAttrsToLDAP(g.Attributes)...)
// attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s", g.Name)}})
// attrs = append(attrs, &ldap.EntryAttribute{Name: "gidNumber", Values: []string{fmt.Sprintf("%d", g.UnixID)}})
// attrs = append(attrs, &ldap.EntryAttribute{Name: "uniqueMember", Values: h.getGroupMembers(g.UnixID)})
// attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: h.getGroupMemberIDs(g.UnixID)})
dn := fmt.Sprintf("cn=%s,%s", *g.Name, ls.groupDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case UserObjectClass, "":
users, err := ls.ac.Client.Core.CoreUsersList(core.NewCoreUsersListParams(), ls.ac.Auth)
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
for _, u := range users.Payload.Results {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{*u.Username},
},
{
Name: "uid",
Values: []string{strconv.Itoa(int(u.Pk))},
},
{
Name: "name",
Values: []string{*u.Name},
},
{
Name: "displayName",
Values: []string{*u.Name},
},
{
Name: "mail",
Values: []string{u.Email.String()},
},
{
Name: "objectClass",
Values: []string{UserObjectClass, "organizationalPerson", "goauthentik.io/ldap/user"},
},
}
if u.IsActive {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
}
if *u.IsSuperuser {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}})
}
// attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: h.getGroupDNs(append(u.OtherGroups, u.PrimaryGroup))})
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...)
dn := fmt.Sprintf("cn=%s,%s", *u.Name, ls.userDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}
ls.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}

20
outpost/pkg/ldap/utils.go Normal file
View File

@ -0,0 +1,20 @@
package ldap
import (
"github.com/nmcclain/ldap"
)
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
attrList := []*ldap.EntryAttribute{}
for attrKey, attrValue := range attrs.(map[string]interface{}) {
entry := &ldap.EntryAttribute{Name: attrKey}
switch attrValue.(type) {
case []string:
entry.Values = attrValue.([]string)
case string:
entry.Values = []string{attrValue.(string)}
}
attrList = append(attrList, entry)
}
return attrList
}