Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sources: add Kerberos #10815

Draft
wants to merge 50 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
0e1347e
sources: introduce new property mappings per-user and group
rissson Feb 29, 2024
a99b6ed
sources/ldap: migrate to new property mappings
rissson Feb 29, 2024
02fd08c
lint-fix and make gen
rissson Feb 29, 2024
5bbe030
web changes
rissson Feb 29, 2024
c8f0a4e
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
0f92e37
fix tests
rissson Mar 1, 2024
1dbd5eb
update tests
rissson Mar 1, 2024
7132d27
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
3df5c67
remove flatten for generic implem
rissson Mar 1, 2024
4927d2f
Merge branch 'main' into refactor-sources
rissson Mar 1, 2024
d276eb3
rework migration
rissson Mar 1, 2024
4323237
lint-fix
rissson Mar 1, 2024
924461b
wip
rissson Mar 1, 2024
99e2ce7
fix migrations
rissson Mar 1, 2024
44c92c2
re-add field migration to property mappings
rissson Mar 1, 2024
20b18bc
Merge branch 'main' into refactor-sources
rissson Apr 24, 2024
cd66792
fix migrations
rissson Apr 24, 2024
bddfb7f
more migrations fixes
rissson Apr 25, 2024
442248a
Merge branch 'main' into refactor-sources
rissson Jun 6, 2024
193e4ed
easy fixes
rissson Jun 6, 2024
8083ab2
migrate to propertymappingmanager
rissson Jun 6, 2024
a82b5b7
ruff and small fixes
rissson Jun 6, 2024
b9b3f55
Merge branch 'main' into refactor-sources
rissson Jul 15, 2024
dea6dc4
move mapping things into a separate class
rissson Jul 15, 2024
eb1b588
migrations: use using(db_alias)
rissson Jul 15, 2024
4d5a05d
migrations: use built-in variable
rissson Jul 15, 2024
8195352
add docs
rissson Jul 15, 2024
8c60f05
add release notes
rissson Jul 15, 2024
ac554e8
Merge branch 'main' into refactor-sources
rissson Jul 16, 2024
d23fc68
Merge branch 'main' into refactor-sources
rissson Jul 19, 2024
1a3c3da
wip
rissson Jul 22, 2024
964f103
Merge branch 'main' into kerberos-source-reworked
rissson Jul 29, 2024
2c00115
wip
rissson Jul 29, 2024
7e7db89
wip
rissson Jul 29, 2024
4895fed
Merge branch 'main' into kerberos-source-reworked
rissson Aug 7, 2024
98a76d0
wip
rissson Aug 7, 2024
354bea8
wip
rissson Aug 7, 2024
9a159a9
wip
rissson Aug 7, 2024
c74aba2
wip
rissson Aug 7, 2024
33464a4
wip
rissson Aug 7, 2024
893d31d
lint
rissson Aug 7, 2024
bfdc225
fix login reverse
rissson Aug 7, 2024
0df3379
Merge branch 'main' into kerberos-source-reworked
rissson Aug 8, 2024
6a1e193
wip
rissson Aug 8, 2024
fc2cf7e
wip
rissson Aug 12, 2024
e73b651
Merge branch 'main' into kerberos-source-reworked
rissson Sep 18, 2024
9439772
refactor source flow manager matching
rissson Sep 20, 2024
63c0c5f
kerberos sync with mode matching
rissson Sep 20, 2024
41521e2
fixup
rissson Sep 20, 2024
38e7df9
wip
rissson Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
  • Loading branch information
rissson committed Aug 8, 2024
commit 6a1e1935062f0cb30bbf172faf8fe788c0dea95c
2 changes: 1 addition & 1 deletion authentik/sources/kerberos/auth.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,114 @@
"""authentik Kerberos Authentication Backend"""

import gssapi
from django.http import HttpRequest
from structlog.stdlib import get_logger

Check warning on line 5 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L3-L5

Added lines #L3 - L5 were not covered by tests

from authentik.core.auth import InbuiltBackend
from authentik.core.models import User
from authentik.lib.generators import generate_id
from authentik.sources.kerberos.models import (

Check warning on line 10 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L7-L10

Added lines #L7 - L10 were not covered by tests
KerberosSource,
Krb5ConfContext,
UserKerberosSourceConnection,
)

LOGGER = get_logger()

Check warning on line 16 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L16

Added line #L16 was not covered by tests


class KerberosBackend(InbuiltBackend):

Check warning on line 19 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L19

Added line #L19 was not covered by tests
"""Authenticate users against Kerberos realm"""

def authenticate(self, request: HttpRequest, **kwargs):

