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:
Jens Langhammer 2021-12-02 20:15:11 +01:00
parent 7e316b5fc2
commit 75051687e6
9 changed files with 53 additions and 7 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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">

View File

@ -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

View File

@ -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`

View File

@ -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`.