LDAP Provider: TLS support (#1137)
This commit is contained in:
@ -2,6 +2,7 @@ package ldap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/outpost/pkg/ak"
|
||||
)
|
||||
|
||||
func (ls *LDAPServer) Refresh() error {
|
||||
@ -24,6 +26,7 @@ func (ls *LDAPServer) Refresh() error {
|
||||
for idx, provider := range outposts.Results {
|
||||
userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", *provider.BaseDn))
|
||||
groupDN := strings.ToLower(fmt.Sprintf("ou=groups,%s", *provider.BaseDn))
|
||||
logger := log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name)
|
||||
providers[idx] = &ProviderInstance{
|
||||
BaseDN: *provider.BaseDn,
|
||||
GroupDN: groupDN,
|
||||
@ -34,7 +37,18 @@ func (ls *LDAPServer) Refresh() error {
|
||||
boundUsersMutex: sync.RWMutex{},
|
||||
boundUsers: make(map[string]UserFlags),
|
||||
s: ls,
|
||||
log: log.WithField("logger", "authentik.outpost.ldap").WithField("provider", provider.Name),
|
||||
log: logger,
|
||||
tlsServerName: provider.TlsServerName,
|
||||
}
|
||||
if provider.Certificate.Get() != nil {
|
||||
logger.WithField("provider", provider.Name).Debug("Enabling TLS")
|
||||
cert, err := ak.ParseCertificate(*provider.Certificate.Get(), ls.ac.Client.CryptoApi)
|
||||
if err != nil {
|
||||
logger.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
|
||||
} else {
|
||||
providers[idx].cert = cert
|
||||
logger.WithField("provider", provider.Name).Debug("Loaded certificates")
|
||||
}
|
||||
}
|
||||
}
|
||||
ls.providers = providers
|
||||
@ -58,9 +72,30 @@ func (ls *LDAPServer) StartLDAPServer() error {
|
||||
return ls.s.ListenAndServe(listen)
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) StartLDAPTLSServer() error {
|
||||
listen := "0.0.0.0:6636"
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
MaxVersion: tls.VersionTLS12,
|
||||
GetCertificate: ls.getCertificates,
|
||||
}
|
||||
|
||||
ln, err := tls.Listen("tcp", listen, tlsConfig)
|
||||
if err != nil {
|
||||
ls.log.Fatalf("FATAL: listen (%s) failed - %s", listen, err)
|
||||
}
|
||||
ls.log.WithField("listen", listen).Info("Starting ldap tls server")
|
||||
err = ls.s.Serve(ln)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ls.log.Printf("closing %s", ln.Addr())
|
||||
return ls.s.ListenAndServe(listen)
|
||||
}
|
||||
|
||||
func (ls *LDAPServer) Start() error {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := ls.StartHTTPServer()
|
||||
@ -75,6 +110,13 @@ func (ls *LDAPServer) Start() error {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := ls.StartLDAPTLSServer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
23
outpost/pkg/ldap/api_tls.go
Normal file
23
outpost/pkg/ldap/api_tls.go
Normal file
@ -0,0 +1,23 @@
|
||||
package ldap
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func (ls *LDAPServer) getCertificates(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if len(ls.providers) == 1 {
|
||||
if ls.providers[0].cert != nil {
|
||||
ls.log.WithField("server-name", info.ServerName).Debug("We only have a single provider, using their cert")
|
||||
return ls.providers[0].cert, nil
|
||||
}
|
||||
}
|
||||
for _, provider := range ls.providers {
|
||||
if provider.tlsServerName == &info.ServerName {
|
||||
if provider.cert == nil {
|
||||
ls.log.WithField("server-name", info.ServerName).Debug("Handler does not have a certificate")
|
||||
return ls.defaultCert, nil
|
||||
}
|
||||
return provider.cert, nil
|
||||
}
|
||||
}
|
||||
ls.log.WithField("server-name", info.ServerName).Debug("Fallback to default cert")
|
||||
return ls.defaultCert, nil
|
||||
}
|
||||
@ -109,7 +109,7 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
|
||||
|
||||
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...)
|
||||
|
||||
dn := fmt.Sprintf("cn=%s,%s", u.Username, pi.UserDN)
|
||||
dn := pi.GetUserDN(u.Username)
|
||||
|
||||
return &ldap.Entry{DN: dn, Attributes: attrs}
|
||||
}
|
||||
@ -129,6 +129,9 @@ func (pi *ProviderInstance) GroupEntry(g api.Group) *ldap.Entry {
|
||||
Values: []string{GroupObjectClass, "goauthentik.io/ldap/group"},
|
||||
},
|
||||
}
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "member", Values: pi.UsersForGroup(g)})
|
||||
attrs = append(attrs, &ldap.EntryAttribute{Name: "goauthentik.io/ldap/superuser", Values: []string{BoolToString(*g.IsSuperuser)}})
|
||||
|
||||
attrs = append(attrs, AKAttrsToLDAP(g.Attributes)...)
|
||||
|
||||
dn := pi.GetGroupDN(g)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"sync"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
@ -25,6 +26,9 @@ type ProviderInstance struct {
|
||||
s *LDAPServer
|
||||
log *log.Entry
|
||||
|
||||
tlsServerName *string
|
||||
cert *tls.Certificate
|
||||
|
||||
searchAllowedGroups []*strfmt.UUID
|
||||
boundUsersMutex sync.RWMutex
|
||||
boundUsers map[string]UserFlags
|
||||
@ -36,11 +40,11 @@ type UserFlags struct {
|
||||
}
|
||||
|
||||
type LDAPServer struct {
|
||||
s *ldap.Server
|
||||
log *log.Entry
|
||||
ac *ak.APIController
|
||||
|
||||
providers []*ProviderInstance
|
||||
s *ldap.Server
|
||||
log *log.Entry
|
||||
ac *ak.APIController
|
||||
defaultCert *tls.Certificate
|
||||
providers []*ProviderInstance
|
||||
}
|
||||
|
||||
func NewServer(ac *ak.APIController) *LDAPServer {
|
||||
@ -52,6 +56,11 @@ func NewServer(ac *ak.APIController) *LDAPServer {
|
||||
ac: ac,
|
||||
providers: []*ProviderInstance{},
|
||||
}
|
||||
defaultCert, err := ak.GenerateSelfSignedCert()
|
||||
if err != nil {
|
||||
log.Warning(err)
|
||||
}
|
||||
ls.defaultCert = &defaultCert
|
||||
s.BindFunc("", ls)
|
||||
s.SearchFunc("", ls)
|
||||
return ls
|
||||
|
||||
@ -2,8 +2,10 @@ package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/nmcclain/ldap"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/outpost/api"
|
||||
)
|
||||
|
||||
@ -14,6 +16,24 @@ func BoolToString(in bool) string {
|
||||
return "false"
|
||||
}
|
||||
|
||||
func ldapResolveTypeSingle(in interface{}) *string {
|
||||
switch t := in.(type) {
|
||||
case string:
|
||||
return &t
|
||||
case *string:
|
||||
return t
|
||||
case bool:
|
||||
s := BoolToString(t)
|
||||
return &s
|
||||
case *bool:
|
||||
s := BoolToString(*t)
|
||||
return &s
|
||||
default:
|
||||
log.WithField("type", reflect.TypeOf(in).String()).Warning("Type can't be mapped to LDAP yet")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
|
||||
attrList := []*ldap.EntryAttribute{}
|
||||
a := attrs.(*map[string]interface{})
|
||||
@ -22,10 +42,19 @@ func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
|
||||
switch t := attrValue.(type) {
|
||||
case []string:
|
||||
entry.Values = t
|
||||
case string:
|
||||
entry.Values = []string{t}
|
||||
case bool:
|
||||
entry.Values = []string{BoolToString(t)}
|
||||
case *[]string:
|
||||
entry.Values = *t
|
||||
case []interface{}:
|
||||
entry.Values = make([]string, len(t))
|
||||
for idx, v := range t {
|
||||
v := ldapResolveTypeSingle(v)
|
||||
entry.Values[idx] = *v
|
||||
}
|
||||
default:
|
||||
v := ldapResolveTypeSingle(t)
|
||||
if v != nil {
|
||||
entry.Values = []string{*v}
|
||||
}
|
||||
}
|
||||
attrList = append(attrList, entry)
|
||||
}
|
||||
@ -40,6 +69,18 @@ func (pi *ProviderInstance) GroupsForUser(user api.User) []string {
|
||||
return groups
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) UsersForGroup(group api.Group) []string {
|
||||
users := make([]string, len(group.UsersObj))
|
||||
for i, user := range group.UsersObj {
|
||||
users[i] = pi.GetUserDN(user.Username)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) GetUserDN(user string) string {
|
||||
return fmt.Sprintf("cn=%s,%s", user, pi.UserDN)
|
||||
}
|
||||
|
||||
func (pi *ProviderInstance) GetGroupDN(group api.Group) string {
|
||||
return fmt.Sprintf("cn=%s,%s", group.Name, pi.GroupDN)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user