Check warning on line 22 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L22

Added line #L22 was not covered by tests
"""Try to authenticate a user via kerberos"""
if "password" not in kwargs or "username" not in kwargs:
return None
username = kwargs.pop("username")
realm = None
if "@" in username:
username, realm = username.rsplit("@", 1)

Check warning on line 29 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L24-L29

Added lines #L24 - L29 were not covered by tests

user, source = self.auth_user(username, realm, **kwargs)
if user:
self.set_method("kerberos", request, source=source)
return user
return None

Check warning on line 35 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L31-L35

Added lines #L31 - L35 were not covered by tests

def auth_user(

Check warning on line 37 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L37

Added line #L37 was not covered by tests
self, username: str, realm: str | None, password: str, **filters
) -> tuple[User | None, KerberosSource | None]:
sources = KerberosSource.objects.filter(enabled=True, password_login_enabled=True)
user = User.objects.filter(usersourceconnection__source__in=sources, **filters).first()

Check warning on line 41 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L40-L41

Added lines #L40 - L41 were not covered by tests

if user is not None:

Check warning on line 43 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L43

Added line #L43 was not covered by tests
# User found, let's get its connections for the sources that are available
user_source_connections = UserKerberosSourceConnection.objects.filter(

Check warning on line 45 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L45

Added line #L45 was not covered by tests
user=user, source__in=sources
)
elif realm is not None:
user_source_connections = UserKerberosSourceConnection.objects.filter(

Check warning on line 49 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L48-L49

Added lines #L48 - L49 were not covered by tests
source__in=sources, identifier__iexact=f"{username}@{realm}"
source__in=sources, identifier=f"{username}@{realm}"
)
# no realm specified, we can't do anything
else:
user_source_connections = UserKerberosSourceConnection.objects.none()

Check warning on line 54 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L54

Added line #L54 was not covered by tests

if not user_source_connections.exists():
LOGGER.debug("no kerberos source found for user", username=username)
return None, None

Check warning on line 58 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L56-L58

Added lines #L56 - L58 were not covered by tests

for user_source_connection in user_source_connections.prefetch_related().select_related(

Check warning on line 60 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L60

Added line #L60 was not covered by tests
"source__kerberossource"
):
# User either has an unusable password,
# or has a password, but couldn't be authenticated by ModelBackend
# This means we check with a kinit to see if the Kerberos password has changed
if self.auth_user_by_kinit(user_source_connection, password):

Check warning on line 66 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L66

Added line #L66 was not covered by tests
# Password was successful in kinit to Kerberos, so we save it in database
LOGGER.debug(

Check warning on line 68 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L68

Added line #L68 was not covered by tests
"Updating user's password in DB",
source=user_source_connection.source,
user=user_source_connection.user,
)
if (

Check warning on line 73 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L73

Added line #L73 was not covered by tests
user_source_connection.source.kerberossource.password_login_update_internal_password
):
user_source_connection.user.set_password(password)
user_source_connection.user.save()
return user, user_source_connection.source

Check warning on line 78 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L76-L78

Added lines #L76 - L78 were not covered by tests
# Password doesn't match, onto next source
LOGGER.debug(

Check warning on line 80 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L80

Added line #L80 was not covered by tests
"failed to kinit, password invalid",
source=user_source_connection.source,
user=user_source_connection.user,
)
# No source with valid password found
LOGGER.debug("no valid kerberos source found for user", user=user)
return None, None

Check warning on line 87 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L86-L87

Added lines #L86 - L87 were not covered by tests

def auth_user_by_kinit(

Check warning on line 89 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L89

Added line #L89 was not covered by tests
self, user_source_connection: UserKerberosSourceConnection, password: str
) -> bool:
"""Attempt authentication by kinit to the source."""
LOGGER.debug(

Check warning on line 93 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L93

Added line #L93 was not covered by tests
"Attempting to kinit as user",
user=user_source_connection.user,
source=user_source_connection.source,
principal=user_source_connection.identifier,
)

with Krb5ConfContext(user_source_connection.source.kerberossource):
name = gssapi.raw.import_name(

Check warning on line 101 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L100-L101

Added lines #L100 - L101 were not covered by tests
user_source_connection.identifier.encode(), gssapi.raw.NameType.kerberos_principal
)
try:

Check warning on line 104 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L104

Added line #L104 was not covered by tests
# Use a temporary credentials cache to not interfere with whatever is defined
# elsewhere
gssapi.raw.ext_krb5.krb5_ccache_name(f"MEMORY:{generate_id(12)}".encode())
gssapi.raw.ext_password.acquire_cred_with_password(name, password.encode())

