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) self.schemes = ["http", "https", "blank"] + list(self.schemes)
def __call__(self, value): def __call__(self, value: str):
# Check if the scheme is valid. # Check if the scheme is valid.
scheme = value.split("://")[0].lower() scheme = value.split("://")[0].lower()
if scheme not in self.schemes: if scheme not in self.schemes:
value = "default" + value 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.db import models
from django.utils.translation import gettext_lazy as _ 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 rest_framework.serializers import Serializer
from authentik.core.models import Group, PropertyMapping, Source from authentik.core.models import Group, PropertyMapping, Source
@ -12,11 +12,22 @@ from authentik.lib.models import DomainlessURLValidator
LDAP_TIMEOUT = 15 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): class LDAPSource(Source):
"""Federate LDAP Directory with authentik, or create new accounts in LDAP.""" """Federate LDAP Directory with authentik, or create new accounts in LDAP."""
server_uri = models.TextField( server_uri = models.TextField(
validators=[DomainlessURLValidator(schemes=["ldap", "ldaps"])], validators=[MultiURLValidator(schemes=["ldap", "ldaps"])],
verbose_name=_("Server URI"), verbose_name=_("Server URI"),
) )
bind_cn = models.TextField(verbose_name=_("Bind CN"), blank=True) bind_cn = models.TextField(verbose_name=_("Bind CN"), blank=True)
@ -88,9 +99,15 @@ class LDAPSource(Source):
def connection(self) -> Connection: def connection(self) -> Connection:
"""Get a fully connected and bound LDAP Connection""" """Get a fully connected and bound LDAP Connection"""
if not self._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( self._connection = Connection(
server, pool,
raise_exceptions=True, raise_exceptions=True,
user=self.bind_cn, user=self.bind_cn,
password=self.bind_password, 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." 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." 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/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Stage" 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." 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." 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/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Stage" 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." 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 "" 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/BoundStagesList.ts
#: src/pages/flows/StageBindingForm.ts #: src/pages/flows/StageBindingForm.ts
msgid "Stage" msgid "Stage"

View file

@ -124,6 +124,9 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
class="pf-c-form-control" class="pf-c-form-control"
required 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>
<ak-form-element-horizontal name="startTls"> <ak-form-element-horizontal name="startTls">
<div class="pf-c-check"> <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://` 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 CN: `<name of your service user>@ad.company`
- Bind Password: The password you've given the user above - Bind Password: The password you've given the user above
- Base DN: The base DN which you want authentik to sync - Base DN: The base DN which you want authentik to sync

View file

@ -30,7 +30,7 @@ The following placeholders will be used:
``` ```
$ ldapmodify -x -D "cn=Directory Manager" -W -h ipa1.freeipa.company -p 389 $ ldapmodify -x -D "cn=Directory Manager" -W -h ipa1.freeipa.company -p 389
dn: cn=ipa_pwd_extop,cn=plugins,cn=config dn: cn=ipa_pwd_extop,cn=plugins,cn=config
changetype: modify changetype: modify
add: passSyncManagersDNs add: passSyncManagersDNs
@ -45,6 +45,11 @@ In authentik, create a new LDAP Source in Resources -> Sources.
Use these settings: Use these settings:
- Server URI: `ldaps://ipa1.freeipa.company` - 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 CN: `uid=svc_authentik,cn=users,cn=accounts,dc=freeipa,dc=company`
- Bind Password: The password you've given the user above - Bind Password: The password you've given the user above
- Base DN: `dc=freeipa,dc=company` - 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. - 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 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. - Bind password: Password used during the bind process.
- Enable StartTLS: Enables StartTLS functionality. To use LDAPS instead, use port `636`. - Enable StartTLS: Enables StartTLS functionality. To use LDAPS instead, use port `636`.