LDAP Provider: TLS support (#1137)

This commit is contained in:
Jens L 2021-07-13 18:24:18 +02:00 committed by GitHub
parent cd0a6f2d7c
commit 7dfc621ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 387 additions and 33 deletions

View File

@ -51,6 +51,7 @@ COPY --from=website-builder /static/build_docs/ /work/website/build_docs/
COPY ./cmd /work/cmd COPY ./cmd /work/cmd
COPY ./web/static.go /work/web/static.go COPY ./web/static.go /work/web/static.go
COPY ./website/static.go /work/website/static.go
COPY ./internal /work/internal COPY ./internal /work/internal
COPY ./go.mod /work/go.mod COPY ./go.mod /work/go.mod
COPY ./go.sum /work/go.sum COPY ./go.sum /work/go.sum

View File

@ -1,24 +1,60 @@
"""Groups API Viewset""" """Groups API Viewset"""
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from rest_framework.fields import JSONField from rest_framework.fields import BooleanField, CharField, JSONField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter from rest_framework_guardian.filters import ObjectPermissionsFilter
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import is_dict 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): class GroupSerializer(ModelSerializer):
"""Group Serializer""" """Group Serializer"""
attributes = JSONField(validators=[is_dict], required=False) attributes = JSONField(validators=[is_dict], required=False)
users_obj = ListSerializer(
child=GroupMemberSerializer(), read_only=True, source="users", required=False
)
class Meta: class Meta:
model = Group model = Group
fields = ["pk", "name", "is_superuser", "parent", "users", "attributes"] fields = [
"pk",
"name",
"is_superuser",
"parent",
"users",
"attributes",
"users_obj",
]
class GroupViewSet(UsedByMixin, ModelViewSet): class GroupViewSet(UsedByMixin, ModelViewSet):

View File

@ -17,6 +17,8 @@ class LDAPProviderSerializer(ProviderSerializer):
fields = ProviderSerializer.Meta.fields + [ fields = ProviderSerializer.Meta.fields + [
"base_dn", "base_dn",
"search_group", "search_group",
"certificate",
"tls_server_name",
] ]
@ -44,6 +46,8 @@ class LDAPOutpostConfigSerializer(ModelSerializer):
"bind_flow_slug", "bind_flow_slug",
"application_slug", "application_slug",
"search_group", "search_group",
"certificate",
"tls_server_name",
] ]

View File

@ -11,4 +11,5 @@ class LDAPDockerController(DockerController):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = [ self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389), DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
] ]

View File

@ -11,4 +11,5 @@ class LDAPKubernetesController(KubernetesController):
super().__init__(outpost, connection) super().__init__(outpost, connection)
self.deployment_ports = [ self.deployment_ports = [
DeploymentPort(389, "ldap", "tcp", 3389), DeploymentPort(389, "ldap", "tcp", 3389),
DeploymentPort(636, "ldaps", "tcp", 6636),
] ]

View File

@ -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=""),
),
]

View File

@ -6,6 +6,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer from rest_framework.serializers import Serializer
from authentik.core.models import Group, Provider from authentik.core.models import Group, Provider
from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.models import OutpostModel 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 @property
def launch_url(self) -> Optional[str]: def launch_url(self) -> Optional[str]:
"""LDAP never has a launch URL""" """LDAP never has a launch URL"""

View File

@ -37,7 +37,7 @@ func GenerateSelfSignedCert() (tls.Certificate, error) {
SerialNumber: serialNumber, SerialNumber: serialNumber,
Subject: pkix.Name{ Subject: pkix.Name{
Organization: []string{"authentik"}, Organization: []string{"authentik"},
CommonName: "authentik Proxy default certificate", CommonName: "authentik Outpost default certificate",
}, },
NotBefore: notBefore, NotBefore: notBefore,
NotAfter: notAfter, NotAfter: notAfter,

View File

@ -1,6 +1,8 @@
package ak package ak
import ( import (
"context"
"crypto/tls"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -9,6 +11,7 @@ import (
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
httptransport "github.com/go-openapi/runtime/client" httptransport "github.com/go-openapi/runtime/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg" "goauthentik.io/outpost/pkg"
) )
@ -66,3 +69,21 @@ func GetTLSTransport() http.RoundTripper {
} }
return tlsTransport 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
}

View File

@ -2,6 +2,7 @@ package ldap
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -10,6 +11,7 @@ import (
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/ak"
) )
func (ls *LDAPServer) Refresh() error { func (ls *LDAPServer) Refresh() error {
@ -24,6 +26,7 @@ func (ls *LDAPServer) Refresh() error {
for idx, provider := range outposts.Results { for idx, provider := range outposts.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", *provider.BaseDn)) userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", *provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=groups,%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{ providers[idx] = &ProviderInstance{
BaseDN: *provider.BaseDn, BaseDN: *provider.BaseDn,
GroupDN: groupDN, GroupDN: groupDN,
@ -34,7 +37,18 @@ func (ls *LDAPServer) Refresh() error {
boundUsersMutex: sync.RWMutex{}, boundUsersMutex: sync.RWMutex{},
boundUsers: make(map[string]UserFlags), boundUsers: make(map[string]UserFlags),
s: ls, 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 ls.providers = providers
@ -58,9 +72,30 @@ func (ls *LDAPServer) StartLDAPServer() error {
return ls.s.ListenAndServe(listen) 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 { func (ls *LDAPServer) Start() error {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(2) wg.Add(3)
go func() { go func() {
defer wg.Done() defer wg.Done()
err := ls.StartHTTPServer() err := ls.StartHTTPServer()
@ -75,6 +110,13 @@ func (ls *LDAPServer) Start() error {
panic(err) panic(err)
} }
}() }()
go func() {
defer wg.Done()
err := ls.StartLDAPTLSServer()
if err != nil {
panic(err)
}
}()
wg.Wait() wg.Wait()
return nil return nil
} }

View 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
}

View File

@ -109,7 +109,7 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...) 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} 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"}, 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)...) attrs = append(attrs, AKAttrsToLDAP(g.Attributes)...)
dn := pi.GetGroupDN(g) dn := pi.GetGroupDN(g)

View File

@ -1,6 +1,7 @@
package ldap package ldap
import ( import (
"crypto/tls"
"sync" "sync"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
@ -25,6 +26,9 @@ type ProviderInstance struct {
s *LDAPServer s *LDAPServer
log *log.Entry log *log.Entry
tlsServerName *string
cert *tls.Certificate
searchAllowedGroups []*strfmt.UUID searchAllowedGroups []*strfmt.UUID
boundUsersMutex sync.RWMutex boundUsersMutex sync.RWMutex
boundUsers map[string]UserFlags boundUsers map[string]UserFlags
@ -39,7 +43,7 @@ type LDAPServer struct {
s *ldap.Server s *ldap.Server
log *log.Entry log *log.Entry
ac *ak.APIController ac *ak.APIController
defaultCert *tls.Certificate
providers []*ProviderInstance providers []*ProviderInstance
} }
@ -52,6 +56,11 @@ func NewServer(ac *ak.APIController) *LDAPServer {
ac: ac, ac: ac,
providers: []*ProviderInstance{}, providers: []*ProviderInstance{},
} }
defaultCert, err := ak.GenerateSelfSignedCert()
if err != nil {
log.Warning(err)
}
ls.defaultCert = &defaultCert
s.BindFunc("", ls) s.BindFunc("", ls)
s.SearchFunc("", ls) s.SearchFunc("", ls)
return ls return ls

View File

@ -2,8 +2,10 @@ package ldap
import ( import (
"fmt" "fmt"
"reflect"
"github.com/nmcclain/ldap" "github.com/nmcclain/ldap"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/api" "goauthentik.io/outpost/api"
) )
@ -14,6 +16,24 @@ func BoolToString(in bool) string {
return "false" 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 { func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
attrList := []*ldap.EntryAttribute{} attrList := []*ldap.EntryAttribute{}
a := attrs.(*map[string]interface{}) a := attrs.(*map[string]interface{})
@ -22,10 +42,19 @@ func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
switch t := attrValue.(type) { switch t := attrValue.(type) {
case []string: case []string:
entry.Values = t entry.Values = t
case string: case *[]string:
entry.Values = []string{t} entry.Values = *t
case bool: case []interface{}:
entry.Values = []string{BoolToString(t)} 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) attrList = append(attrList, entry)
} }
@ -40,6 +69,18 @@ func (pi *ProviderInstance) GroupsForUser(user api.User) []string {
return groups 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 { func (pi *ProviderInstance) GetGroupDN(group api.Group) string {
return fmt.Sprintf("cn=%s,%s", group.Name, pi.GroupDN) return fmt.Sprintf("cn=%s,%s", group.Name, pi.GroupDN)
} }

View File

@ -1,7 +1,6 @@
package proxy package proxy
import ( import (
"context"
"crypto/tls" "crypto/tls"
"net" "net"
"net/http" "net/http"
@ -16,6 +15,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/api" "goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg/ak"
) )
type providerBundle struct { type providerBundle struct {
@ -90,23 +90,12 @@ func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.
if provider.Certificate.Get() != nil { if provider.Certificate.Get() != nil {
pb.log.WithField("provider", provider.Name).Debug("Enabling TLS") 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 { if err != nil {
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate") pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
return providerOpts return providerOpts
} }
key, _, err := pb.s.ak.Client.CryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), *provider.Certificate.Get()).Execute() pb.cert = cert
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.log.WithField("provider", provider.Name).Debug("Loaded certificates") pb.log.WithField("provider", provider.Name).Debug("Loaded certificates")
} }
return providerOpts return providerOpts

View File

@ -19927,11 +19927,100 @@ components:
attributes: attributes:
type: object type: object
additionalProperties: {} additionalProperties: {}
users_obj:
type: array
items:
$ref: '#/components/schemas/GroupMember'
readOnly: true
required: required:
- name - name
- parent - parent
- pk - pk
- users - 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: GroupRequest:
type: object type: object
description: Group Serializer description: Group Serializer
@ -20402,6 +20491,12 @@ components:
nullable: true nullable: true
description: Users in this group can do search queries. If not set, every description: Users in this group can do search queries. If not set, every
user can execute search queries. user can execute search queries.
certificate:
type: string
format: uuid
nullable: true
tls_server_name:
type: string
required: required:
- application_slug - application_slug
- bind_flow_slug - bind_flow_slug
@ -20514,6 +20609,12 @@ components:
nullable: true nullable: true
description: Users in this group can do search queries. If not set, every description: Users in this group can do search queries. If not set, every
user can execute search queries. user can execute search queries.
certificate:
type: string
format: uuid
nullable: true
tls_server_name:
type: string
required: required:
- assigned_application_name - assigned_application_name
- assigned_application_slug - assigned_application_slug
@ -20547,6 +20648,12 @@ components:
nullable: true nullable: true
description: Users in this group can do search queries. If not set, every description: Users in this group can do search queries. If not set, every
user can execute search queries. user can execute search queries.
certificate:
type: string
format: uuid
nullable: true
tls_server_name:
type: string
required: required:
- authorization_flow - authorization_flow
- name - name
@ -24883,6 +24990,12 @@ components:
nullable: true nullable: true
description: Users in this group can do search queries. If not set, every description: Users in this group can do search queries. If not set, every
user can execute search queries. user can execute search queries.
certificate:
type: string
format: uuid
nullable: true
tls_server_name:
type: string
PatchedLDAPSourceRequest: PatchedLDAPSourceRequest:
type: object type: object
description: LDAP Source Serializer description: LDAP Source Serializer

View File

@ -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 { t } from "@lingui/macro";
import { customElement } from "lit-element"; import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html"; 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> <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> <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>
<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> </div>
</ak-form-group> </ak-form-group>
</form>`; </form>`;

View File

@ -22,7 +22,7 @@ You can bind using the DN `cn=<username>,ou=users,<base DN>`, or using the follo
ldapsearch \ ldapsearch \
-x \ # Only simple binds are currently supported -x \ # Only simple binds are currently supported
-h *ip* \ -h *ip* \
-p 3389 \ -p 389 \
-D 'cn=*user*,ou=users,DC=ldap,DC=goauthentik,DC=io' \ # Bind user and password -D 'cn=*user*,ou=users,DC=ldap,DC=goauthentik,DC=io' \ # Bind user and password
-w '*password*' \ -w '*password*' \
-b 'ou=users,DC=ldap,DC=goauthentik,DC=io' \ # The search base -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 - `cn`: The group's name
- `uid`: Unique group identifier - `uid`: Unique group identifier
- `member`: A list of all DNs of the group's members
- `objectClass`: A list of these strings: - `objectClass`: A list of these strings:
- "group" - "group"
- "goauthentik.io/ldap/group" - "goauthentik.io/ldap/group"
**Additionally**, for both users and groups, any attributes you set are also present as LDAP Attributes. **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.