Check warning on line 108 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L107-L108

Added lines #L107 - L108 were not covered by tests
# Restore the credentials cache to what it was before
gssapi.raw.ext_krb5.krb5_ccache_name(None)
return True
except gssapi.exceptions.GSSError as exc:
LOGGER.warning("failed to kinit", exc=exc)
return False

Check warning on line 114 in authentik/sources/kerberos/auth.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/auth.py#L110-L114

Added lines #L110 - L114 were not covered by tests
2 changes: 1 addition & 1 deletion authentik/sources/kerberos/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,88 +33,88 @@
manager: PropertyMappingManager

def __init__(self, source: KerberosSource):
self._source = source
self._connection = self._source.connection()
self._messages = []
self._logger = get_logger().bind(source=self._source, syncer=self.__class__.__name__)
self.mapper = SourceMapper(self._source)
self.manager = self.mapper.get_manager(User, ["principal"])

Check warning on line 41 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L36-L41

Added lines #L36 - L41 were not covered by tests

@staticmethod
def name() -> str:
"""UI name for the type of object this class synchronizes"""
return "users"

Check warning on line 46 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L46

Added line #L46 was not covered by tests

@property
def messages(self) -> list[str]:
"""Get all UI messages"""
return self._messages

Check warning on line 51 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L51

Added line #L51 was not covered by tests

def message(self, *args, **kwargs):
"""Add message that is later added to the System Task and shown to the user"""
formatted_message = " ".join(args)
self._messages.append(formatted_message)
self._logger.warning(*args, **kwargs)

Check warning on line 57 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L55-L57

Added lines #L55 - L57 were not covered by tests

def _sync_principal(self, principal: str) -> bool:
try:
defaults = self.mapper.build_object_properties(

Check warning on line 61 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L60-L61

Added lines #L60 - L61 were not covered by tests
object_type=User, manager=self.manager, user=None, request=None, principal=principal
)
self._logger.debug("Writing user with attributes", **defaults)
if "username" not in defaults:
raise IntegrityError("Username was not set by propertymappings")
ak_user, created = self.update_or_create_user(principal, defaults)
except PropertyMappingExpressionException as exc:
raise StopSync(exc, None, exc.mapping) from exc
except SkipObjectException:
return False
except (IntegrityError, FieldError, TypeError, AttributeError) as exc:
Event.new(

Check warning on line 73 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L64-L73

Added lines #L64 - L73 were not covered by tests
EventAction.CONFIGURATION_ERROR,
message=(f"Failed to create user: {str(exc)} "),
source=self._source,
principal=principal,
).save()
return False
self._logger.debug("Synced User", user=ak_user.username, created=created)
return True

Check warning on line 81 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L79-L81

Added lines #L79 - L81 were not covered by tests

def update_or_create_user(self, principal: str, data: dict[str, Any]) -> tuple[User, bool]:
"""
Same as django's update_or_create but correctly update attributes by merging dicts,
and create a UserKerberosSourceConnection object if needed
"""
user_source_connection = UserKerberosSourceConnection.objects.filter(

Check warning on line 88 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L88

Added line #L88 was not covered by tests
source=self._source, identifier__iexact=principal
source=self._source, identifier=principal
).first()

# TODO: handle groups

# User doesn't exists
if not user_source_connection:
with transaction.atomic():
user = User.objects.create(**data)
if user.type == UserTypes.INTERNAL_SERVICE_ACCOUNT:
user.set_unusable_password()
user.save()
user_source_connection = UserKerberosSourceConnection.objects.create(

Check warning on line 101 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L95-L101

Added lines #L95 - L101 were not covered by tests
source=self._source, user=user, identifier=principal
)
return user, True

Check warning on line 104 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L104

Added line #L104 was not covered by tests

user_source_connection.user.update_attributes(data)
return user_source_connection.user, False

Check warning on line 107 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L106-L107

Added lines #L106 - L107 were not covered by tests

def sync(self) -> int:
"""Iterate over all Kerberos users and create authentik_core.User instances"""
if not self._source.enabled or not self._source.sync_users:
self.message("Source is disabled or user syncing is disabled for this Source")
return -1

Check warning on line 113 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L111-L113

Added lines #L111 - L113 were not covered by tests

user_count = 0
with Krb5ConfContext(self._source):
for principal in self._connection.principals():
if self._sync_principal(principal):
user_count += 1
return user_count

Check warning on line 120 in authentik/sources/kerberos/sync.py

View check run for this annotation

Codecov / codecov/patch

authentik/sources/kerberos/sync.py#L115-L120

Added lines #L115 - L120 were not covered by tests
Loading