sources/ldap: allow multiple server URIs for loadbalancing and failover
closes #1874 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -68,9 +68,9 @@ class DomainlessURLValidator(URLValidator): | ||||
|         ) | ||||
|         self.schemes = ["http", "https", "blank"] + list(self.schemes) | ||||
|  | ||||
|     def __call__(self, value): | ||||
|     def __call__(self, value: str): | ||||
|         # Check if the scheme is valid. | ||||
|         scheme = value.split("://")[0].lower() | ||||
|         if scheme not in self.schemes: | ||||
|             value = "default" + value | ||||
|         return super().__call__(value) | ||||
|         super().__call__(value) | ||||
|  | ||||
| @ -3,7 +3,7 @@ from typing import Optional, Type | ||||
|  | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from ldap3 import ALL, Connection, Server | ||||
| from ldap3 import ALL, ROUND_ROBIN, Connection, Server, ServerPool | ||||
| from rest_framework.serializers import Serializer | ||||
|  | ||||
| from authentik.core.models import Group, PropertyMapping, Source | ||||
| @ -12,11 +12,22 @@ from authentik.lib.models import DomainlessURLValidator | ||||
| LDAP_TIMEOUT = 15 | ||||
|  | ||||
|  | ||||
| class MultiURLValidator(DomainlessURLValidator): | ||||
|     """Same as DomainlessURLValidator but supports multiple URLs separated with a comma.""" | ||||
|  | ||||
|     def __call__(self, value: str): | ||||
|         if "," in value: | ||||
|             for url in value.split(","): | ||||
|                 super().__call__(url) | ||||
|         else: | ||||
|             super().__call__(value) | ||||
|  | ||||
|  | ||||
| class LDAPSource(Source): | ||||
|     """Federate LDAP Directory with authentik, or create new accounts in LDAP.""" | ||||
|  | ||||
|     server_uri = models.TextField( | ||||
|         validators=[DomainlessURLValidator(schemes=["ldap", "ldaps"])], | ||||
|         validators=[MultiURLValidator(schemes=["ldap", "ldaps"])], | ||||
|         verbose_name=_("Server URI"), | ||||
|     ) | ||||
|     bind_cn = models.TextField(verbose_name=_("Bind CN"), blank=True) | ||||
| @ -88,9 +99,15 @@ class LDAPSource(Source): | ||||
|     def connection(self) -> Connection: | ||||
|         """Get a fully connected and bound LDAP Connection""" | ||||
|         if not self._connection: | ||||
|             server = Server(self.server_uri, get_info=ALL, connect_timeout=LDAP_TIMEOUT) | ||||
|             servers = [] | ||||
|             if "," in self.server_uri: | ||||
|                 for server in self.server_uri.split(","): | ||||
|                     servers.append(Server(server, get_info=ALL, connect_timeout=LDAP_TIMEOUT)) | ||||
|             else: | ||||
|                 servers = [Server(self.server_uri, get_info=ALL, connect_timeout=LDAP_TIMEOUT)] | ||||
|             pool = ServerPool(servers, ROUND_ROBIN, active=True, exhaust=True) | ||||
|             self._connection = Connection( | ||||
|                 server, | ||||
|                 pool, | ||||
|                 raise_exceptions=True, | ||||
|                 user=self.bind_cn, | ||||
|                 password=self.bind_password, | ||||
|  | ||||
| @ -4199,6 +4199,10 @@ msgstr "Sources" | ||||
| msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves." | ||||
| msgstr "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves." | ||||
|  | ||||
| #: src/pages/sources/ldap/LDAPSourceForm.ts | ||||
| msgid "Specify multiple server URIs by separating them with a comma." | ||||
| msgstr "Specify multiple server URIs by separating them with a comma." | ||||
|  | ||||
| #: src/pages/flows/BoundStagesList.ts | ||||
| #: src/pages/flows/StageBindingForm.ts | ||||
| msgid "Stage" | ||||
|  | ||||
| @ -4162,6 +4162,10 @@ msgstr "Sources" | ||||
| msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves." | ||||
| msgstr "Sources d'identités, qui peuvent soit être synchronisées dans la base de données d'Authentik, soit être utilisées par les utilisateurs pour s'authentifier et s'inscrire." | ||||
|  | ||||
| #: src/pages/sources/ldap/LDAPSourceForm.ts | ||||
| msgid "Specify multiple server URIs by separating them with a comma." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/flows/BoundStagesList.ts | ||||
| #: src/pages/flows/StageBindingForm.ts | ||||
| msgid "Stage" | ||||
|  | ||||
| @ -4191,6 +4191,10 @@ msgstr "" | ||||
| msgid "Sources of identities, which can either be synced into authentik's database, or can be used by users to authenticate and enroll themselves." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/sources/ldap/LDAPSourceForm.ts | ||||
| msgid "Specify multiple server URIs by separating them with a comma." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/pages/flows/BoundStagesList.ts | ||||
| #: src/pages/flows/StageBindingForm.ts | ||||
| msgid "Stage" | ||||
|  | ||||
| @ -124,6 +124,9 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> { | ||||
|                             class="pf-c-form-control" | ||||
|                             required | ||||
|                         /> | ||||
|                         <p class="pf-c-form__helper-text"> | ||||
|                             ${t`Specify multiple server URIs by separating them with a comma.`} | ||||
|                         </p> | ||||
|                     </ak-form-element-horizontal> | ||||
|                     <ak-form-element-horizontal name="startTls"> | ||||
|                         <div class="pf-c-check"> | ||||
|  | ||||
| @ -43,6 +43,10 @@ Use these settings: | ||||
|  | ||||
|     For authentik to be able to write passwords back to Active Directory, make sure to use `ldaps://` | ||||
|  | ||||
|     You can specify multiple servers by separating URIs with a comma, like `ldap://dc1.ad.company,ldap://dc2.ad.company`. | ||||
|  | ||||
|     When using a DNS entry with multiple Records, authentik will select a random entry when first connecting. | ||||
|  | ||||
| - Bind CN: `<name of your service user>@ad.company` | ||||
| - Bind Password: The password you've given the user above | ||||
| - Base DN: The base DN which you want authentik to sync | ||||
|  | ||||
| @ -45,6 +45,11 @@ In authentik, create a new LDAP Source in Resources -> Sources. | ||||
| Use these settings: | ||||
|  | ||||
| - Server URI: `ldaps://ipa1.freeipa.company` | ||||
|  | ||||
|     You can specify multiple servers by separating URIs with a comma, like `ldap://ipa1.freeipa.company,ldap://ipa2.freeipa.company`. | ||||
|  | ||||
|     When using a DNS entry with multiple Records, authentik will select a random entry when first connecting. | ||||
|  | ||||
| - Bind CN: `uid=svc_authentik,cn=users,cn=accounts,dc=freeipa,dc=company` | ||||
| - Bind Password: The password you've given the user above | ||||
| - Base DN: `dc=freeipa,dc=company` | ||||
|  | ||||
| @ -15,6 +15,11 @@ For FreeIPA, follow the [FreeIPA Integration](../freeipa/index.md) | ||||
| ::: | ||||
|  | ||||
| - Server URI: URI to your LDAP server/Domain Controller. | ||||
|  | ||||
|     You can specify multiple servers by separating URIs with a comma, like `ldap://ldap1.company,ldap://ldap2.company`. | ||||
|  | ||||
|     When using a DNS entry with multiple Records, authentik will select a random entry when first connecting. | ||||
|  | ||||
| - Bind CN: CN of the bind user. This can also be a UPN in the format of `user@domain.tld`. | ||||
| - Bind password: Password used during the bind process. | ||||
| - Enable StartTLS: Enables StartTLS functionality. To use LDAPS instead, use port `636`. | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer