providers/scim, sources/ldap: switch to using postgres advisory locks instead of redis locks (#9511)

* providers/scim, sources/ldap: switch to using postgres advisory locks instead of redis locks

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* website/integrations: discord: fix typo

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* fix timeout logic

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* remove redis locks completely

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>

* Apply suggestions from code review

Signed-off-by: Jens L. <jens@beryju.org>

---------

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Jens L <jens@goauthentik.io>
This commit is contained in:
Marc 'risson' Schmitt
2024-05-23 13:41:42 +02:00
committed by GitHub
parent fbab822db1
commit fbad02fac1
8 changed files with 85 additions and 60 deletions

View File

@ -6,19 +6,19 @@ from shutil import rmtree
from ssl import CERT_REQUIRED
from tempfile import NamedTemporaryFile, mkdtemp
from django.core.cache import cache
import pglock
from django.db import connection, models
from django.templatetags.static import static
from django.utils.translation import gettext_lazy as _
from ldap3 import ALL, NONE, RANDOM, Connection, Server, ServerPool, Tls
from ldap3.core.exceptions import LDAPException, LDAPInsufficientAccessRightsResult, LDAPSchemaError
from redis.lock import Lock
from rest_framework.serializers import Serializer
from authentik.core.models import Group, PropertyMapping, Source
from authentik.crypto.models import CertificateKeyPair
from authentik.lib.config import CONFIG
from authentik.lib.models import DomainlessURLValidator
from authentik.lib.sync.outgoing import LOCK_ACQUIRE_TIMEOUT
LDAP_TIMEOUT = 15
@ -209,15 +209,12 @@ class LDAPSource(Source):
return RuntimeError("Failed to bind")
@property
def sync_lock(self) -> Lock:
"""Redis lock for syncing LDAP to prevent multiple parallel syncs happening"""
return Lock(
cache.client.get_client(),
name=f"goauthentik.io/sources/ldap/sync/{connection.schema_name}-{self.slug}",
# Convert task timeout hours to seconds, and multiply times 3
# (see authentik/sources/ldap/tasks.py:54)
# multiply by 3 to add even more leeway
timeout=(60 * 60 * CONFIG.get_int("ldap.task_timeout_hours")) * 3,
def sync_lock(self) -> pglock.advisory:
"""Postgres lock for syncing LDAP to prevent multiple parallel syncs happening"""
return pglock.advisory(
lock_id=f"goauthentik.io/{connection.schema_name}/sources/ldap/sync/{self.slug}",
timeout=LOCK_ACQUIRE_TIMEOUT,
side_effect=pglock.Raise,
)
def check_connection(self) -> dict[str, dict[str, str]]:

View File

@ -4,8 +4,8 @@ from uuid import uuid4
from celery import chain, group
from django.core.cache import cache
from django.db.utils import OperationalError
from ldap3.core.exceptions import LDAPException
from redis.exceptions import LockError
from structlog.stdlib import get_logger
from authentik.events.models import SystemTask as DBSystemTask
@ -64,12 +64,8 @@ def ldap_sync_single(source_pk: str):
source: LDAPSource = LDAPSource.objects.filter(pk=source_pk).first()
if not source:
return
lock = source.sync_lock
if lock.locked():
LOGGER.debug("LDAP sync locked, skipping task", source=source.slug)
return
try:
with lock:
with source.sync_lock:
# Delete all sync tasks from the cache
DBSystemTask.objects.filter(name="ldap_sync", uid__startswith=source.slug).delete()
task = chain(
@ -84,10 +80,8 @@ def ldap_sync_single(source_pk: str):
),
)
task()
except LockError:
# This should never happen, we check if the lock is locked above so this
# would only happen if there was some other timeout
LOGGER.debug("Failed to acquire lock for LDAP sync", source=source.slug)
except OperationalError:
LOGGER.debug("Failed to acquire lock for LDAP sync, skipping task", source=source.slug)
def ldap_sync_paginator(source: LDAPSource, sync: type[BaseLDAPSynchronizer]) -> list: