From 029395d08bab1fa7b6fcf2cb6ad291e6b2b4e8ba Mon Sep 17 00:00:00 2001
From: ChandonPierre <80500072+ChandonPierre@users.noreply.github.com>
Date: Mon, 12 Jun 2023 09:41:44 -0400
Subject: [PATCH] sources/ldap: add support for cert based auth (#5850)
* ldap: support cert based auth
Signed-off-by: Jens Langhammer
* ldap: default sni switch to off
* ldap: `get_info=NONE` on insufficient access error
* fix: Make file locale script
* ldap: add google ldap attribute mappings
* ldap: move google secure ldap blueprint to examples
Revert "ldap: add google ldap attribute mappings"
This reverts commit 8a861bb92c1bd763b6e7ec0513f73b3039a1adb4.
* ldap: remove `validate` for client cert auth
not strictly necessary
* ldap: write temp cert files more securely
* ldap: use first array value for sni when provided csv input
* don't specify tempdir
we set $TMPDIR in the dockerfile
Signed-off-by: Jens Langhammer
* limit API to only allow certificate key pairs with private key
Signed-off-by: Jens Langhammer
* use maxsplit
Signed-off-by: Jens Langhammer
* update locale
Signed-off-by: Jens Langhammer
---------
Signed-off-by: Jens Langhammer
Signed-off-by: Jens L.
Co-authored-by: Jens Langhammer
---
Makefile | 2 +-
authentik/sources/ldap/api.py | 15 ++
...ent_certificate_ldapsource_sni_and_more.py | 45 ++++
authentik/sources/ldap/models.py | 37 ++-
.../example/sources-google-ldap-mappings.yaml | 222 ++++++++++++++++++
blueprints/schema.json | 9 +
locale/en/LC_MESSAGES/django.po | 91 +++----
schema.yml | 36 +++
web/src/admin/sources/ldap/LDAPSourceForm.ts | 59 +++++
web/xliff/de.xlf | 28 ++-
web/xliff/en.xlf | 29 ++-
web/xliff/es.xlf | 28 ++-
web/xliff/fr_FR.xlf | 28 ++-
web/xliff/pl.xlf | 28 ++-
web/xliff/pseudo-LOCALE.xlf | 29 ++-
web/xliff/tr.xlf | 28 ++-
web/xliff/zh-Hans.xlf | 31 ++-
web/xliff/zh-Hant.xlf | 28 ++-
web/xliff/zh_TW.xlf | 28 ++-
19 files changed, 680 insertions(+), 121 deletions(-)
create mode 100644 authentik/sources/ldap/migrations/0003_ldapsource_client_certificate_ldapsource_sni_and_more.py
create mode 100644 blueprints/example/sources-google-ldap-mappings.yaml
diff --git a/Makefile b/Makefile
index 1943e85b4..dcd8e73ca 100644
--- a/Makefile
+++ b/Makefile
@@ -151,7 +151,7 @@ web-check-compile:
cd web && npm run tsc
web-i18n-extract:
- cd web && npm run extract
+ cd web && npm run extract-locales
#########################
## Website
diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py
index 3f6e24837..0a8849345 100644
--- a/authentik/sources/ldap/api.py
+++ b/authentik/sources/ldap/api.py
@@ -8,6 +8,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field, inline_ser
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import DictField, ListField
+from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
@@ -16,6 +17,7 @@ from authentik.admin.api.tasks import TaskSerializer
from authentik.core.api.propertymappings import PropertyMappingSerializer
from authentik.core.api.sources import SourceSerializer
from authentik.core.api.used_by import UsedByMixin
+from authentik.crypto.models import CertificateKeyPair
from authentik.events.monitored_tasks import TaskInfo
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from authentik.sources.ldap.tasks import SYNC_CLASSES
@@ -24,6 +26,15 @@ from authentik.sources.ldap.tasks import SYNC_CLASSES
class LDAPSourceSerializer(SourceSerializer):
"""LDAP Source Serializer"""
+ client_certificate = PrimaryKeyRelatedField(
+ allow_null=True,
+ help_text="Client certificate to authenticate against the LDAP Server's Certificate.",
+ queryset=CertificateKeyPair.objects.exclude(
+ key_data__exact="",
+ ),
+ required=False,
+ )
+
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
"""Check that only a single source has password_sync on"""
sync_users_password = attrs.get("sync_users_password", True)
@@ -42,9 +53,11 @@ class LDAPSourceSerializer(SourceSerializer):
fields = SourceSerializer.Meta.fields + [
"server_uri",
"peer_certificate",
+ "client_certificate",
"bind_cn",
"bind_password",
"start_tls",
+ "sni",
"base_dn",
"additional_user_dn",
"additional_group_dn",
@@ -75,7 +88,9 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
"server_uri",
"bind_cn",
"peer_certificate",
+ "client_certificate",
"start_tls",
+ "sni",
"base_dn",
"additional_user_dn",
"additional_group_dn",
diff --git a/authentik/sources/ldap/migrations/0003_ldapsource_client_certificate_ldapsource_sni_and_more.py b/authentik/sources/ldap/migrations/0003_ldapsource_client_certificate_ldapsource_sni_and_more.py
new file mode 100644
index 000000000..7c67597bb
--- /dev/null
+++ b/authentik/sources/ldap/migrations/0003_ldapsource_client_certificate_ldapsource_sni_and_more.py
@@ -0,0 +1,45 @@
+# Generated by Django 4.1.7 on 2023-06-06 18:33
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("authentik_crypto", "0004_alter_certificatekeypair_name"),
+ ("authentik_sources_ldap", "0002_auto_20211203_0900"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="ldapsource",
+ name="client_certificate",
+ field=models.ForeignKey(
+ default=None,
+ help_text="Client certificate to authenticate against the LDAP Server's Certificate.",
+ null=True,
+ on_delete=django.db.models.deletion.SET_DEFAULT,
+ related_name="ldap_client_certificates",
+ to="authentik_crypto.certificatekeypair",
+ ),
+ ),
+ migrations.AddField(
+ model_name="ldapsource",
+ name="sni",
+ field=models.BooleanField(
+ default=False, verbose_name="Use Server URI for SNI verification"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="ldapsource",
+ name="peer_certificate",
+ field=models.ForeignKey(
+ default=None,
+ help_text="Optionally verify the LDAP Server's Certificate against the CA Chain in this keypair.",
+ null=True,
+ on_delete=django.db.models.deletion.SET_DEFAULT,
+ related_name="ldap_peer_certificates",
+ to="authentik_crypto.certificatekeypair",
+ ),
+ ),
+ ]
diff --git a/authentik/sources/ldap/models.py b/authentik/sources/ldap/models.py
index 2b4acbedf..4a6c2fd2e 100644
--- a/authentik/sources/ldap/models.py
+++ b/authentik/sources/ldap/models.py
@@ -1,11 +1,13 @@
"""authentik LDAP Models"""
+from os import chmod
from ssl import CERT_REQUIRED
+from tempfile import NamedTemporaryFile, mkdtemp
from typing import Optional
from django.db import models
from django.utils.translation import gettext_lazy as _
from ldap3 import ALL, NONE, RANDOM, Connection, Server, ServerPool, Tls
-from ldap3.core.exceptions import LDAPSchemaError
+from ldap3.core.exceptions import LDAPInsufficientAccessRightsResult, LDAPSchemaError
from rest_framework.serializers import Serializer
from authentik.core.models import Group, PropertyMapping, Source
@@ -39,14 +41,24 @@ class LDAPSource(Source):
on_delete=models.SET_DEFAULT,
default=None,
null=True,
+ related_name="ldap_peer_certificates",
help_text=_(
"Optionally verify the LDAP Server's Certificate against the CA Chain in this keypair."
),
)
+ client_certificate = models.ForeignKey(
+ CertificateKeyPair,
+ on_delete=models.SET_DEFAULT,
+ default=None,
+ null=True,
+ related_name="ldap_client_certificates",
+ help_text=_("Client certificate to authenticate against the LDAP Server's Certificate."),
+ )
bind_cn = models.TextField(verbose_name=_("Bind CN"), blank=True)
bind_password = models.TextField(blank=True)
start_tls = models.BooleanField(default=False, verbose_name=_("Enable Start TLS"))
+ sni = models.BooleanField(default=False, verbose_name=_("Use Server URI for SNI verification"))
base_dn = models.TextField(verbose_name=_("Base DN"))
additional_user_dn = models.TextField(
@@ -112,8 +124,22 @@ class LDAPSource(Source):
if self.peer_certificate:
tls_kwargs["ca_certs_data"] = self.peer_certificate.certificate_data
tls_kwargs["validate"] = CERT_REQUIRED
+ if self.client_certificate:
+ temp_dir = mkdtemp()
+ with NamedTemporaryFile(mode="w", delete=False, dir=temp_dir) as temp_cert:
+ temp_cert.write(self.client_certificate.certificate_data)
+ certificate_file = temp_cert.name
+ chmod(certificate_file, 0o600)
+ with NamedTemporaryFile(mode="w", delete=False, dir=temp_dir) as temp_key:
+ temp_key.write(self.client_certificate.key_data)
+ private_key_file = temp_key.name
+ chmod(private_key_file, 0o600)
+ tls_kwargs["local_private_key_file"] = private_key_file
+ tls_kwargs["local_certificate_file"] = certificate_file
if ciphers := CONFIG.y("ldap.tls.ciphers", None):
tls_kwargs["ciphers"] = ciphers.strip()
+ if self.sni:
+ tls_kwargs["sni"] = self.server_uri.split(",", maxsplit=1)[0].strip()
server_kwargs = {
"get_info": ALL,
"connect_timeout": LDAP_TIMEOUT,
@@ -133,8 +159,10 @@ class LDAPSource(Source):
"""Get a fully connected and bound LDAP Connection"""
server_kwargs = server_kwargs or {}
connection_kwargs = connection_kwargs or {}
- connection_kwargs.setdefault("user", self.bind_cn)
- connection_kwargs.setdefault("password", self.bind_password)
+ if self.bind_cn is not None:
+ connection_kwargs.setdefault("user", self.bind_cn)
+ if self.bind_password is not None:
+ connection_kwargs.setdefault("password", self.bind_password)
connection = Connection(
self.server(**server_kwargs),
raise_exceptions=True,
@@ -148,9 +176,10 @@ class LDAPSource(Source):
successful = connection.bind()
if successful:
return connection
- except LDAPSchemaError as exc:
+ except (LDAPSchemaError, LDAPInsufficientAccessRightsResult) as exc:
# Schema error, so try connecting without schema info
# See https://github.com/goauthentik/authentik/issues/4590
+ # See also https://github.com/goauthentik/authentik/issues/3399
if server_kwargs.get("get_info", ALL) == NONE:
raise exc
server_kwargs["get_info"] = NONE
diff --git a/blueprints/example/sources-google-ldap-mappings.yaml b/blueprints/example/sources-google-ldap-mappings.yaml
new file mode 100644
index 000000000..e070798dd
--- /dev/null
+++ b/blueprints/example/sources-google-ldap-mappings.yaml
@@ -0,0 +1,222 @@
+version: 1
+metadata:
+ labels:
+ blueprints.goauthentik.io/instantiate: "false"
+ name: Example - Google Secure LDAP mappings
+entries:
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-uid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: uid"
+ object_field: "username"
+ expression: |
+ return ldap.get('uid')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-googleuid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: googleUid"
+ object_field: "attributes.googleUid"
+ expression: |
+ return ldap.get('googleUid')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-posixuid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: posixUid"
+ object_field: "attributes.posixUid"
+ expression: |
+ return ldap.get('posixUid')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-cn
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: cn"
+ object_field: "name"
+ expression: |
+ return ldap.get('cn')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-sn
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: sn"
+ object_field: "attributes.sn"
+ expression: |
+ return list_flatten(ldap.get('sn'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-givenname
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: givenName"
+ object_field: "attributes.givenName"
+ expression: |
+ return list_flatten(ldap.get('givenName'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-displayname
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: displayName"
+ object_field: "attributes.displayName"
+ expression: |
+ return ldap.get('displayName')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-mail
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: mail"
+ object_field: "email"
+ expression: |
+ return ldap.get('mail')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-memberof
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: memberOf"
+ object_field: "attributes.memberOf"
+ expression: |
+ return ldap.get('memberOf')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-title
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: title"
+ object_field: "attributes.title"
+ expression: |
+ return ldap.get('title')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-employeenumber
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: employeeNumber"
+ object_field: "attributes.employeeNumber"
+ expression: |
+ return ldap.get('employeeNumber')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-employeetype
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: employeeType"
+ object_field: "attributes.employeeType"
+ expression: |
+ return ldap.get('employeeType')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-departmentnumber
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: departmentNumber"
+ object_field: "attributes.departmentNumber"
+ expression: |
+ return ldap.get('departmentNumber')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-physicaldeliveryofficename
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: physicalDeliveryOfficeName"
+ object_field: "attributes.physicalDeliveryOfficeName"
+ expression: |
+ return ldap.get('physicalDeliveryOfficeName')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-jpegphoto
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: jpegPhoto"
+ object_field: "attributes.jpegPhoto"
+ expression: |
+ return ldap.get('jpegPhoto')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-entryuuid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: entryUuid"
+ object_field: "attributes.entryUuid"
+ expression: |
+ return ldap.get('entryUuid')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-objectsid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: objectSid"
+ object_field: "attributes.objectSid"
+ expression: |
+ return ldap.get('objectSid')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-uidnumber
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: uidNumber"
+ object_field: "attributes.uidNumber"
+ expression: |
+ return ldap.get('uidNumber')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-gidnumber
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: gidNumber"
+ object_field: "attributes.gidNumber"
+ expression: |
+ return ldap.get('gidNumber')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-homedirectory
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: homeDirectory"
+ object_field: "attributes.homeDirectory"
+ expression: |
+ return ldap.get('homeDirectory')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-loginshell
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: loginShell"
+ object_field: "attributes.loginShell"
+ expression: |
+ return ldap.get('loginShell')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-gidnumber
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: gidNumber"
+ object_field: "attributes.gidNumber"
+ expression: |
+ return ldap.get('gidNumber')
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-sshpublickey
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: sshPublicKey"
+ object_field: "attributes.sshPublicKey"
+ expression: |
+ return list_flatten(ldap.get('sshPublicKey'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-description
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: description"
+ object_field: "attributes.description"
+ expression: |
+ return list_flatten(ldap.get('description'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-member
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: member"
+ object_field: "attributes.member"
+ expression: |
+ return list_flatten(ldap.get('member'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-memberuid
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: memberUid"
+ object_field: "attributes.memberUid"
+ expression: |
+ return list_flatten(ldap.get('memberUid'))
+ - identifiers:
+ managed: goauthentik.io/sources/ldap/google-googleadmincreated
+ model: authentik_sources_ldap.ldappropertymapping
+ attrs:
+ name: "Google Secure LDAP Mapping: googleAdminCreated"
+ object_field: "attributes.googleAdminCreated"
+ expression: |
+ return list_flatten(ldap.get('googleAdminCreated'))
diff --git a/blueprints/schema.json b/blueprints/schema.json
index 9422fbc79..edbc7f81b 100644
--- a/blueprints/schema.json
+++ b/blueprints/schema.json
@@ -4732,6 +4732,11 @@
"title": "Peer certificate",
"description": "Optionally verify the LDAP Server's Certificate against the CA Chain in this keypair."
},
+ "client_certificate": {
+ "type": "integer",
+ "title": "Client certificate",
+ "description": "Client certificate to authenticate against the LDAP Server's Certificate."
+ },
"bind_cn": {
"type": "string",
"title": "Bind CN"
@@ -4744,6 +4749,10 @@
"type": "boolean",
"title": "Enable Start TLS"
},
+ "sni": {
+ "type": "boolean",
+ "title": "Use Server URI for SNI verification"
+ },
"base_dn": {
"type": "string",
"minLength": 1,
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
index 7499d20f4..a8e43fce6 100644
--- a/locale/en/LC_MESSAGES/django.po
+++ b/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2023-05-21 21:59+0000\n"
+"POT-Creation-Date: 2023-06-12 12:11+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -31,16 +31,16 @@ msgstr ""
msgid "Validation Error"
msgstr ""
-#: authentik/blueprints/api.py:43
+#: authentik/blueprints/api.py:44
msgid "Blueprint file does not exist"
msgstr ""
-#: authentik/blueprints/api.py:54
+#: authentik/blueprints/api.py:55
#, python-format
msgid "Failed to validate blueprint: %(logs)s"
msgstr ""
-#: authentik/blueprints/api.py:59
+#: authentik/blueprints/api.py:60
msgid "Either path or content must be set."
msgstr ""
@@ -69,24 +69,24 @@ msgstr ""
msgid "authentik Export - %(date)s"
msgstr ""
-#: authentik/blueprints/v1/tasks.py:149 authentik/crypto/tasks.py:93
+#: authentik/blueprints/v1/tasks.py:150 authentik/crypto/tasks.py:93
#, python-format
msgid "Successfully imported %(count)d files."
msgstr ""
-#: authentik/core/api/providers.py:113
+#: authentik/core/api/providers.py:120
msgid "SAML Provider from Metadata"
msgstr ""
-#: authentik/core/api/providers.py:114
+#: authentik/core/api/providers.py:121
msgid "Create a SAML Provider by importing its Metadata."
msgstr ""
-#: authentik/core/api/users.py:118
+#: authentik/core/api/users.py:143
msgid "No leading or trailing slashes allowed."
msgstr ""
-#: authentik/core/api/users.py:121
+#: authentik/core/api/users.py:146
msgid "No empty segments in user path allowed."
msgstr ""
@@ -1365,7 +1365,7 @@ msgstr ""
msgid "Authentication token"
msgstr ""
-#: authentik/providers/scim/models.py:33 authentik/sources/ldap/models.py:82
+#: authentik/providers/scim/models.py:33 authentik/sources/ldap/models.py:94
msgid "Property mappings used for group creation/updating."
msgstr ""
@@ -1426,79 +1426,88 @@ msgstr ""
msgid "Used recovery-link to authenticate."
msgstr ""
-#: authentik/sources/ldap/models.py:35
+#: authentik/sources/ldap/models.py:37
msgid "Server URI"
msgstr ""
-#: authentik/sources/ldap/models.py:43
+#: authentik/sources/ldap/models.py:46
msgid ""
"Optionally verify the LDAP Server's Certificate against the CA Chain in this "
"keypair."
msgstr ""
-#: authentik/sources/ldap/models.py:47
-msgid "Bind CN"
-msgstr ""
-
-#: authentik/sources/ldap/models.py:49
-msgid "Enable Start TLS"
-msgstr ""
-
-#: authentik/sources/ldap/models.py:51
-msgid "Base DN"
-msgstr ""
-
-#: authentik/sources/ldap/models.py:53
-msgid "Prepended to Base DN for User-queries."
-msgstr ""
-
-#: authentik/sources/ldap/models.py:54
-msgid "Addition User DN"
+#: authentik/sources/ldap/models.py:55
+msgid ""
+"Client certificate to authenticate against the LDAP Server's Certificate."
msgstr ""
#: authentik/sources/ldap/models.py:58
-msgid "Prepended to Base DN for Group-queries."
+msgid "Bind CN"
msgstr ""
-#: authentik/sources/ldap/models.py:59
-msgid "Addition Group DN"
+#: authentik/sources/ldap/models.py:60
+msgid "Enable Start TLS"
+msgstr ""
+
+#: authentik/sources/ldap/models.py:61
+msgid "Use Server URI for SNI verification"
+msgstr ""
+
+#: authentik/sources/ldap/models.py:63
+msgid "Base DN"
msgstr ""
#: authentik/sources/ldap/models.py:65
+msgid "Prepended to Base DN for User-queries."
+msgstr ""
+
+#: authentik/sources/ldap/models.py:66
+msgid "Addition User DN"
+msgstr ""
+
+#: authentik/sources/ldap/models.py:70
+msgid "Prepended to Base DN for Group-queries."
+msgstr ""
+
+#: authentik/sources/ldap/models.py:71
+msgid "Addition Group DN"
+msgstr ""
+
+#: authentik/sources/ldap/models.py:77
msgid "Consider Objects matching this filter to be Users."
msgstr ""
-#: authentik/sources/ldap/models.py:68
+#: authentik/sources/ldap/models.py:80
msgid "Field which contains members of a group."
msgstr ""
-#: authentik/sources/ldap/models.py:72
+#: authentik/sources/ldap/models.py:84
msgid "Consider Objects matching this filter to be Groups."
msgstr ""
-#: authentik/sources/ldap/models.py:75
+#: authentik/sources/ldap/models.py:87
msgid "Field which contains a unique Identifier."
msgstr ""
-#: authentik/sources/ldap/models.py:89
+#: authentik/sources/ldap/models.py:101
msgid ""
"When a user changes their password, sync it back to LDAP. This can only be "
"enabled on a single LDAP source."
msgstr ""
-#: authentik/sources/ldap/models.py:159
+#: authentik/sources/ldap/models.py:188
msgid "LDAP Source"
msgstr ""
-#: authentik/sources/ldap/models.py:160
+#: authentik/sources/ldap/models.py:189
msgid "LDAP Sources"
msgstr ""
-#: authentik/sources/ldap/models.py:182
+#: authentik/sources/ldap/models.py:211
msgid "LDAP Property Mapping"
msgstr ""
-#: authentik/sources/ldap/models.py:183
+#: authentik/sources/ldap/models.py:212
msgid "LDAP Property Mappings"
msgstr ""
diff --git a/schema.yml b/schema.yml
index 5ac3ccba5..63065ceee 100644
--- a/schema.yml
+++ b/schema.yml
@@ -17096,6 +17096,11 @@ paths:
name: bind_cn
schema:
type: string
+ - in: query
+ name: client_certificate
+ schema:
+ type: string
+ format: uuid
- in: query
name: enabled
schema:
@@ -17171,6 +17176,10 @@ paths:
name: slug
schema:
type: string
+ - in: query
+ name: sni
+ schema:
+ type: boolean
- in: query
name: start_tls
schema:
@@ -30950,11 +30959,20 @@ components:
nullable: true
description: Optionally verify the LDAP Server's Certificate against the
CA Chain in this keypair.
+ client_certificate:
+ type: string
+ format: uuid
+ nullable: true
+ description: Client certificate to authenticate against the LDAP Server's
+ Certificate.
bind_cn:
type: string
start_tls:
type: boolean
title: Enable Start TLS
+ sni:
+ type: boolean
+ title: Use Server URI for SNI verification
base_dn:
type: string
additional_user_dn:
@@ -31064,6 +31082,12 @@ components:
nullable: true
description: Optionally verify the LDAP Server's Certificate against the
CA Chain in this keypair.
+ client_certificate:
+ type: string
+ format: uuid
+ nullable: true
+ description: Client certificate to authenticate against the LDAP Server's
+ Certificate.
bind_cn:
type: string
bind_password:
@@ -31072,6 +31096,9 @@ components:
start_tls:
type: boolean
title: Enable Start TLS
+ sni:
+ type: boolean
+ title: Use Server URI for SNI verification
base_dn:
type: string
minLength: 1
@@ -36356,6 +36383,12 @@ components:
nullable: true
description: Optionally verify the LDAP Server's Certificate against the
CA Chain in this keypair.
+ client_certificate:
+ type: string
+ format: uuid
+ nullable: true
+ description: Client certificate to authenticate against the LDAP Server's
+ Certificate.
bind_cn:
type: string
bind_password:
@@ -36364,6 +36397,9 @@ components:
start_tls:
type: boolean
title: Enable Start TLS
+ sni:
+ type: boolean
+ title: Use Server URI for SNI verification
base_dn:
type: string
minLength: 1
diff --git a/web/src/admin/sources/ldap/LDAPSourceForm.ts b/web/src/admin/sources/ldap/LDAPSourceForm.ts
index 986a973b1..0985f7cde 100644
--- a/web/src/admin/sources/ldap/LDAPSourceForm.ts
+++ b/web/src/admin/sources/ldap/LDAPSourceForm.ts
@@ -184,6 +184,26 @@ export class LDAPSourceForm extends ModelForm {
${msg("To use SSL instead, use 'ldaps://' and disable this option.")}
+
+
+
+
+
+
+
+
+ ${msg("Use Server URI for SNI verification")}
+
+
+ ${msg("Required for servers using TLS 1.3+")}
+
+
{
)}
+
+ => {
+ const args: CryptoCertificatekeypairsListRequest = {
+ ordering: "name",
+ hasKey: true,
+ includeDetails: false,
+ };
+ if (query !== undefined) {
+ args.search = query;
+ }
+ const certificates = await new CryptoApi(
+ DEFAULT_CONFIG,
+ ).cryptoCertificatekeypairsList(args);
+ return certificates.results;
+ }}
+ .renderElement=${(item: CertificateKeyPair): string => {
+ return item.name;
+ }}
+ .value=${(item: CertificateKeyPair | undefined): string | undefined => {
+ return item?.pk;
+ }}
+ .selected=${(item: CertificateKeyPair): boolean => {
+ return item.pk === this.instance?.clientCertificate;
+ }}
+ ?blankable=${true}
+ >
+
+
+ ${msg(
+ "Client certificate keypair to authenticate against the LDAP Server's Certificate.",
+ )}
+
+
Certificate
Zertifikat
-
- Due to protocol limitations, this certificate is only used when the outpost has a single provider, or all providers use the same certificate.
-
-
- If multiple providers share an outpost, a self-signed certificate is used.
- Wenn sich mehrere Anbieter einen Außenposten teilen, wird ein selbstsigniertes Zertifikat verwendet.
-
UID start number
UID-Startnummer
@@ -5722,6 +5715,27 @@ Bindings to groups/users are checked against the user of the event.
Activate
Aktivieren
+
+ Use Server URI for SNI verification
+
+
+ Required for servers using TLS 1.3+
+
+
+ Client certificate keypair to authenticate against the LDAP Server's Certificate.
+
+
+ The certificate for the above configured Base DN. As a fallback, the provider uses a self-signed certificate.
+
+
+ TLS Server name
+
+
+ DNS name for which the above configured certificate should be used. The certificate cannot be detected based on the base DN, as the SSL/TLS negotiation happens before such data is exchanged.
+
+
+ TLS Client authentication certificate
+