LDAP Provider: TLS support (#1137)
This commit is contained in:
		| @ -51,6 +51,7 @@ COPY --from=website-builder /static/build_docs/ /work/website/build_docs/ | ||||
|  | ||||
| COPY ./cmd /work/cmd | ||||
| COPY ./web/static.go /work/web/static.go | ||||
| COPY ./website/static.go /work/website/static.go | ||||
| COPY ./internal /work/internal | ||||
| COPY ./go.mod /work/go.mod | ||||
| COPY ./go.sum /work/go.sum | ||||
|  | ||||
| @ -1,24 +1,60 @@ | ||||
| """Groups API Viewset""" | ||||
| from django.db.models.query import QuerySet | ||||
| from rest_framework.fields import JSONField | ||||
| from rest_framework.serializers import ModelSerializer | ||||
| from rest_framework.fields import BooleanField, CharField, JSONField | ||||
| from rest_framework.serializers import ListSerializer, ModelSerializer | ||||
| from rest_framework.viewsets import ModelViewSet | ||||
| from rest_framework_guardian.filters import ObjectPermissionsFilter | ||||
|  | ||||
| from authentik.core.api.used_by import UsedByMixin | ||||
| from authentik.core.api.utils import is_dict | ||||
| from authentik.core.models import Group | ||||
| from authentik.core.models import Group, User | ||||
|  | ||||
|  | ||||
| class GroupMemberSerializer(ModelSerializer): | ||||
|     """Stripped down user serializer to show relevant users for groups""" | ||||
|  | ||||
|     is_superuser = BooleanField(read_only=True) | ||||
|     avatar = CharField(read_only=True) | ||||
|     attributes = JSONField(validators=[is_dict], required=False) | ||||
|     uid = CharField(read_only=True) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = User | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "username", | ||||
|             "name", | ||||
|             "is_active", | ||||
|             "last_login", | ||||
|             "is_superuser", | ||||
|             "email", | ||||
|             "avatar", | ||||
|             "attributes", | ||||
|             "uid", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class GroupSerializer(ModelSerializer): | ||||
|     """Group Serializer""" | ||||
|  | ||||
|     attributes = JSONField(validators=[is_dict], required=False) | ||||
|     users_obj = ListSerializer( | ||||
|         child=GroupMemberSerializer(), read_only=True, source="users", required=False | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|  | ||||
|         model = Group | ||||
|         fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"] | ||||
|         fields = [ | ||||
|             "pk", | ||||
|             "name", | ||||
|             "is_superuser", | ||||
|             "parent", | ||||
|             "users", | ||||
|             "attributes", | ||||
|             "users_obj", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| class GroupViewSet(UsedByMixin, ModelViewSet): | ||||
|  | ||||
| @ -17,6 +17,8 @@ class LDAPProviderSerializer(ProviderSerializer): | ||||
|         fields = ProviderSerializer.Meta.fields + [ | ||||
|             "base_dn", | ||||
|             "search_group", | ||||
|             "certificate", | ||||
|             "tls_server_name", | ||||
|         ] | ||||
|  | ||||
|  | ||||
| @ -44,6 +46,8 @@ class LDAPOutpostConfigSerializer(ModelSerializer): | ||||
|             "bind_flow_slug", | ||||
|             "application_slug", | ||||
|             "search_group", | ||||
|             "certificate", | ||||
|             "tls_server_name", | ||||
|         ] | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -11,4 +11,5 @@ class LDAPDockerController(DockerController): | ||||
|         super().__init__(outpost, connection) | ||||
|         self.deployment_ports = [ | ||||
|             DeploymentPort(389, "ldap", "tcp", 3389), | ||||
|             DeploymentPort(636, "ldaps", "tcp", 6636), | ||||
|         ] | ||||
|  | ||||
| @ -11,4 +11,5 @@ class LDAPKubernetesController(KubernetesController): | ||||
|         super().__init__(outpost, connection) | ||||
|         self.deployment_ports = [ | ||||
|             DeploymentPort(389, "ldap", "tcp", 3389), | ||||
|             DeploymentPort(636, "ldaps", "tcp", 6636), | ||||
|         ] | ||||
|  | ||||
| @ -0,0 +1,30 @@ | ||||
| # Generated by Django 3.2.5 on 2021-07-13 11:38 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_crypto", "0002_create_self_signed_kp"), | ||||
|         ("authentik_providers_ldap", "0002_ldapprovider_search_group"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="ldapprovider", | ||||
|             name="certificate", | ||||
|             field=models.ForeignKey( | ||||
|                 blank=True, | ||||
|                 null=True, | ||||
|                 on_delete=django.db.models.deletion.SET_NULL, | ||||
|                 to="authentik_crypto.certificatekeypair", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name="ldapprovider", | ||||
|             name="tls_server_name", | ||||
|             field=models.TextField(blank=True, default=""), | ||||
|         ), | ||||
|     ] | ||||
| @ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.core.models import Group, Provider | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.outposts.models import OutpostModel | ||||
|  | ||||
|  | ||||
| @ -28,6 +29,17 @@ class LDAPProvider(OutpostModel, Provider): | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|     tls_server_name = models.TextField( | ||||
|         default="", | ||||
|         blank=True, | ||||
|     ) | ||||
|     certificate = models.ForeignKey( | ||||
|         CertificateKeyPair, | ||||
|         on_delete=models.SET_NULL, | ||||
|         null=True, | ||||
|         blank=True, | ||||
|     ) | ||||
|  | ||||
|     @property | ||||
|     def launch_url(self) -> Optional[str]: | ||||
|         """LDAP never has a launch URL""" | ||||
|  | ||||
| @ -37,7 +37,7 @@ func GenerateSelfSignedCert() (tls.Certificate, error) { | ||||
| 		SerialNumber: serialNumber, | ||||
| 		Subject: pkix.Name{ | ||||
| 			Organization: []string{"authentik"}, | ||||
| 			CommonName:   "authentik Proxy default certificate", | ||||
| 			CommonName:   "authentik Outpost default certificate", | ||||
| 		}, | ||||
| 		NotBefore: notBefore, | ||||
| 		NotAfter:  notAfter, | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| package ak | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| @ -9,6 +11,7 @@ import ( | ||||
| 	"github.com/getsentry/sentry-go" | ||||
| 	httptransport "github.com/go-openapi/runtime/client" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/outpost/api" | ||||
| 	"goauthentik.io/outpost/pkg" | ||||
| ) | ||||
|  | ||||
| @ -66,3 +69,21 @@ func GetTLSTransport() http.RoundTripper { | ||||
| 	} | ||||
| 	return tlsTransport | ||||
| } | ||||
|  | ||||
| // ParseCertificate Load certificate from Keyepair UUID and parse it into a go Certificate | ||||
| func ParseCertificate(kpUuid string, cryptoApi *api.CryptoApiService) (*tls.Certificate, error) { | ||||
| 	cert, _, err := cryptoApi.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), kpUuid).Execute() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	key, _, err := cryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), kpUuid).Execute() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &x509cert, nil | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| @ -39,7 +43,7 @@ type LDAPServer struct { | ||||
| 	s           *ldap.Server | ||||
| 	log         *log.Entry | ||||
| 	ac          *ak.APIController | ||||
|  | ||||
| 	defaultCert *tls.Certificate | ||||
| 	providers   []*ProviderInstance | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| package proxy | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/tls" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| @ -16,6 +15,7 @@ import ( | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"goauthentik.io/outpost/api" | ||||
| 	"goauthentik.io/outpost/pkg/ak" | ||||
| ) | ||||
|  | ||||
| type providerBundle struct { | ||||
| @ -90,23 +90,12 @@ func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options. | ||||
|  | ||||
| 	if provider.Certificate.Get() != nil { | ||||
| 		pb.log.WithField("provider", provider.Name).Debug("Enabling TLS") | ||||
| 		cert, _, err := pb.s.ak.Client.CryptoApi.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), *provider.Certificate.Get()).Execute() | ||||
| 		cert, err := ak.ParseCertificate(*provider.Certificate.Get(), pb.s.ak.Client.CryptoApi) | ||||
| 		if err != nil { | ||||
| 			pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate") | ||||
| 			return providerOpts | ||||
| 		} | ||||
| 		key, _, err := pb.s.ak.Client.CryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), *provider.Certificate.Get()).Execute() | ||||
| 		if err != nil { | ||||
| 			pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch private key") | ||||
| 			return providerOpts | ||||
| 		} | ||||
|  | ||||
| 		x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data)) | ||||
| 		if err != nil { | ||||
| 			pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to parse certificate") | ||||
| 			return providerOpts | ||||
| 		} | ||||
| 		pb.cert = &x509cert | ||||
| 		pb.cert = cert | ||||
| 		pb.log.WithField("provider", provider.Name).Debug("Loaded certificates") | ||||
| 	} | ||||
| 	return providerOpts | ||||
|  | ||||
							
								
								
									
										113
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								schema.yml
									
									
									
									
									
								
							| @ -19927,11 +19927,100 @@ components: | ||||
