LDAP Provider: TLS support (#1137)
This commit is contained in:
parent
cd0a6f2d7c
commit
7dfc621ae4
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 a new issue