diff --git a/authentik/events/monitored_tasks.py b/authentik/events/monitored_tasks.py index 670770220..35b89a843 100644 --- a/authentik/events/monitored_tasks.py +++ b/authentik/events/monitored_tasks.py @@ -102,7 +102,7 @@ class TaskInfo: key = CACHE_KEY_PREFIX + self.task_name if self.result.uid: key += f"/{self.result.uid}" - self.task_name += f"_{self.result.uid}" + self.task_name += f"/{self.result.uid}" self.set_prom_metrics() cache.set(key, self, timeout=timeout_hours * 60 * 60) diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index f838cdd3b..8349bb10d 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -17,9 +17,7 @@ from authentik.core.api.sources import SourceSerializer from authentik.core.api.used_by import UsedByMixin from authentik.events.monitored_tasks import TaskInfo from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource -from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer -from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer -from authentik.sources.ldap.sync.users import UserLDAPSynchronizer +from authentik.sources.ldap.tasks import SYNC_CLASSES class LDAPSourceSerializer(SourceSerializer): @@ -104,13 +102,9 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): """Get source's sync status""" source = self.get_object() results = [] - for sync_class in [ - UserLDAPSynchronizer, - GroupLDAPSynchronizer, - MembershipLDAPSynchronizer, - ]: + for sync_class in SYNC_CLASSES: sync_name = sync_class.__name__.replace("LDAPSynchronizer", "").lower() - task = TaskInfo.by_name(f"ldap_sync_{source.slug}_{sync_name}") + task = TaskInfo.by_name(f"ldap_sync/{source.slug}_{sync_name}") if task: results.append(task) return Response(TaskSerializer(results, many=True).data) diff --git a/authentik/sources/ldap/management/__init__.py b/authentik/sources/ldap/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/sources/ldap/management/commands/__init__.py b/authentik/sources/ldap/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/authentik/sources/ldap/management/commands/ldap_sync.py b/authentik/sources/ldap/management/commands/ldap_sync.py new file mode 100644 index 000000000..3e52fc5c7 --- /dev/null +++ b/authentik/sources/ldap/management/commands/ldap_sync.py @@ -0,0 +1,27 @@ +"""LDAP Sync""" +from django.core.management.base import BaseCommand +from structlog.stdlib import get_logger + +from authentik.lib.utils.reflection import class_to_path +from authentik.sources.ldap.models import LDAPSource +from authentik.sources.ldap.tasks import SYNC_CLASSES, ldap_sync + +LOGGER = get_logger() + + +class Command(BaseCommand): + """Run sync for an LDAP Source""" + + def add_arguments(self, parser): + parser.add_argument("source_slugs", nargs="+", type=str) + + def handle(self, **options): + for source_slug in options["source_slugs"]: + source = LDAPSource.objects.filter(slug=source_slug).first() + if not source: + LOGGER.warning("Source does not exist", slug=source_slug) + continue + for sync_class in SYNC_CLASSES: + LOGGER.info("Starting sync", cls=sync_class) + # pylint: disable=no-value-for-parameter + ldap_sync(source.pk, class_to_path(sync_class)) diff --git a/authentik/sources/ldap/tasks.py b/authentik/sources/ldap/tasks.py index 1188d95ad..d97ea4a5f 100644 --- a/authentik/sources/ldap/tasks.py +++ b/authentik/sources/ldap/tasks.py @@ -13,17 +13,18 @@ from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer from authentik.sources.ldap.sync.users import UserLDAPSynchronizer LOGGER = get_logger() +SYNC_CLASSES = [ + UserLDAPSynchronizer, + GroupLDAPSynchronizer, + MembershipLDAPSynchronizer, +] @CELERY_APP.task() def ldap_sync_all(): """Sync all sources""" for source in LDAPSource.objects.filter(enabled=True): - for sync_class in [ - UserLDAPSynchronizer, - GroupLDAPSynchronizer, - MembershipLDAPSynchronizer, - ]: + for sync_class in SYNC_CLASSES: ldap_sync.delay(source.pk, class_to_path(sync_class)) diff --git a/website/docs/troubleshooting/ldap_source.md b/website/docs/troubleshooting/ldap_source.md new file mode 100644 index 000000000..5d7450c69 --- /dev/null +++ b/website/docs/troubleshooting/ldap_source.md @@ -0,0 +1,15 @@ +--- +title: Troubleshooting LDAP Synchronization +--- + +To troubleshoot LDAP sources, you can run the command below to run a synchronization in the foreground and see any errors or warnings that might happen directly + +``` +docker-compose run --rm server ldap_sync *slug of the source* +``` + +or, for Kubernetes, run + +``` +kubectl exec -it deployment/authentik-worker -c authentik -- ak ldap_sync *slug of the source* +``` diff --git a/website/integrations/sources/ldap/index.md b/website/integrations/sources/ldap/index.md index 846064d9a..f64e70a07 100644 --- a/website/integrations/sources/ldap/index.md +++ b/website/integrations/sources/ldap/index.md @@ -41,3 +41,7 @@ LDAP property mappings can be used to convert the raw LDAP response into an auth By default, authentik ships with some pre-configured mappings for the most common LDAP setups. You can assign the value of a mapping to any user attribute, or save it as a custom attribute by prefixing the object field with `attribute.` Keep in mind though, data types from the LDAP server will be carried over. This means that with some implementations, where fields are stored as array in LDAP, they will be saved as array in authentik. To prevent this, use the built-in `list_flatten` function. + +## Troubleshooting + +To troubleshoot LDAP sources and their synchronization, see [LDAP Troubleshooting](../../../docs/troubleshooting/ldap_source) diff --git a/website/sidebars.js b/website/sidebars.js index 0cb085903..6f82aca3b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -284,6 +284,7 @@ module.exports = { "troubleshooting/image_upload", "troubleshooting/missing_permission", "troubleshooting/missing_admin_group", + "troubleshooting/ldap_source", ], }, {