|         attributes: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|         users_obj: | ||||
|           type: array | ||||
|           items: | ||||
|             $ref: '#/components/schemas/GroupMember' | ||||
|           readOnly: true | ||||
|       required: | ||||
|       - name | ||||
|       - parent | ||||
|       - pk | ||||
|       - users | ||||
|       - users_obj | ||||
|     GroupMember: | ||||
|       type: object | ||||
|       description: Stripped down user serializer to show relevant users for groups | ||||
|       properties: | ||||
|         pk: | ||||
|           type: integer | ||||
|           readOnly: true | ||||
|           title: ID | ||||
|         username: | ||||
|           type: string | ||||
|           description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ | ||||
|             only. | ||||
|           pattern: ^[\w.@+-]+$ | ||||
|           maxLength: 150 | ||||
|         name: | ||||
|           type: string | ||||
|           description: User's display name. | ||||
|         is_active: | ||||
|           type: boolean | ||||
|           title: Active | ||||
|           description: Designates whether this user should be treated as active. Unselect | ||||
|             this instead of deleting accounts. | ||||
|         last_login: | ||||
|           type: string | ||||
|           format: date-time | ||||
|           nullable: true | ||||
|         is_superuser: | ||||
|           type: boolean | ||||
|           readOnly: true | ||||
|         email: | ||||
|           type: string | ||||
|           format: email | ||||
|           title: Email address | ||||
|           maxLength: 254 | ||||
|         avatar: | ||||
|           type: string | ||||
|           readOnly: true | ||||
|         attributes: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|         uid: | ||||
|           type: string | ||||
|           readOnly: true | ||||
|       required: | ||||
|       - avatar | ||||
|       - is_superuser | ||||
|       - name | ||||
|       - pk | ||||
|       - uid | ||||
|       - username | ||||
|     GroupMemberRequest: | ||||
|       type: object | ||||
|       description: Stripped down user serializer to show relevant users for groups | ||||
|       properties: | ||||
|         username: | ||||
|           type: string | ||||
|           description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ | ||||
|             only. | ||||
|           pattern: ^[\w.@+-]+$ | ||||
|           maxLength: 150 | ||||
|         name: | ||||
|           type: string | ||||
|           description: User's display name. | ||||
|         is_active: | ||||
|           type: boolean | ||||
|           title: Active | ||||
|           description: Designates whether this user should be treated as active. Unselect | ||||
|             this instead of deleting accounts. | ||||
|         last_login: | ||||
|           type: string | ||||
|           format: date-time | ||||
|           nullable: true | ||||
|         email: | ||||
|           type: string | ||||
|           format: email | ||||
|           title: Email address | ||||
|           maxLength: 254 | ||||
|         attributes: | ||||
|           type: object | ||||
|           additionalProperties: {} | ||||
|       required: | ||||
|       - name | ||||
|       - username | ||||
|     GroupRequest: | ||||
|       type: object | ||||
|       description: Group Serializer | ||||
| @ -20402,6 +20491,12 @@ components: | ||||
|           nullable: true | ||||
|           description: Users in this group can do search queries. If not set, every | ||||
|             user can execute search queries. | ||||
|         certificate: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           nullable: true | ||||
|         tls_server_name: | ||||
|           type: string | ||||
|       required: | ||||
|       - application_slug | ||||
|       - bind_flow_slug | ||||
| @ -20514,6 +20609,12 @@ components: | ||||
|           nullable: true | ||||
|           description: Users in this group can do search queries. If not set, every | ||||
|             user can execute search queries. | ||||
|         certificate: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           nullable: true | ||||
|         tls_server_name: | ||||
|           type: string | ||||
|       required: | ||||
|       - assigned_application_name | ||||
|       - assigned_application_slug | ||||
| @ -20547,6 +20648,12 @@ components: | ||||
|           nullable: true | ||||
|           description: Users in this group can do search queries. If not set, every | ||||
|             user can execute search queries. | ||||
|         certificate: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           nullable: true | ||||
|         tls_server_name: | ||||
|           type: string | ||||
|       required: | ||||
|       - authorization_flow | ||||
|       - name | ||||
| @ -24883,6 +24990,12 @@ components: | ||||
|           nullable: true | ||||
|           description: Users in this group can do search queries. If not set, every | ||||
|             user can execute search queries. | ||||
|         certificate: | ||||
|           type: string | ||||
|           format: uuid | ||||
|           nullable: true | ||||
|         tls_server_name: | ||||
|           type: string | ||||
|     PatchedLDAPSourceRequest: | ||||
|       type: object | ||||
|       description: LDAP Source Serializer | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { FlowsApi, ProvidersApi, LDAPProvider, CoreApi, FlowsInstancesListDesignationEnum } from "authentik-api"; | ||||
| import { FlowsApi, ProvidersApi, LDAPProvider, CoreApi, FlowsInstancesListDesignationEnum, CryptoApi } from "authentik-api"; | ||||
| import { t } from "@lingui/macro"; | ||||
| import { customElement } from "lit-element"; | ||||
| import { html, TemplateResult } from "lit-html"; | ||||
| @ -90,6 +90,27 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> { | ||||
|                         <input type="text" value="${first(this.instance?.baseDn, "DC=ldap,DC=goauthentik,DC=io")}" class="pf-c-form-control" required> | ||||
|                         <p class="pf-c-form__helper-text">${t`LDAP DN under which bind requests and search requests can be made.`}</p> | ||||
|                     </ak-form-element-horizontal> | ||||
|                     <ak-form-element-horizontal | ||||
|                         label=${t`TLS Server name`} | ||||
|                         name="baseDn"> | ||||
|                         <input type="text" value="${first(this.instance?.tlsServerName, "")}" class="pf-c-form-control"> | ||||
|                         <p class="pf-c-form__helper-text">${t`Server name for which this provider's certificate is valid for.`}</p> | ||||
|                     </ak-form-element-horizontal> | ||||
|                     <ak-form-element-horizontal | ||||
|                         label=${t`Certificate`} | ||||
|                         name="certificate"> | ||||
|                         <select class="pf-c-form-control"> | ||||
|                             <option value="" ?selected=${this.instance?.certificate === undefined}>---------</option> | ||||
|                             ${until(new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsList({ | ||||
|                                 ordering: "pk", | ||||
|                                 hasKey: true, | ||||
|                             }).then(keys => { | ||||
|                                 return keys.results.map(key => { | ||||
|                                     return html`<option value=${ifDefined(key.pk)} ?selected=${this.instance?.certificate === key.pk}>${key.name}</option>`; | ||||
|                                 }); | ||||
|                             }), html`<option>${t`Loading...`}</option>`)} | ||||
|                         </select> | ||||
|                     </ak-form-element-horizontal> | ||||
|                 </div> | ||||
|             </ak-form-group> | ||||
|         </form>`; | ||||
|  | ||||
| @ -22,7 +22,7 @@ You can bind using the DN `cn=<username>,ou=users,<base DN>`, or using the follo | ||||
| ldapsearch \ | ||||
|   -x \ # Only simple binds are currently supported | ||||
|   -h *ip* \ | ||||
|   -p 3389 \ | ||||
|   -p 389 \ | ||||
|   -D 'cn=*user*,ou=users,DC=ldap,DC=goauthentik,DC=io' \ # Bind user and password | ||||
|   -w '*password*' \ | ||||
|   -b 'ou=users,DC=ldap,DC=goauthentik,DC=io' \ # The search base | ||||
| @ -48,8 +48,15 @@ The following fields are current set for groups: | ||||
|  | ||||
| - `cn`: The group's name | ||||
| - `uid`: Unique group identifier | ||||
| - `member`: A list of all DNs of the group's members | ||||
| - `objectClass`: A list of these strings: | ||||
|   - "group" | ||||
|   - "goauthentik.io/ldap/group" | ||||
|  | ||||
| **Additionally**, for both users and groups, any attributes you set are also present as LDAP Attributes. | ||||
|  | ||||
| ## SSL | ||||
|  | ||||
| You can also configure SSL for your LDAP Providers by selecting a certificate and a server name in the provider settings. | ||||
|  | ||||
| This enables you to bind on port 636 using LDAPS, StartTLS is not supported. | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens L
					Jens L