diff --git a/authentik/sources/kerberos/models.py b/authentik/sources/kerberos/models.py index b5656e46409c..5d679d4480cd 100644 --- a/authentik/sources/kerberos/models.py +++ b/authentik/sources/kerberos/models.py @@ -6,7 +6,6 @@ from typing import Any import gssapi -import kadmin import pglock from django.db import connection, models from django.db.models.fields import b64decode @@ -14,6 +13,8 @@ from django.shortcuts import reverse from django.templatetags.static import static from django.utils.translation import gettext_lazy as _ +from kadmin import KAdmin +from kadmin.exceptions import PyKAdminException from rest_framework.serializers import Serializer from structlog.stdlib import get_logger @@ -30,9 +31,8 @@ LOGGER = get_logger() -# python-kadmin leaks file descriptors. As such, this global is used to reuse -# existing kadmin connections instead of creating new ones, which results in less to no file -# descriptors leaks +# Creating kadmin connections is expensive. As such, this global is used to reuse +# existing kadmin connections instead of creating new ones _kadmin_connections: dict[str, Any] = {} @@ -198,13 +198,13 @@ def krb5_conf_path(self) -> str | None: conf_path.write_text(self.krb5_conf) return str(conf_path) - def _kadmin_init(self) -> "kadmin.KAdmin | None": + def _kadmin_init(self) -> KAdmin | None: # kadmin doesn't use a ccache for its connection # as such, we don't need to create a separate ccache for each source if not self.sync_principal: return None if self.sync_password: - return kadmin.init_with_password( + return KAdmin.with_password( self.sync_principal, self.sync_password, ) @@ -215,18 +215,18 @@ def _kadmin_init(self) -> "kadmin.KAdmin | None": keytab_path.touch(mode=0o600) keytab_path.write_bytes(b64decode(self.sync_keytab)) keytab = f"FILE:{keytab_path}" - return kadmin.init_with_keytab( + return KAdmin.with_keytab( self.sync_principal, keytab, ) if self.sync_ccache: - return kadmin.init_with_ccache( + return KAdmin.with_ccache( self.sync_principal, self.sync_ccache, ) return None - def connection(self) -> "kadmin.KAdmin | None": + def connection(self) -> KAdmin | None: """Get kadmin connection""" if str(self.pk) not in _kadmin_connections: kadm = self._kadmin_init() @@ -246,7 +246,7 @@ def check_connection(self) -> dict[str, str]: status["status"] = "no connection" return status status["principal_exists"] = kadm.principal_exists(self.sync_principal) - except kadmin.KAdminError as exc: + except PyKAdminException as exc: status["status"] = str(exc) return status diff --git a/authentik/sources/kerberos/signals.py b/authentik/sources/kerberos/signals.py index af3306a7a1e3..7c1db64be8fa 100644 --- a/authentik/sources/kerberos/signals.py +++ b/authentik/sources/kerberos/signals.py @@ -1,8 +1,8 @@ """authentik kerberos source signals""" -import kadmin from django.db.models.signals import post_save from django.dispatch import receiver +from kadmin.exceptions import PyKAdminException from rest_framework.serializers import ValidationError from structlog.stdlib import get_logger @@ -48,7 +48,7 @@ def kerberos_sync_password(sender, user: User, password: str, **_): source.connection().getprinc(user_source_connection.identifier).change_password( password ) - except kadmin.KAdminError as exc: + except PyKAdminException as exc: LOGGER.warning("failed to set Kerberos password", exc=exc, source=source) Event.new( EventAction.CONFIGURATION_ERROR, diff --git a/authentik/sources/kerberos/sync.py b/authentik/sources/kerberos/sync.py index 6fcd87c5380c..4b34fed6c675 100644 --- a/authentik/sources/kerberos/sync.py +++ b/authentik/sources/kerberos/sync.py @@ -2,9 +2,9 @@ from typing import Any -import kadmin from django.core.exceptions import FieldError from django.db import IntegrityError, transaction +from kadmin import KAdmin from structlog.stdlib import BoundLogger, get_logger from authentik.core.expression.exceptions import ( @@ -30,7 +30,7 @@ class KerberosSync: _source: KerberosSource _logger: BoundLogger - _connection: "kadmin.KAdmin" + _connection: KAdmin mapper: SourceMapper user_manager: PropertyMappingManager group_manager: PropertyMappingManager @@ -161,7 +161,7 @@ def sync(self) -> int: user_count = 0 with Krb5ConfContext(self._source): - for principal in self._connection.principals(): + for principal in self._connection.list_principals(None): if self._handle_principal(principal): user_count += 1 return user_count diff --git a/authentik/sources/kerberos/tests/test_auth.py b/authentik/sources/kerberos/tests/test_auth.py index 72db23e7192a..a3996d680687 100644 --- a/authentik/sources/kerberos/tests/test_auth.py +++ b/authentik/sources/kerberos/tests/test_auth.py @@ -23,6 +23,7 @@ def setUp(self): ) self.user = User.objects.create(username=generate_id()) self.user.set_unusable_password() + self.user.save() UserKerberosSourceConnection.objects.create( source=self.source, user=self.user, identifier=self.realm.user_princ ) diff --git a/authentik/sources/kerberos/tests/test_spnego.py b/authentik/sources/kerberos/tests/test_spnego.py index 3d7f3ccf4a2c..3e9269fd2100 100644 --- a/authentik/sources/kerberos/tests/test_spnego.py +++ b/authentik/sources/kerberos/tests/test_spnego.py @@ -2,6 +2,8 @@ from base64 import b64decode, b64encode from pathlib import Path +from sys import platform +from unittest import skipUnless import gssapi from django.urls import reverse @@ -36,6 +38,7 @@ def test_api_read(self): ) self.assertEqual(response.status_code, 200) + @skipUnless(platform.startswith("linux"), "Requires compatible GSSAPI implementation") def test_source_login(self): """test login view""" response = self.client.get( diff --git a/poetry.lock b/poetry.lock index 201bf6b702b9..ce9465a68e6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -3951,19 +3951,50 @@ files = [ cli = ["click (>=5.0)"] [[package]] -name = "python-kadmin" +name = "python-kadmin-rs" version = "0.2.0" -description = "Python module for kerberos admin (kadm5)" -optional = false -python-versions = ">=3.8" -files = [] -develop = false - -[package.source] -type = "git" -url = "https://github.com/authentik-community/python-kadmin.git" -reference = "v0.2.0" -resolved_reference = "6f9ce6ee2427e3488b403a900a9211166c7569e1" +description = "Python interface to the Kerberos administration interface (kadm5)" +optional = false +python-versions = "<3.14,>=3.9" +files = [ + {file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1fd29e57c165b0b677b5fd1fac6dba829f077125bd081dd061ed2d9431f0c14f"}, + {file = "python_kadmin_rs-0.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:aa1d4ddb2a12fb852a650091e1924c57639b376538c5b23bdd2b00b93472d7a4"}, + {file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2bdc6011906f074368311bb5be300b0636671549f3d6e918c5275189ee300659"}, + {file = "python_kadmin_rs-0.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:eb6b967246debe6090293cea35aa8ef159f04c99cb78acfa260b55c00f96cd76"}, + {file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95aeb75ff0921f01e9d47b02651994421619eaa7fb7f29c9552465134f638377"}, + {file = "python_kadmin_rs-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6285c951f74de5749b9bd43561439e6fe9fe3db5234837ad7d2f0bcd3725887a"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1857a02b3f5f63fc4ec7fdaab87951e20517116e8075fedc25fcd235b93e58ce"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:910ad697934bf5502d810d2e8f8d7231c367c2644f135c7e5fa77afd50f11fae"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d2938d701553433a84148c5679c6285d4b6e30644f9275e93c49b3c7afd75244"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:03a28a1522ada645a5a88f96b821d70b141266f4d524779868c82a8bc22e11dc"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:532406f48aa1567f8b374f7d6bb3a5ab46b8d4c6047e38fd8336dbbf37aa2a2b"}, + {file = "python_kadmin_rs-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:36786ecc1015ca52f7f9b9b6000d1f9dab21e37f05ddce7cb9b674eb6922270c"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:be901980ded6077ae17b82af24ef4b7f78fae361aafb3c59cb95a81757c38765"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fbce43213bda7e3071725432cbece677ded29e4c0f2227b63227aff8c828ae8b"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ce1ca8f6dcbd92ff8d7fd42ab83d7d25ff5e5432ae76ab605df2623cf4d52944"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e97ccf97054399b383b7ad1314b064191e16657cd320e2f4a06f0993e60cd946"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:77a017bdb8bf70cbbe599129c5efdb014255dd4a68e4c65825eadb210748ae3f"}, + {file = "python_kadmin_rs-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bdc00198fa573261ad2b60bbaac3d9f0b67adcfa8bb0b49685dedc0f5843c1da"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e63d95ab5bc4ff12be5614d1928386bbee6d54141533b955f65bc6df9144bf97"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d266974af2faf4d44b9573f9d2c4b5c815dfb227223e9f019422936e06029224"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:619a751b283d399edb6a8fa1957d6bc44dc404a9c0f16ed61517a2c288980341"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6a918aa7df7ae60d31cfefe9de7c05b2acba049af6e6a63fe541f2e43c3e9298"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eeebccec2f3d38366c7406418c112ae72ac074581c378f22dcf458703a0e0f65"}, + {file = "python_kadmin_rs-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81cce25d8257774761c0e7d264ce8f43d90a752f6ebfdb87e17ed42d7199222e"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:9d208448e6c5bb694e3e75f80a46df4a8d5d457da4d50fa8fbdc6039f15bd5f6"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fa29bb5873b909852b3f8ec3a79b7d9b79c2367ae1d1b80a2239dbc2d600388"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:685dfd62df8da0ce0023af0ff27a18859d5f9b98e4494bd18d6cd792aa10dd19"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4cad0165ac4444d2f38ba9e320039cdc33e47e6cb0f4ed0f5f1da880d6d6f979"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cffe60c6f03612b27da2b0f63514190aadd7c97ae37a60cd3fe558f167c0ceff"}, + {file = "python_kadmin_rs-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7d5a5dc9a38eea43e5579a20564fc071cd8e7b04be7ea6d73e08625087c4f52a"}, + {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:8861f7ebf4921f6ef01fac3a44c18b8777703e1bb34c5f90eab9d9edae254e0f"}, + {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0c075a82da8bb6d36ba50a0c227816068acb54e08edfaaffda2959a643e1015a"}, + {file = "python_kadmin_rs-0.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:00c33345be4871e031ed642a3a26fbf20b5133307dd932a8a7a4af2b8b3a4e11"}, + {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:798ceca4480cff3b82becb9ce6c8f6fda0daac41e90efc458872b5208608b506"}, + {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3b9b5a40457649271bf49670a91cd697e831b4515c9f59e923a68bfacb6be884"}, + {file = "python_kadmin_rs-0.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b74f4b76d1a67fa81615936a945794ea8cfe92ca0718389f6a6e5f89d19a2284"}, + {file = "python_kadmin_rs-0.2.0.tar.gz", hash = "sha256:9a3e49b7a27f39e6b533a13eb0a0d23f2ecc186d8448d531b858a1dab3226f8a"}, +] [[package]] name = "pytz" @@ -5567,4 +5598,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "10aa88f2f0e56cddd91adba8c39c52de92763429fb615a27c3dc218952cff808" +content-hash = "2a7712ca513371eb16ff0b87fc80218e982a5a734bba0bb130b8123e807eb16f" diff --git a/pyproject.toml b/pyproject.toml index 5fdcca5e501f..768b993aa0ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,8 +131,7 @@ pydantic-scim = "*" pyjwt = "*" pyrad = "*" python = "~3.12" -# Fork of python-kadmin with compilation fixes as it's unmaintained -python-kadmin = { git = "https://github.com/authentik-community/python-kadmin.git", tag = "v0.2.0" } +python-kadmin-rs = "0.2.0" pyyaml = "*" requests-oauthlib = "*" scim2-filter-parser = "*"